storysplat-viewer 2.9.3 → 2.9.4

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 CHANGED
@@ -1,2 +1,2 @@
1
- import*as t from"playcanvas";function e(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}function n(t){return t.replace(/<\/script/gi,"<\\/script")}function o(t,o={}){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:d}=o,p=c??t.uiOptions?.lazyLoad??!1,h=d??t.uiOptions?.lazyLoadButtonText,u=r?`<link rel="icon" href="${e(r)}" />`:"",m=l?`<style>${l}</style>`:"",g=n(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 = ${g};\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: ${p},\n lazyLoadButtonText: ${h?`'${n(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 n=await fetch(t);if(!n.ok)throw new Error(`Failed to fetch scene: ${n.statusText}`);return o(await n.json(),e)}function s(t){const e=t.loadedModelUrl||t.splatUrl||"",n=t.sogModelUrl||t.sogUrl,o=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;n?c=n:o?c=o: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:n,compressedPlyUrl:o,selected:c});const d=(t.waypoints||[]).map(t=>{let e=t.fov||60;e<3.5&&(e*=180/Math.PI);let n=t.info||t.description||"";if(!n&&t.interactions){const e=t.interactions.find(t=>"info"===t.type),o=function(t){if(!t||"object"!=typeof t)return;const e=t;return"string"==typeof e.text?e.text:void 0}(e?.data);o&&(n=o)}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:n,interactions:t.interactions||[],triggerDistance:t.triggerDistance??1}}),p=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:n,lodMetaUrl:i,fallbackUrls:[...t.fallbackUrls||[],n!==c?n:null,o!==c?o: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:d,hotspots:t.hotspots||[],portals:t.portals||[],skybox:a,skyboxUrl:a?.url,skyboxRotation:a?.rotation,customMeshes:t.customMeshes||[],htmlMeshes:t.htmlMeshes||[],lights:(t.lights||[]).map(t=>"hemispheric"===t.type?{...t,type:"directional",direction:t.direction??{x:0,y:-1,z:0}}:t),particles:p,collisionMeshesData:t.collisionMeshesData||[],voxelCollisionUrl:t.voxelCollisionUrl,playerHeight:t.playerHeight,walkSpeed:t.walkSpeed,uiColor:t.uiColor||"#ffffff",uiOptions:{showStartExperience:t.uiOptions?.showStartExperience??!1,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,hideProgressText:t.uiOptions?.hideProgressText,viewerTheme:t.uiOptions?.viewerTheme},defaultCameraMode:t.defaultCameraMode||"tour",allowedCameraModes:t.allowedCameraModes||["tour","explore"],cameraMovementSpeed:t.cameraMovementSpeed,cameraRotationSensitivity:t.cameraRotationSensitivity,cameraDamping:t.cameraDamping,invertCameraRotation:t.invertCameraRotation,fov:null!=t.fov?t.fov<3.5?t.fov*(180/Math.PI):t.fov:void 0,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??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||[],doubleTapMoveSpeed:t.doubleTapMoveSpeed,headBobEnabled:t.headBobEnabled,frameSequence:t.frameSequence,splatRelighting:t.splatRelighting,revealEffect:t.revealEffect??(!1===t.useNodeMaterial?"none":void 0),revealStyle:t.revealStyle,swapTransitionType:t.swapTransitionType,lodSettings:t.lodSettings}}function a(t){const e=t.fov||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,voxelCollisionUrl:t.voxelCollisionUrl,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.8,walkSpeed:t.walkSpeed,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||[],doubleTapMoveSpeed:t.doubleTapMoveSpeed,headBobEnabled:t.headBobEnabled,frameSequence:t.frameSequence,splatRelighting:t.splatRelighting,revealEffect:t.revealEffect,revealStyle:t.revealStyle,swapTransitionType:t.swapTransitionType,lodSettings:t.lodSettings}}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",helpTourControls:"Tour Mode:",helpTourScroll:"Scroll - Move along path",helpTourDrag:"Drag - Look around",helpExploreControls:"Explore Mode:",helpExploreLMB:"LMB Drag - Orbit camera",helpExploreRMB:"RMB Drag - Fly/look",helpExploreWASD:"WASD/QE - Move camera",helpExploreShift:"Shift - Move fast",helpExploreScroll:"Scroll/Pinch - Zoom",helpExploreDblClick:"Double-click - Focus",helpWalkControls:"Walk Mode:",helpWalkClick:"Click to lock mouse",helpWalkWASD:"WASD/Arrows - Move",helpWalkMouse:"Mouse - Look around",helpWalkShift:"Shift - Sprint",helpWalkSpace:"Space - Jump",errorWebGLTitle:"Unable to Initialize 3D Graphics",errorWebGLMessage:"Your browser or device may not support WebGL. Please try a different browser or device.",percentageFormat:"{n}%",scenes:"Scenes"};function l(t,e){return t?.[e]||r[e]}function c(t="#CC5833",e="minimal",n){const o=n||{},i=o.globalTextColor||"white";return`\n @keyframes storysplat-morph {\n 0% { border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%; }\n 25% { border-radius: 30% 60% 70% 40% / 50% 60% 30% 60%; }\n 50% { border-radius: 20% 70% 50% 50% / 30% 30% 70% 70%; }\n 75% { border-radius: 70% 30% 40% 60% / 60% 30% 50% 40%; }\n 100% { border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%; }\n }\n\n @keyframes storysplat-spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n }\n\n @keyframes storysplat-float {\n 0%, 100% { transform: translateY(0) scale(1); }\n 50% { transform: translateY(-5px) scale(0.98); }\n }\n\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: ${o.fontFamily||"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: ${o.preloaderBg||"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: 16px;\n }\n\n .storysplat-preloader-image {\n height: 64px;\n width: auto;\n object-fit: contain;\n }\n\n .storysplat-preloader-image-inverted {\n height: 64px;\n width: auto;\n object-fit: contain;\n filter: invert(1);\n margin-right: -32px;\n }\n\n .storysplat-preloader-logo-text {\n font-family: 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif;\n font-weight: 700;\n font-size: 28px;\n color: white;\n white-space: nowrap;\n letter-spacing: -0.02em;\n }\n\n .storysplat-loader-container {\n position: relative;\n width: 64px;\n height: 64px;\n display: flex;\n align-items: center;\n justify-content: center;\n animation: storysplat-float 3s ease-in-out infinite;\n }\n\n .storysplat-blob {\n position: absolute;\n inset: -15%;\n background: #CC5833;\n opacity: 0.85;\n animation: storysplat-morph 4s ease-in-out infinite, storysplat-spin 12s linear infinite;\n box-shadow: inset 10px 0 20px rgba(0,0,0,0.1), 0 5px 15px rgba(204, 88, 51, 0.4);\n filter: blur(1px);\n }\n\n .storysplat-core {\n width: 50%;\n height: 50%;\n background-color: #e5e7eb;\n border-radius: 40% 60% 70% 30% / 40% 50% 60% 50%;\n box-shadow: inset -5px -5px 10px rgba(0,0,0,0.1), 0 2px 8px rgba(0,0,0,0.2);\n position: relative;\n z-index: 2;\n animation: storysplat-morph 5s ease-in-out infinite reverse, storysplat-spin 18s linear infinite reverse;\n }\n\n @media (max-width: 768px) {\n .storysplat-preloader-image,\n .storysplat-preloader-image-inverted {\n height: 40px;\n }\n .storysplat-preloader-image-inverted {\n margin-right: -20px;\n }\n .storysplat-loader-container {\n height: 40px;\n width: 40px;\n }\n .storysplat-preloader-logo-text {\n font-size: 20px;\n }\n }\n\n .storysplat-preloader-progress {\n width: 200px;\n height: ${o.preloaderBarHeight||"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: ${o.preloaderTextColor||"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: ${o.progressFontSize||"14px"};\n color: ${i};\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: ${o.progressBarHeight||"3px"};\n background: ${"white"===i?"rgba(255, 255, 255, 0.2)":"rgba(0, 0, 0, 0.15)"};\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: ${o.buttonBg||"rgba(0, 0, 0, 0.3)"};\n border: none;\n color: ${o.buttonTextColor||"white"};\n padding: 4px 8px;\n font-size: ${o.buttonFontSize||"12px"};\n border-radius: ${o.buttonBorderRadius||"3px"};\n cursor: pointer;\n transition: background 0.2s;\n text-align: center;\n }\n\n .storysplat-btn:hover {\n background: ${o.buttonHoverBg||"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: ${i};\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: ${i};\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: ${i};\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: ${i};\n padding: 3px 6px;\n font-size: ${o.modeBtnFontSize||"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: ${i};\n }\n\n /* Mute Button - Minimal (left side, next to help) */\n .storysplat-mute-btn {\n position: absolute;\n top: 10px;\n left: 45px;\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: ${i};\n }\n\n /* Relighting Toggle Button */\n .storysplat-relight-btn {\n position: absolute;\n top: 10px;\n left: 80px;\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 transition: opacity 0.3s ease;\n }\n\n .storysplat-relight-btn svg {\n width: 20px;\n height: 20px;\n fill: ${i};\n transition: fill 0.3s ease;\n }\n\n .storysplat-relight-btn.active svg {\n fill: #FFD700;\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: ${i};\n border: 1px solid ${"white"===i?"rgba(255, 255, 255, 0.15)":"rgba(0, 0, 0, 0.12)"};\n cursor: pointer;\n font-size: 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1002;\n transition: all 0.2s ease;\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n }\n\n .storysplat-help-btn:hover {\n background: rgba(0, 0, 0, 0.5);\n transform: scale(1.05);\n }\n\n .storysplat-help-btn.active {\n background: ${t};\n border-color: ${t};\n }\n\n .storysplat-help-panel {\n position: absolute;\n top: 50px;\n left: 10px;\n background: ${o.helpPanelBg||"rgba(0, 0, 0, 0.85)"};\n color: ${i};\n padding: 0;\n border-radius: ${o.helpPanelBorderRadius||"8px"};\n max-width: 260px;\n z-index: 1001;\n font-size: 11px;\n border: 1px solid ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.1)"};\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);\n opacity: 0;\n transform: translateY(-8px);\n pointer-events: none;\n transition: opacity 0.2s ease, transform 0.2s ease;\n }\n\n .storysplat-help-panel.visible {\n opacity: 1;\n transform: translateY(0);\n pointer-events: auto;\n }\n\n .storysplat-help-tabs {\n display: flex;\n border-bottom: 1px solid ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.1)"};\n }\n\n .storysplat-help-tab {\n flex: 1;\n padding: 8px 4px;\n background: none;\n border: none;\n color: ${i};\n opacity: 0.5;\n font-size: 10px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.15s ease;\n text-transform: uppercase;\n text-align: center;\n letter-spacing: 0.5px;\n border-bottom: 2px solid transparent;\n }\n\n .storysplat-help-tab:hover {\n opacity: 0.8;\n }\n\n .storysplat-help-tab.active {\n color: ${t};\n opacity: 1;\n border-bottom-color: ${t};\n }\n\n .storysplat-help-tab-content {\n display: none;\n padding: 10px 12px;\n }\n\n .storysplat-help-tab-content.active {\n display: block;\n }\n\n .storysplat-help-row {\n display: flex;\n align-items: center;\n padding: 3px 0;\n gap: 8px;\n }\n\n .storysplat-help-key {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 22px;\n height: 20px;\n padding: 0 5px;\n background: ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.08)"};\n border: 1px solid ${"white"===i?"rgba(255, 255, 255, 0.15)":"rgba(0, 0, 0, 0.12)"};\n border-radius: 3px;\n font-size: 9px;\n font-weight: 600;\n font-family: system-ui, -apple-system, sans-serif;\n white-space: nowrap;\n flex-shrink: 0;\n }\n\n .storysplat-help-desc {\n color: ${i};\n opacity: 0.7;\n font-size: 11px;\n }\n\n /* XR (VR/AR) Buttons - Minimal, positioned to the right of the ? help button */\n .storysplat-xr-btn {\n position: absolute;\n bottom: 10px;\n left: 10px;\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: ${i};\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 left: 10px;\n }\n\n .storysplat-help-panel h3 {\n margin: 0;\n padding: 10px 12px;\n font-size: 12px;\n font-weight: 600;\n text-align: center;\n border-bottom: 1px solid ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.1)"};\n }\n\n .storysplat-help-panel p {\n margin: 3px 0;\n line-height: 1.3;\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: ${o.infoBannerBg||"rgba(0, 0, 0, 0.5)"};\n color: ${i};\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: ${o.infoBannerTitleFontSize||"18px"};\n font-weight: bold;\n }\n\n .storysplat-waypoint-info p {\n margin: 0;\n font-size: ${o.infoBannerContentFontSize||"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: ${o.popupBg||"rgba(0, 0, 0, 0.75)"};\n color: ${o.popupTextColor||"white"};\n padding: 20px;\n border-radius: ${o.popupBorderRadius||"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: ${o.fontFamily||"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 /* Fits content and stays centered rather than filling the whole screen */\n .storysplat-hotspot-popup.fullscreen {\n top: 0 !important;\n left: 0 !important;\n right: 0 !important;\n bottom: 0 !important;\n transform: none !important;\n width: min(calc(100% - 40px), 840px) !important;\n height: fit-content !important;\n max-height: calc(100% - 40px) !important;\n margin: auto !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: 20px !important;\n overflow: auto !important;\n box-sizing: border-box !important;\n }\n\n .storysplat-hotspot-popup h2,\n .storysplat-hotspot-popup-title {\n margin: 0 0 10px 0;\n font-size: ${o.popupTitleFontSize||"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: ${o.popupContentFontSize||"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: 0 0 10px 0;\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 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: 10px auto 0 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: 100% !important;\n max-height: 70vh !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: 100% !important;\n max-width: 100% !important;\n height: auto !important;\n aspect-ratio: 16 / 9 !important;\n max-height: 70vh !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: ${o.portalPopupBg||"rgba(0, 0, 0, 0.85)"};\n color: ${i};\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: ${i};\n }\n\n /* Model container for 3D model lightbox */\n .storysplat-hotspot-popup .model-container {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n aspect-ratio: 1 / 1;\n max-height: 70vh;\n margin: 10px 0;\n position: relative;\n border-radius: 8px;\n overflow: hidden;\n background: rgba(0, 0, 0, 0.3);\n }\n .storysplat-hotspot-popup .model-container canvas {\n width: 100% !important;\n height: 100% !important;\n display: block;\n border-radius: 8px;\n }\n .storysplat-hotspot-popup .model-container .model-loading {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: rgba(255, 255, 255, 0.7);\n font-size: 14px;\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: 100% !important;\n max-width: 100% !important;\n max-height: 70vh !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: 70vh !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: ${o.popupCloseBtnColor||t};\n border: none;\n color: white;\n cursor: pointer;\n border-radius: ${o.buttonBorderRadius||"5px"};\n margin: 5px 0 0 0;\n font-size: 14px;\n font-weight: 500;\n font-family: ${o.fontFamily||"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 opacity: 0.85;\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: ${o.errorPopupBg||"rgba(0, 0, 0, 0.95)"};\n border-radius: ${o.errorPopupBorderRadius||"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: ${o.errorTitleColor||"#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, #CC5833, #b54d2d);\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: ${o.popupLinkBtnColor||"#007bff"};\n color: white;\n text-decoration: none;\n border-radius: ${o.buttonBorderRadius||"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 {\n max-width: calc(100% - 40px);\n }\n\n .storysplat-hotspot-popup.fullscreen {\n width: min(calc(100% - 20px), 840px) !important;\n max-height: calc(100% - 20px) !important;\n padding: 15px !important;\n }\n\n .storysplat-hotspot-popup-title {\n font-size: 16px !important;\n }\n }\n\n @media (max-width: 540px) {\n .storysplat-scroll-controls {\n margin-bottom: 45px;\n }\n\n .storysplat-hotspot-popup {\n max-width: calc(100% - 20px);\n }\n\n .storysplat-hotspot-popup.fullscreen {\n width: min(calc(100% - 10px), 840px) !important;\n max-height: calc(100% - 10px) !important;\n padding: 10px !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: 130px;\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: ${o.joystickBaseColor||"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: ${o.joystickThumbColor||"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: 130px;\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 /* WASD Key Hint (desktop fly mode) */\n @keyframes storysplat-hint-fadein {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n }\n\n .storysplat-wasd-hint {\n position: absolute;\n bottom: 20px;\n left: 20px;\n z-index: 2000;\n pointer-events: none;\n display: none;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n animation: storysplat-hint-fadein 0.4s ease-out;\n }\n\n .storysplat-wasd-hint.visible {\n display: flex;\n pointer-events: auto;\n }\n\n .storysplat-wasd-hint-row {\n display: flex;\n gap: 4px;\n justify-content: center;\n }\n\n .storysplat-wasd-key {\n width: 36px;\n height: 36px;\n background: rgba(255, 255, 255, 0.1);\n border: 1.5px solid rgba(255, 255, 255, 0.25);\n border-radius: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n font-size: 13px;\n font-weight: 600;\n color: rgba(255, 255, 255, 0.5);\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n cursor: pointer;\n user-select: none;\n -webkit-user-select: none;\n transition: background 0.1s ease, border-color 0.1s ease, color 0.1s ease, box-shadow 0.1s ease;\n }\n\n .storysplat-wasd-key:hover {\n background: rgba(255, 255, 255, 0.15);\n border-color: rgba(255, 255, 255, 0.35);\n color: rgba(255, 255, 255, 0.7);\n }\n\n .storysplat-wasd-key.active {\n background: rgba(255, 255, 255, 0.3);\n border-color: rgba(255, 255, 255, 0.6);\n color: rgba(255, 255, 255, 0.95);\n box-shadow: 0 0 8px rgba(255, 255, 255, 0.15);\n }\n\n .storysplat-wasd-hint-label {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n font-size: 10px;\n color: rgba(255, 255, 255, 0.35);\n margin-top: 2px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n\n /* Orbit hint (explore orbit mode — drag to rotate / double-click to refocus) */\n .storysplat-orbit-hint {\n position: absolute;\n bottom: 20px;\n left: 20px;\n z-index: 2000;\n pointer-events: none;\n display: none;\n flex-direction: column;\n align-items: flex-start;\n gap: 8px;\n animation: storysplat-hint-fadein 0.4s ease-out;\n }\n\n .storysplat-orbit-hint.visible {\n display: flex;\n }\n\n .storysplat-orbit-hint-row {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .storysplat-orbit-hint-icon {\n width: 28px;\n height: 28px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(255, 255, 255, 0.1);\n border: 1.5px solid rgba(255, 255, 255, 0.25);\n border-radius: 6px;\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n flex-shrink: 0;\n }\n\n .storysplat-orbit-hint-icon svg {\n width: 16px;\n height: 16px;\n fill: rgba(255, 255, 255, 0.5);\n }\n\n .storysplat-orbit-hint-label {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n font-size: 11px;\n color: rgba(255, 255, 255, 0.45);\n white-space: nowrap;\n letter-spacing: 0.3px;\n }\n\n /* Double-tap hint (explore mode) */\n .storysplat-doubletap-hint {\n position: absolute;\n bottom: 90px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 2000;\n pointer-events: none;\n display: none;\n align-items: center;\n gap: 8px;\n padding: 8px 16px;\n background: rgba(0, 0, 0, 0.5);\n border: 1px solid rgba(255, 255, 255, 0.15);\n border-radius: 20px;\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n animation: storysplat-hint-fadein 0.4s ease-out;\n transition: opacity 0.4s ease-out;\n }\n\n .storysplat-doubletap-hint.visible {\n display: flex;\n }\n\n .storysplat-doubletap-hint.fading {\n opacity: 0;\n }\n\n .storysplat-doubletap-hint-icon {\n width: 20px;\n height: 20px;\n flex-shrink: 0;\n }\n\n .storysplat-doubletap-hint-icon svg {\n width: 20px;\n height: 20px;\n fill: rgba(255, 255, 255, 0.5);\n }\n\n .storysplat-doubletap-hint-text {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n font-size: 12px;\n color: rgba(255, 255, 255, 0.6);\n white-space: nowrap;\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: ${o.lazyLoadBg||"#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: ${o.lazyLoadBtnBorderRadius||"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: ${o.buttonBg||"rgba(0, 0, 0, 0.3)"};\n border: none;\n border-radius: 8px;\n color: ${o.buttonTextColor||"white"};\n cursor: pointer;\n font-size: 14px;\n backdrop-filter: blur(${o.dropdownBlur||"10px"});\n -webkit-backdrop-filter: blur(${o.dropdownBlur||"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: ${o.buttonHoverBg||"rgba(0, 0, 0, 0.5)"};\n }\n\n .storysplat-waypoint-list-toggle svg {\n width: 16px;\n height: 16px;\n fill: ${i};\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: ${o.dropdownBg||"rgba(0, 0, 0, 0.8)"};\n border-radius: ${o.dropdownBorderRadius||"8px"};\n backdrop-filter: blur(${o.dropdownBlur||"10px"});\n -webkit-backdrop-filter: blur(${o.dropdownBlur||"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: ${i};\n cursor: pointer;\n border-bottom: 1px solid ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.08)"};\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: ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.05)"};\n }\n\n .storysplat-waypoint-item.active {\n background: ${t}40;\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: ${o.watermarkBg||"rgba(0, 0, 0, 0.5)"};\n color: ${i};\n padding: 5px 10px;\n border-radius: 5px;\n font-size: ${o.watermarkFontSize||"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 bottom: 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\n /* Scene Navigation Menu */\n .storysplat-scene-menu-container {\n position: absolute;\n top: 10px;\n left: 10px;\n z-index: 1001;\n font-family: ${o.fontFamily||"inherit"};\n }\n\n .storysplat-scene-menu-toggle {\n display: flex;\n align-items: center;\n gap: 6px;\n background: ${o.buttonBg||"rgba(0, 0, 0, 0.5)"};\n backdrop-filter: blur(${o.dropdownBlur||"10px"});\n -webkit-backdrop-filter: blur(${o.dropdownBlur||"10px"});\n color: ${o.buttonTextColor||i};\n border: 1px solid rgba(255, 255, 255, 0.1);\n border-radius: ${o.buttonBorderRadius||"8px"};\n padding: 8px 12px;\n cursor: pointer;\n font-size: ${o.buttonFontSize||"13px"};\n font-family: inherit;\n transition: background 0.2s, border-color 0.2s;\n }\n\n .storysplat-scene-menu-toggle:hover {\n background: ${o.buttonHoverBg||"rgba(0, 0, 0, 0.7)"};\n border-color: rgba(255, 255, 255, 0.2);\n }\n\n .storysplat-scene-menu-toggle svg {\n width: 16px;\n height: 16px;\n fill: currentColor;\n flex-shrink: 0;\n }\n\n .storysplat-scene-menu-toggle.open svg {\n transform: rotate(90deg);\n }\n\n .storysplat-scene-menu-label {\n white-space: nowrap;\n }\n\n .storysplat-scene-menu-dropdown {\n display: none;\n position: absolute;\n top: calc(100% + 6px);\n left: 0;\n min-width: 220px;\n max-width: 320px;\n max-height: 60vh;\n overflow-y: auto;\n background: ${o.dropdownBg||"rgba(0, 0, 0, 0.75)"};\n backdrop-filter: blur(${o.dropdownBlur||"12px"});\n -webkit-backdrop-filter: blur(${o.dropdownBlur||"12px"});\n border: 1px solid rgba(255, 255, 255, 0.1);\n border-radius: ${o.dropdownBorderRadius||"10px"};\n padding: 6px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);\n }\n\n .storysplat-scene-menu-dropdown.open {\n display: block;\n }\n\n .storysplat-scene-menu-dropdown::-webkit-scrollbar {\n width: 4px;\n }\n\n .storysplat-scene-menu-dropdown::-webkit-scrollbar-thumb {\n background: rgba(255, 255, 255, 0.2);\n border-radius: 2px;\n }\n\n .storysplat-scene-menu-folder {\n margin-bottom: 2px;\n }\n\n .storysplat-scene-menu-folder-header {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 8px;\n color: ${i};\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n opacity: 0.7;\n cursor: pointer;\n border-radius: 6px;\n transition: opacity 0.15s;\n user-select: none;\n }\n\n .storysplat-scene-menu-folder-header:hover {\n opacity: 1;\n }\n\n .storysplat-scene-menu-folder-header svg {\n width: 10px;\n height: 10px;\n fill: currentColor;\n transition: transform 0.15s;\n flex-shrink: 0;\n }\n\n .storysplat-scene-menu-folder-header.collapsed svg {\n transform: rotate(-90deg);\n }\n\n .storysplat-scene-menu-folder-children {\n padding-left: 12px;\n }\n\n .storysplat-scene-menu-folder-children.collapsed {\n display: none;\n }\n\n .storysplat-scene-menu-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 10px;\n color: ${i};\n font-size: ${o.buttonFontSize||"13px"};\n cursor: pointer;\n border-radius: 6px;\n transition: background 0.15s;\n text-decoration: none;\n }\n\n .storysplat-scene-menu-item:hover {\n background: rgba(255, 255, 255, 0.1);\n }\n\n .storysplat-scene-menu-item-thumb {\n width: 32px;\n height: 32px;\n border-radius: 4px;\n object-fit: cover;\n flex-shrink: 0;\n background: rgba(255, 255, 255, 0.05);\n }\n\n .storysplat-scene-menu-item-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n @media (max-width: 768px) {\n .storysplat-scene-menu-label {\n display: none;\n }\n\n .storysplat-scene-menu-toggle {\n padding: 8px 10px;\n }\n\n .storysplat-scene-menu-dropdown {\n min-width: 200px;\n max-width: 80vw;\n }\n }\n `+function(t,e){if("standard"===e)return`\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 `;if("pro"===e)return"\n /* ===== PRO (Dark) Template Overrides ===== */\n\n /* Top-right bar: holds mode toggle + waypoint list */\n .storysplat-top-right-bar {\n position: absolute;\n top: 10px;\n right: 10px;\n display: flex;\n align-items: center;\n gap: 8px;\n z-index: 1000;\n }\n\n .storysplat-top-right-bar.with-fullscreen {\n right: 45px;\n }\n\n /* Override absolute positioning when inside top-right-bar */\n .storysplat-top-right-bar .storysplat-waypoint-list-container,\n .storysplat-top-right-bar .storysplat-waypoint-list-container.with-fullscreen,\n .storysplat-top-right-bar .storysplat-waypoint-list-container.no-fullscreen {\n position: relative;\n top: auto;\n right: auto;\n }\n\n .storysplat-scroll-controls {\n width: auto;\n padding: 8px 12px;\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-text {\n display: none;\n }\n\n .storysplat-progress-container {\n width: 100%;\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-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 in top-right bar, horizontal layout */\n .storysplat-mode-container {\n position: relative;\n bottom: auto;\n left: auto;\n margin-top: 0;\n }\n\n .storysplat-mode-toggle {\n flex-direction: row;\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.3);\n border: none;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n }\n\n .storysplat-mode-btn:hover {\n background: rgba(0, 0, 0, 0.5);\n }\n\n .storysplat-explore-controls {\n flex-direction: row;\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 padding: 6px 10px;\n bottom: 10px;\n }\n\n .storysplat-top-right-bar {\n right: 10px;\n gap: 6px;\n }\n\n .storysplat-top-right-bar.with-fullscreen {\n right: 40px;\n }\n\n .storysplat-btn {\n padding: 6px 12px;\n font-size: 12px;\n }\n }\n\n @media (max-width: 540px) {\n .storysplat-wasd-hint {\n transform: translateY(-50%);\n }\n .storysplat-orbit-hint {\n transform: translateY(-50%);\n }\n }\n ";return""}(t,e)+function(t){if(!t)return"";const e=t.elements,n=t.mobile;if(!e&&!n)return"";let o="\n/* ===== Custom UI Layout Overrides ===== */\n";const i=t=>{let e="";for(const[n,o]of Object.entries(t)){if(!o)continue;const t=d[n];if(!t)continue;const i=h(o,n);e+=` ${t} { ${p.has(n)?"position: absolute !important; z-index: 1001 !important; ":""}${i}; }\n`,"waypointDropdown"===n&&(e+=` ${t}.with-fullscreen { ${i}; }\n`,e+=` ${t}.no-fullscreen { ${i}; }\n`)}return e};e&&Object.keys(e).length>0&&(o+="@media (min-width: 769px) {\n",o+=i(e),o+="}\n");n&&Object.keys(n).length>0&&(o+="@media (max-width: 768px) {\n",o+=i(n),o+="}\n");return o}(o?.uiLayout)}const d={helpButton:".storysplat-help-btn",muteButton:".storysplat-mute-btn",fullscreenButton:".storysplat-fullscreen-btn",watermark:".storysplat-watermark",waypointDropdown:".storysplat-waypoint-list-container",scrollControls:".storysplat-scroll-controls",waypointBanner:".storysplat-waypoint-info",progressText:".storysplat-progress-text",modeToggle:".storysplat-mode-container",exploreControls:".storysplat-explore-controls",prevButton:".storysplat-btn-prev",playButton:".storysplat-btn-play",nextButton:".storysplat-btn-next",hotspotPopup:".storysplat-hotspot-popup",portalPopup:".storysplat-portal-popup",relightingButton:".storysplat-relight-btn",sceneMenu:".storysplat-scene-menu-container"},p=new Set(["progressText","modeToggle","exploreControls","prevButton","playButton","nextButton"]);function h(t,e){const{anchor:n,offsetX:o,offsetY:i}=t,[s,a]=n.split("-"),r="center"!==s||a?s:"center",l=a||("center"===s?"center":"left"),c=["top: auto !important","right: auto !important","bottom: auto !important","left: auto !important"];"top"===r?c.push(`top: ${i}px !important`):"bottom"===r?c.push(`bottom: ${i}px !important`):c.push("top: 50% !important"),"waypointBanner"===e?"center"===l?c.push("left: 0 !important","right: 0 !important"):"left"===l?(c.push(`left: ${o}px !important`),c.push("right: auto !important")):(c.push(`right: ${o}px !important`),c.push("left: auto !important")):"left"===l?c.push(`left: ${o}px !important`):"right"===l?c.push(`right: ${o}px !important`):c.push("left: 50% !important");const d="center"===l&&"waypointBanner"!==e,p="center"===r;return"scrollControls"===e?d&&p?c.push("transform: translate(-50%, -50%) !important"):d?c.push("transform: translateX(-50%) !important"):p?c.push("transform: translateY(-50%) !important"):c.push("transform: none !important"):d&&p?c.push("transform: translate(-50%, -50%) !important"):d?c.push("transform: translateX(-50%) !important"):p&&c.push("transform: translateY(-50%) !important"),null!=t.width&&t.width>0&&c.push(`width: ${t.width}px !important`),null!=t.height&&t.height>0&&c.push(`height: ${t.height}px !important`),c.join("; ")}function u(t="#CC5833",e="minimal",n){const o=document.getElementById("storysplat-viewer-styles");o&&o.remove();const i=document.createElement("style");return i.id="storysplat-viewer-styles",i.textContent=c(t,e,n),document.head.appendChild(i),i}function m(t,e,n){const o=document.createElement("div");o.className="storysplat-preloader";const i=!!e,s=document.createElement("div");s.className="storysplat-preloader-content";const a=document.createElement("div");if(a.className="storysplat-preloader-media",i){const t=document.createElement("img");t.className="storysplat-preloader-image",t.src=e,t.alt="Custom Logo",a.appendChild(t)}else{const t=document.createElement("span");t.className="storysplat-preloader-logo-text",t.textContent="StorySplat",a.appendChild(t);const e=document.createElement("div");e.className="storysplat-loader-container";const n=document.createElement("div");n.className="storysplat-blob";const o=document.createElement("div");o.className="storysplat-core",e.appendChild(n),e.appendChild(o),a.appendChild(e)}s.appendChild(a);const r=document.createElement("div");r.className="storysplat-preloader-progress";const c=document.createElement("div");c.className="storysplat-preloader-text",c.textContent=`${l(n,"loading")} 0%`;const d=document.createElement("div");return d.className="storysplat-preloader-bar",r.appendChild(c),r.appendChild(d),s.appendChild(r),o.appendChild(s),t.appendChild(o),i||function(){if(document.querySelector('link[href*="Plus+Jakarta+Sans"]'))return;const t=document.createElement("link");t.rel="stylesheet",t.href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@700&display=swap",document.head.appendChild(t)}(),o}function g(t){t.classList.add("hidden"),setTimeout(()=>t.remove(),500)}function f(t,e,n={}){const{uiColor:o="#4CAF50",showScrollControls:i=!0,showModeToggle:s=!0,showFullscreenButton:a=!0,showHelpButton:r=!1,showMuteButton:c=!1,showPreloader:d=!0,allowedCameraModes:p=["tour","explore"],defaultCameraMode:h="tour",customPreloaderLogoUrl:g,buttonLabels:f,hideWatermark:y=!1,watermarkText:v,watermarkLink:x,sceneId:b,showWaypointList:w=!0,template:S="minimal",hideProgressText:_=!1,viewerTheme:M,showRelightingToggle:E=!1,showSceneMenu:C}=n,T={tour:l(f,"tour"),explore:l(f,"explore"),walk:l(f,"walk"),orbit:l(f,"orbit"),fly:l(f,"fly"),previous:l(f,"previous"),next:l(f,"next"),fullscreen:l(f,"fullscreen"),waypoints:l(f,"waypoints"),close:l(f,"close"),yes:l(f,"yes"),cancel:l(f,"cancel"),vr:l(f,"vr"),ar:l(f,"ar"),loading:l(f,"loading"),helpTitle:l(f,"helpTitle"),helpCameraModes:l(f,"helpCameraModes"),helpTourDesc:l(f,"helpTourDesc"),helpExploreDesc:l(f,"helpExploreDesc"),helpWalkDesc:l(f,"helpWalkDesc"),helpTourControls:l(f,"helpTourControls"),helpTourScroll:l(f,"helpTourScroll"),helpTourDrag:l(f,"helpTourDrag"),helpExploreControls:l(f,"helpExploreControls"),helpExploreLMB:l(f,"helpExploreLMB"),helpExploreRMB:l(f,"helpExploreRMB"),helpExploreWASD:l(f,"helpExploreWASD"),helpExploreShift:l(f,"helpExploreShift"),helpExploreScroll:l(f,"helpExploreScroll"),helpExploreDblClick:l(f,"helpExploreDblClick"),helpWalkControls:l(f,"helpWalkControls"),helpWalkClick:l(f,"helpWalkClick"),helpWalkWASD:l(f,"helpWalkWASD"),helpWalkMouse:l(f,"helpWalkMouse"),helpWalkShift:l(f,"helpWalkShift"),helpWalkSpace:l(f,"helpWalkSpace"),hotspotDefaultTitle:l(f,"hotspotDefaultTitle"),openExternalLink:l(f,"openExternalLink"),mute:l(f,"mute"),unmute:l(f,"unmute"),scenes:l(f,"scenes")},A={};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-scene-menu-container, .storysplat-hotspot-popup, .storysplat-portal-popup, .storysplat-wasd-hint, .storysplat-orbit-hint, .storysplat-doubletap-hint, .storysplat-look-zone, .storysplat-joystick-container, .storysplat-relight-btn, .storysplat-fps-counter, .storysplat-xr-btn, .storysplat-mode-container, .storysplat-explore-controls, .storysplat-top-right-bar").forEach(t=>t.remove()),u(o,S,M),t.classList.add("storysplat-viewer-container"),d&&(A.preloader=m(t,g,f));const P=document.createElement("div");if(P.className="storysplat-waypoint-info",P.innerHTML='\n <h2 class="storysplat-waypoint-title"></h2>\n <p class="storysplat-waypoint-description"></p>\n ',t.appendChild(P),A.waypointInfo=P,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">${T.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">${T.next}</button>\n </div>\n <div class="storysplat-explore-controls">\n ${p.includes("explore")?`<button class="storysplat-explore-btn" data-explore-mode="orbit">${T.orbit}</button>\n <button class="storysplat-explore-btn" data-explore-mode="fly">${T.fly}</button>`:""}\n ${p.includes("walk")?`<button class="storysplat-explore-btn" data-explore-mode="walk">${T.walk}</button>`:""}\n </div>\n ${s&&p.includes("tour")&&(p.includes("explore")||p.includes("walk"))?`\n <div class="storysplat-mode-container">\n <div class="storysplat-mode-toggle">\n ${p.includes("tour")?`<button class="storysplat-mode-btn ${"tour"===h?"selected":""}" data-mode="tour">${T.tour}</button>`:""}\n ${p.includes("explore")&&p.includes("walk")?`<button class="storysplat-mode-btn ${"explore"===h||"walk"===h?"selected":""}" data-mode="explore">${T.explore}</button>`:""}\n ${p.includes("explore")&&!p.includes("walk")?`<button class="storysplat-mode-btn ${"explore"===h?"selected":""}" data-mode="explore">${T.explore}</button>`:""}\n ${!p.includes("explore")&&p.includes("walk")?`<button class="storysplat-mode-btn ${"walk"===h?"selected":""}" data-mode="walk">${T.walk}</button>`:""}\n </div>\n </div>\n `:""}\n </div>\n `,t.appendChild(e),A.scrollControls=e,A.progressBar=e.querySelector(".storysplat-progress-bar"),A.progressText=e.querySelector(".storysplat-progress-text"),A.exploreControls=e.querySelector(".storysplat-explore-controls"),A.modeContainer=e.querySelector(".storysplat-mode-container"),A.prevButton=e.querySelector(".storysplat-btn-prev"),A.playButton=e.querySelector(".storysplat-btn-play"),A.nextButton=e.querySelector(".storysplat-btn-next"),"pro"===S){const e=document.createElement("div");e.className="storysplat-top-right-bar "+(a?"with-fullscreen":""),t.appendChild(e),A.modeContainer&&e.appendChild(A.modeContainer)}const n=M?.uiLayout,o=t=>n?.elements?.[t]||n?.mobile?.[t];o("progressText")&&A.progressText&&t.appendChild(A.progressText),o("exploreControls")&&A.exploreControls&&t.appendChild(A.exploreControls),o("prevButton")&&A.prevButton&&t.appendChild(A.prevButton),o("playButton")&&A.playButton&&t.appendChild(A.playButton),o("nextButton")&&A.nextButton&&t.appendChild(A.nextButton),"pro"!==S&&o("modeToggle")&&A.modeContainer&&t.appendChild(A.modeContainer),_&&A.progressText&&(A.progressText.style.display="none")}if(a){const e=document.createElement("button");e.className="storysplat-fullscreen-btn",e.setAttribute("aria-label",T.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),A.fullscreenButton=e}if(c){const e=document.createElement("button");e.className="storysplat-mute-btn",e.setAttribute("aria-label",T.mute);const n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.classList.add("storysplat-unmuted-icon"),n.setAttribute("viewBox","0 0 24 24");const o=document.createElementNS("http://www.w3.org/2000/svg","path");o.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"),n.appendChild(o),e.appendChild(n);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),A.muteButton=e}if(E){const e=document.createElement("button");e.className="storysplat-relight-btn active",e.setAttribute("aria-label","Toggle Relighting");const n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("viewBox","0 0 24 24");const o=document.createElementNS("http://www.w3.org/2000/svg","path");o.setAttribute("d","M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7z"),n.appendChild(o),e.appendChild(n),t.appendChild(e),A.relightingButton=e}if(w&&e.waypoints&&e.waypoints.length>0){const n=document.createElement("div");n.className="storysplat-waypoint-list-container "+(a?"with-fullscreen":"no-fullscreen");const o=e.waypoints.map((t,e)=>`<div class="storysplat-waypoint-item" data-waypoint-index="${e}">${t.name||`Waypoint ${e+1}`}</div>`).join("");n.innerHTML=`\n <button class="storysplat-waypoint-list-toggle" aria-label="${T.waypoints}">\n ${T.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 ${o}\n </div>\n `;const i=t.querySelector(".storysplat-top-right-bar");i?i.appendChild(n):t.appendChild(n),A.waypointListContainer=n;const s=n.querySelector(".storysplat-waypoint-list-toggle"),r=n.querySelector(".storysplat-waypoint-list-dropdown");s?.addEventListener("click",t=>{t.stopPropagation(),s.classList.toggle("open"),r?.classList.toggle("open")}),document.addEventListener("click",t=>{n.contains(t.target)||(s?.classList.remove("open"),r?.classList.remove("open"))})}if(e.portals&&e.portals.length>0&&!1!==C){const n=document.createElement("div");n.className="storysplat-scene-menu-container";let o=10;r&&(o+=35),c&&(o+=35),E&&(o+=35),n.style.left=`${o}px`;const i=T.scenes,s=document.createElement("button");s.className="storysplat-scene-menu-toggle",s.setAttribute("aria-label",i);const a=document.createElementNS("http://www.w3.org/2000/svg","svg");a.setAttribute("viewBox","0 0 24 24");const l=document.createElementNS("http://www.w3.org/2000/svg","path");l.setAttribute("d","M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"),a.appendChild(l),s.appendChild(a);const d=document.createElement("span");d.className="storysplat-scene-menu-label",d.textContent=i,s.appendChild(d);const p=document.createElement("div");p.className="storysplat-scene-menu-dropdown",function(t,e){const n={children:new Map,items:[]};for(const t of e){const e=t.menuPath?.trim();if(!e){n.items.push(t);continue}const o=e.split("/").map(t=>t.trim()).filter(Boolean);let i=n;for(const t of o)i.children.has(t)||i.children.set(t,{name:t,children:new Map,items:[]}),i=i.children.get(t);i.items.push(t)}function o(t){const e=document.createElement("div");if(e.className="storysplat-scene-menu-item",e.setAttribute("data-portal-id",t.id),e.setAttribute("data-target-scene-id",t.targetSceneId),t.targetSceneThumbnail){const n=document.createElement("img");n.className="storysplat-scene-menu-item-thumb",n.src=t.targetSceneThumbnail,n.alt="",n.loading="lazy",e.appendChild(n)}const n=document.createElement("span");return n.className="storysplat-scene-menu-item-label",n.textContent=t.title||t.targetSceneName||"Untitled",e.appendChild(n),e}function i(t,e){for(const[,n]of e.children){const e=document.createElement("div");e.className="storysplat-scene-menu-folder";const o=document.createElement("div");o.className="storysplat-scene-menu-folder-header";const s=document.createElementNS("http://www.w3.org/2000/svg","svg");s.setAttribute("viewBox","0 0 24 24");const a=document.createElementNS("http://www.w3.org/2000/svg","path");a.setAttribute("d","M7 10l5 5 5-5z"),s.appendChild(a),o.appendChild(s),o.appendChild(document.createTextNode(n.name)),e.appendChild(o);const r=document.createElement("div");r.className="storysplat-scene-menu-folder-children",i(r,n),e.appendChild(r),t.appendChild(e)}for(const n of e.items)t.appendChild(o(n))}i(t,n)}(p,e.portals),n.appendChild(s),n.appendChild(p),t.appendChild(n),A.sceneMenuContainer=n,s.addEventListener("click",t=>{t.stopPropagation(),s.classList.toggle("open"),p.classList.toggle("open")}),p.querySelectorAll(".storysplat-scene-menu-folder-header").forEach(t=>{t.addEventListener("click",e=>{e.stopPropagation(),t.classList.toggle("collapsed");const n=t.nextElementSibling;n&&n.classList.toggle("collapsed")})}),document.addEventListener("click",t=>{n.contains(t.target)||(s.classList.remove("open"),p.classList.remove("open"))})}const L=document.createElement("button");L.className="storysplat-xr-btn storysplat-vr-btn",L.setAttribute("aria-label",T.vr),L.textContent=T.vr,t.appendChild(L),A.vrButton=L;const k=document.createElement("button");if(k.className="storysplat-xr-btn storysplat-ar-btn",k.setAttribute("aria-label",T.ar),k.textContent=T.ar,t.appendChild(k),A.arButton=k,r){const e=document.createElement("button");e.className="storysplat-help-btn",e.setAttribute("title",T.helpTitle),e.textContent="?",t.appendChild(e),A.helpButton=e;const n=document.createElement("div");n.className="storysplat-help-panel";const o=document.createElement("h3");o.textContent=T.helpTitle,n.appendChild(o);const i=document.createElement("div");i.className="storysplat-help-tabs";[{key:"tour",label:T.tour},{key:"explore",label:T.explore}].forEach((t,e)=>{const n=document.createElement("button");n.className="storysplat-help-tab"+(0===e?" active":""),n.setAttribute("data-tab",t.key),n.textContent=t.label,i.appendChild(n)}),n.appendChild(i);const s=(t,e)=>{const n=document.createElement("div");n.className="storysplat-help-row";const o=document.createElement("span");o.className="storysplat-help-key",o.textContent=t;const i=document.createElement("span");return i.className="storysplat-help-desc",i.textContent=e.replace(/^[•\s]+/,""),n.appendChild(o),n.appendChild(i),n},a=(t,e,n,o)=>{const i=document.createElement("div");i.className="storysplat-help-tab-content"+(o?" active":""),i.setAttribute("data-tab",t);const a=document.createElement("p");return a.style.cssText="margin-bottom:6px;color:rgba(255,255,255,0.5)",a.textContent=e,i.appendChild(a),n.forEach(([t,e])=>i.appendChild(s(t,e))),i};n.appendChild(a("tour",T.helpTourDesc,[["Scroll",T.helpTourScroll],["Drag",T.helpTourDrag]],!0));const r=[["LMB",T.helpExploreLMB],["RMB",T.helpExploreRMB],["WASD",T.helpExploreWASD],["Shift",T.helpExploreShift],["Scroll",T.helpExploreScroll],["Dbl-click",T.helpExploreDblClick]];p.includes("walk")&&r.push(["",`— ${T.walk} —`],["Click",T.helpWalkClick],["WASD",T.helpWalkWASD],["Mouse",T.helpWalkMouse],["Shift",T.helpWalkShift],["Space",T.helpWalkSpace]),n.appendChild(a("explore",T.helpExploreDesc,r,!1)),t.appendChild(n),A.helpPanel=n;const l=n.querySelectorAll(".storysplat-help-tab"),c=n.querySelectorAll(".storysplat-help-tab-content");l.forEach(t=>{t.addEventListener("click",t=>{const e=t.currentTarget.getAttribute("data-tab");l.forEach(t=>t.classList.remove("active")),c.forEach(t=>t.classList.remove("active")),t.currentTarget.classList.add("active"),n.querySelector(`.storysplat-help-tab-content[data-tab="${e}"]`)?.classList.add("active")})})}const z=document.createElement("div");z.className="storysplat-hotspot-popup",z.id="hotspotContent",z.innerHTML=`\n <h2 class="storysplat-hotspot-popup-title"></h2>\n <div class="storysplat-hotspot-popup-content"></div>\n <button class="storysplat-hotspot-popup-close">${T.close}</button>\n `,t.appendChild(z),A.hotspotPopup=z;const R=z.querySelector(".storysplat-hotspot-popup-close");R?.addEventListener("click",()=>{z.classList.remove("visible","fullscreen")});const D=document.createElement("div");D.className="storysplat-portal-popup",D.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">${T.yes}</button>\n <button class="storysplat-portal-popup-btn storysplat-portal-popup-cancel">${T.cancel}</button>\n </div>\n `,t.appendChild(D),A.portalPopup=D;const I=document.createElement("div");I.className="storysplat-joystick-container",I.innerHTML='\n <div class="storysplat-joystick-base"></div>\n <div class="storysplat-joystick-thumb"></div>\n ',t.appendChild(I),A.joystick=I,A.joystickThumb=I.querySelector(".storysplat-joystick-thumb");const F=document.createElement("div");F.className="storysplat-look-zone",F.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(F),A.lookZone=F;const B=document.createElement("div");B.className="storysplat-wasd-hint";const U=document.createElement("div");U.className="storysplat-orbit-hint-row",U.style.marginBottom="6px";const $=document.createElement("div");$.className="storysplat-orbit-hint-icon";const V=document.createElementNS("http://www.w3.org/2000/svg","svg");V.setAttribute("viewBox","0 0 24 24");const O=document.createElementNS("http://www.w3.org/2000/svg","path");O.setAttribute("d","M13.64 21.97C13.14 22.21 12.54 22 12.31 21.5L10.13 16.76L7.62 18.78C7.45 18.92 7.24 19 7 19C6.44 19 6 18.56 6 18V3C6 2.44 6.44 2 7 2C7.24 2 7.47 2.09 7.64 2.23L7.65 2.22L19.14 11.86C19.57 12.22 19.31 12.92 18.76 12.96L14.78 13.21L16.97 17.95C17.21 18.45 17 19.05 16.5 19.28L13.64 21.97Z"),V.appendChild(O),$.appendChild(V),U.appendChild($);const W=document.createElement("div");W.className="storysplat-orbit-hint-label";const N=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);W.textContent=N?"Double-tap to refocus":"Double-click to refocus",U.appendChild(W),B.appendChild(U);const G=document.createElement("div");G.className="storysplat-wasd-hint-row",["Q","W","E"].forEach(t=>{const e=document.createElement("div");e.className="storysplat-wasd-key",e.textContent=t,e.setAttribute("data-keycode",`Key${t}`),G.appendChild(e)}),B.appendChild(G);const H=document.createElement("div");H.className="storysplat-wasd-hint-row",["A","S","D"].forEach(t=>{const e=document.createElement("div");e.className="storysplat-wasd-key",e.textContent=t,e.setAttribute("data-keycode",`Key${t}`),H.appendChild(e)}),B.appendChild(H);const X=document.createElement("div");X.className="storysplat-wasd-hint-label",X.textContent="Move",B.appendChild(X),t.appendChild(B),A.wasdHint=B;const q=document.createElement("div");q.className="storysplat-orbit-hint";const j=document.createElement("div");j.className="storysplat-orbit-hint-row";const Y=document.createElement("div");Y.className="storysplat-orbit-hint-icon";const Z=document.createElementNS("http://www.w3.org/2000/svg","svg");Z.setAttribute("viewBox","0 0 24 24");const Q=document.createElementNS("http://www.w3.org/2000/svg","path");Q.setAttribute("d","M13 6v5h5V7.75L22.25 12 18 16.25V13h-5v5h3.25L12 22.25 7.75 18H11v-5H6v3.25L1.75 12 6 7.75V11h5V6H7.75L12 1.75 16.25 6H13z"),Z.appendChild(Q),Y.appendChild(Z),j.appendChild(Y);const K=document.createElement("div");K.className="storysplat-orbit-hint-label",K.textContent="Drag to rotate",j.appendChild(K),q.appendChild(j);const J=document.createElement("div");J.className="storysplat-orbit-hint-row";const tt=document.createElement("div");tt.className="storysplat-orbit-hint-icon";const et=document.createElementNS("http://www.w3.org/2000/svg","svg");et.setAttribute("viewBox","0 0 24 24");const nt=document.createElementNS("http://www.w3.org/2000/svg","path");nt.setAttribute("d","M13.64 21.97C13.14 22.21 12.54 22 12.31 21.5L10.13 16.76L7.62 18.78C7.45 18.92 7.24 19 7 19C6.44 19 6 18.56 6 18V3C6 2.44 6.44 2 7 2C7.24 2 7.47 2.09 7.64 2.23L7.65 2.22L19.14 11.86C19.57 12.22 19.31 12.92 18.76 12.96L14.78 13.21L16.97 17.95C17.21 18.45 17 19.05 16.5 19.28L13.64 21.97Z"),et.appendChild(nt),tt.appendChild(et),J.appendChild(tt);const ot=document.createElement("div");ot.className="storysplat-orbit-hint-label";const it=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);ot.textContent=it?"Double-tap to refocus":"Double-click to refocus",J.appendChild(ot),q.appendChild(J),t.appendChild(q),A.orbitHint=q;const st=document.createElement("div");st.className="storysplat-doubletap-hint";const at=document.createElement("div");at.className="storysplat-doubletap-hint-icon";const rt=document.createElementNS("http://www.w3.org/2000/svg","svg");rt.setAttribute("viewBox","0 0 24 24");const lt=document.createElementNS("http://www.w3.org/2000/svg","path");lt.setAttribute("d","M13.64 21.97C13.14 22.21 12.54 22 12.31 21.5L10.13 16.76L7.62 18.78C7.45 18.92 7.24 19 7 19C6.44 19 6 18.56 6 18V3C6 2.44 6.44 2 7 2C7.24 2 7.47 2.09 7.64 2.23L7.65 2.22L19.14 11.86C19.57 12.22 19.31 12.92 18.76 12.96L14.78 13.21L16.97 17.95C17.21 18.45 17 19.05 16.5 19.28L13.64 21.97Z"),rt.appendChild(lt),at.appendChild(rt),st.appendChild(at);const ct=document.createElement("div");ct.className="storysplat-doubletap-hint-text";const dt=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);if(ct.textContent=dt?"Double-tap to focus on a point":"Double-click to focus on a point",st.appendChild(ct),t.appendChild(st),A.doubleTapHint=st,!y){const e=document.createElement("div");e.className="storysplat-watermark";const n=x||(b?`https://storysplat.com?ref=${b}`:"https://storysplat.com");e.innerHTML=v?`<a href="${n}" target="_blank">${v}</a>`:`Created with <a href="${n}" target="_blank">StorySplat</a>`,t.appendChild(e),A.watermark=e}if(n.debugMode){const e=document.createElement("div");e.className="storysplat-fps-counter",e.textContent="-- FPS",t.appendChild(e),A.fpsCounter=e}return A}function y(t,e,n="tour",o,i){const s=i?.container,a=i?.hideProgressText,l=t.prevButton||t.scrollControls?.querySelector(".storysplat-btn-prev"),c=t.nextButton||t.scrollControls?.querySelector(".storysplat-btn-next"),d=t.playButton||t.scrollControls?.querySelector(".storysplat-btn-play");if(l&&l.addEventListener("click",()=>e.prevWaypoint()),c&&c.addEventListener("click",()=>e.nextWaypoint()),d){const t=t=>{d.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>'};d.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.helpButton.classList.toggle("active")}),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 n=document;if(n.fullscreenElement||n.webkitFullscreenElement){n.exitFullscreen?n.exitFullscreen():n.webkitExitFullscreen&&n.webkitExitFullscreen();const e=t.fullscreenButton.querySelector(".storysplat-expand-icon"),o=t.fullscreenButton.querySelector(".storysplat-compress-icon");e&&(e.style.display="block"),o&&(o.style.display="none")}else{e?.requestFullscreen?e.requestFullscreen():e?.webkitRequestFullscreen&&e.webkitRequestFullscreen?.();const n=t.fullscreenButton.querySelector(".storysplat-expand-icon"),o=t.fullscreenButton.querySelector(".storysplat-compress-icon");n&&(n.style.display="none"),o&&(o.style.display="block")}});const n=()=>{const e=document,n=e.fullscreenElement||e.webkitFullscreenElement,o=t.fullscreenButton.querySelector(".storysplat-expand-icon"),i=t.fullscreenButton.querySelector(".storysplat-compress-icon");o&&(o.style.display=n?"none":"block"),i&&(i.style.display=n?"block":"none")};document.addEventListener("fullscreenchange",n),document.addEventListener("webkitfullscreenchange",n)}}const p=e=>{const n=t.scrollControls?.querySelector(".storysplat-progress-container"),o=t.scrollControls?.querySelector(".storysplat-scroll-buttons"),i=e?"":"none";t.progressText&&(t.progressText.style.display=e&&!a?"":"none"),n&&(n.style.display=i),o&&(o.style.display=i),t.prevButton&&t.prevButton.parentElement===s&&(t.prevButton.style.display=i),t.playButton&&t.playButton.parentElement===s&&(t.playButton.style.display=i),t.nextButton&&t.nextButton.parentElement===s&&(t.nextButton.style.display=i)},h=e=>{t.exploreControls&&(e?t.exploreControls.classList.add("visible"):t.exploreControls.classList.remove("visible"))},u=s||t.scrollControls?.parentElement;if(p("tour"===n),h("explore"===n||"walk"===n),e.setCameraMode){const t=u?.querySelectorAll(".storysplat-mode-btn");t?.forEach(n=>{n.addEventListener("click",()=>{const o=n.getAttribute("data-mode");o&&e.setCameraMode&&(e.setCameraMode(o),t.forEach(t=>t.classList.remove("selected")),n.classList.add("selected"),p("tour"===o),h("explore"===o||"walk"===o))})});const n=!!u?.querySelector('.storysplat-mode-btn[data-mode="walk"]');e.on("modeChange",({mode:e})=>{p("tour"===e),h("explore"===e||"walk"===e);const o="walk"===e?n?"walk":"explore":e;t?.forEach(t=>{const e=t.getAttribute("data-mode");t.classList.toggle("selected",e===o)})})}let m=0,g=-1;e.on("progressUpdate",({progress:e})=>{const n=100*Math.max(0,Math.min(1,e)),i=Math.round(n),s=performance.now();var a,l;s-m<33||(m=s,t.progressBar&&(t.progressBar.style.width=`${n}%`),t.progressText&&i!==g&&(g=i,t.progressText.innerHTML="",t.progressText.textContent=(a=o,l=i,(a?.percentageFormat||r.percentageFormat).replace("{n}",String(l)))))});let f=-1;e.on("waypointChange",({index:n,waypoint:o})=>{if(n===f)return;if(f=n,!t.waypointInfo)return;const i=t.waypointInfo.querySelector(".storysplat-waypoint-title"),s=t.waypointInfo.querySelector(".storysplat-waypoint-description");if(!i||!s)return;const a=o||e.getWaypoints?.()[n];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 v(e,n,o){const i=e.querySelector(".storysplat-hotspot-popup");if(!i)return;const s=i.querySelector(".storysplat-hotspot-popup-title"),a=i.querySelector(".storysplat-hotspot-popup-content"),r=i.querySelector(".storysplat-hotspot-popup-close");i.style.cssText="",i.classList.remove("fullscreen");const c=n.activationMode||"click",d=n.photoUrl||n.popupVideoUrl||"iframe"===n.contentType&&n.iframeUrl||"model"===n.contentType&&n.modelUrl;"click"===c&&d&&i.classList.add("fullscreen");const p=n.backgroundAlpha??.75;if(n.backgroundColor){const t=n.backgroundColor.replace("#","");if(6===t.length){const e=parseInt(t.substring(0,2),16),n=parseInt(t.substring(2,4),16),o=parseInt(t.substring(4,6),16);i.style.backgroundColor=`rgba(${e}, ${n}, ${o}, ${p})`}else i.style.backgroundColor=n.backgroundColor}else i.style.backgroundColor=`rgba(0, 0, 0, ${p})`;n.textColor&&(i.style.color=n.textColor,s&&(s.style.color=n.textColor)),n.fontFamily&&(i.style.fontFamily=n.fontFamily),n.fontSize&&(i.style.fontSize=`${n.fontSize}px`),r&&n.closeButtonColor&&(r.style.backgroundColor=n.closeButtonColor),s&&(s.textContent=n.title||l(o,"hotspotDefaultTitle"));let h="";if("iframe"===n.contentType&&n.iframeUrl&&(h+=`<iframe src="${n.iframeUrl}" title="${n.title||"Embedded content"}"></iframe>`),n.popupVideoUrl&&"video"===n.contentType&&(h+=`<div class="video-container"><video src="${n.popupVideoUrl}" controls playsinline webkit-playsinline preload="metadata"></video></div>`),!n.photoUrl||n.contentType&&"image"!==n.contentType&&"gif"!==n.contentType||(h+=`<img src="${n.photoUrl}" alt="${n.title||"Hotspot image"}" />`),"model"===n.contentType&&n.modelUrl&&(h+='<div class="model-container"><div class="model-loading">Loading 3D model...</div><canvas class="model-canvas"></canvas></div>'),n.information&&(h+=`<p>${n.information}</p>`),n.externalLinkUrl){const t=n.externalLinkButtonColor||"#007bff";h+=`\n <div onclick="window.open('${n.externalLinkUrl}', '_blank', 'noopener,noreferrer')"\n class="storysplat-hotspot-popup-link" style="background-color: ${t}">\n ${n.externalLinkText||l(o,"openExternalLink")}\n </div>\n `}if(a&&(a.innerHTML=h,"model"===n.contentType&&n.modelUrl)){const e=a.querySelector(".model-canvas");e&&function(e,n,o){const i=e.parentElement;if(!i)return;const s=i.getBoundingClientRect();e.width=s.width*window.devicePixelRatio,e.height=s.height*window.devicePixelRatio;const a=new t.Application(e,{graphicsDeviceOptions:{alpha:!0,antialias:!0,preserveDrawingBuffer:!1}});a.setCanvasResolution(t.RESOLUTION_AUTO),a.setCanvasFillMode(t.FILLMODE_NONE);const r=new t.Entity("popup-camera");r.addComponent("camera",{clearColor:new t.Color(0,0,0,0),fov:45,nearClip:.01,farClip:1e3}),a.root.addChild(r);const l=new t.Entity("popup-key-light");l.addComponent("light",{type:"directional",color:new t.Color(1,1,1),intensity:1.2,castShadows:!1}),l.setEulerAngles(45,30,0),a.root.addChild(l);const c=new t.Entity("popup-fill-light");c.addComponent("light",{type:"directional",color:new t.Color(.7,.75,.85),intensity:.6,castShadows:!1}),c.setEulerAngles(-30,-60,0),a.root.addChild(c),a.start();let d=0,p=-20,h=5,u=new t.Vec3(0,0,0),m=!1,g=0,f=0;function y(){const e=d*t.math.DEG_TO_RAD,n=p*t.math.DEG_TO_RAD,o=h*Math.cos(n)*Math.sin(e),i=h*Math.sin(n),s=h*Math.cos(n)*Math.cos(e);r.setPosition(u.x+o,u.y+i,u.z+s),r.lookAt(u)}e.addEventListener("mousedown",t=>{m=!0,g=t.clientX,f=t.clientY,t.preventDefault()}),e.addEventListener("mousemove",t=>{if(!m)return;const e=t.clientX-g,n=t.clientY-f;d-=.4*e,p=Math.max(-89,Math.min(89,p-.4*n)),g=t.clientX,f=t.clientY,y()}),e.addEventListener("mouseup",()=>{m=!1}),e.addEventListener("mouseleave",()=>{m=!1}),e.addEventListener("wheel",t=>{h=Math.max(.1,h*(1+.001*t.deltaY)),y(),t.preventDefault()},{passive:!1});let v=0,x=0;e.addEventListener("touchstart",t=>{if(1===t.touches.length)m=!0,g=t.touches[0].clientX,f=t.touches[0].clientY;else if(2===t.touches.length){m=!1;const e=t.touches[1].clientX-t.touches[0].clientX,n=t.touches[1].clientY-t.touches[0].clientY;v=Math.sqrt(e*e+n*n),x=h}t.preventDefault()},{passive:!1}),e.addEventListener("touchmove",t=>{if(1===t.touches.length&&m){const e=t.touches[0].clientX-g,n=t.touches[0].clientY-f;d-=.4*e,p=Math.max(-89,Math.min(89,p-.4*n)),g=t.touches[0].clientX,f=t.touches[0].clientY,y()}else if(2===t.touches.length){const e=t.touches[1].clientX-t.touches[0].clientX,n=t.touches[1].clientY-t.touches[0].clientY,o=Math.sqrt(e*e+n*n);v>0&&(h=Math.max(.1,x*(v/o)),y())}t.preventDefault()},{passive:!1}),e.addEventListener("touchend",()=>{m=!1,v=0});const b=new ResizeObserver(t=>{for(const n of t){const{width:t,height:o}=n.contentRect;t>0&&o>0&&(e.width=t*window.devicePixelRatio,e.height=o*window.devicePixelRatio,a.resizeCanvas(t,o))}});b.observe(i);const w=new t.Asset("popup-model","container",{url:n});w.on("load",()=>{const e=i.querySelector(".model-loading");e&&e.remove();const n=w.resource.instantiateRenderEntity();a.root.addChild(n);const o=[];if(n.findComponents("render").forEach(t=>{o.push(...t.meshInstances)}),o.length>0){const e=new t.BoundingBox;e.copy(o[0].aabb);for(let t=1;t<o.length;t++)e.add(o[t].aabb);u.copy(e.center);const n=e.halfExtents,i=Math.max(n.x,n.y,n.z);h=2.5*i}y()}),w.on("error",t=>{const e=i.querySelector(".model-loading");e&&(e.textContent="Failed to load model"),console.error("Model hotspot load error:",t)}),a.assets.add(w),a.assets.load(w),y(),o.__modelViewerCleanup=()=>{b.disconnect(),a.destroy()}}(e,n.modelUrl,i)}i.classList.add("visible");if(i.querySelector("video")){const t=()=>{const t=document,n=t.fullscreenElement||t.webkitFullscreenElement;n&&e.contains(n)?e.style.overflow="visible":e.style.overflow="hidden"};document.addEventListener("fullscreenchange",t),document.addEventListener("webkitfullscreenchange",t),i.__fullscreenCleanup=()=>{document.removeEventListener("fullscreenchange",t),document.removeEventListener("webkitfullscreenchange",t),e.style.overflow="hidden"}}}function x(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 b(t,e,n,o,i){if(t.joystickThumb)if(e){t.joystickThumb.classList.add("active");const e=Math.sqrt(n*n+o*o),s=Math.min(e,i),a=e>0?s/e:0,r=n*a,l=o*a;t.joystickThumb.style.transform=`translate(${r}px, ${l}px)`}else t.joystickThumb.classList.remove("active"),t.joystickThumb.style.transform="translate(0, 0)"}function w(t,e){t.lookZone&&(e?t.lookZone.classList.add("active"):t.lookZone.classList.remove("active"))}function S(t,e){t.wasdHint&&(e?t.wasdHint.classList.add("visible"):t.wasdHint.classList.remove("visible"))}function _(t,e){t.orbitHint&&(e?t.orbitHint.classList.add("visible"):t.orbitHint.classList.remove("visible"))}function M(t){if(!t.wasdHint)return null;const e=t.wasdHint.querySelectorAll(".storysplat-wasd-key[data-keycode]");if(0===e.length)return null;const n=new Map;e.forEach(t=>{const e=t.getAttribute("data-keycode");e&&n.set(e,t)});const o=new Set,i=t=>{const e=n.get(t.code);e&&e.classList.add("active")},s=t=>{const e=n.get(t.code);e&&!o.has(t.code)&&e.classList.remove("active")};document.addEventListener("keydown",i),document.addEventListener("keyup",s);const a=new Map;return e.forEach(t=>{const e=t.getAttribute("data-keycode");if(!e)return;const n=()=>{a.has(e)&&(a.delete(e),o.delete(e),t.classList.remove("active"),document.dispatchEvent(new KeyboardEvent("keyup",{code:e,key:e.replace("Key","").toLowerCase(),bubbles:!0,cancelable:!0})))};t.addEventListener("pointerdown",n=>{var i;n.preventDefault(),n.stopPropagation(),t.setPointerCapture(n.pointerId),i=n.pointerId,a.has(e)||(a.set(e,i),o.add(e),t.classList.add("active"),document.dispatchEvent(new KeyboardEvent("keydown",{code:e,key:e.replace("Key","").toLowerCase(),bubbles:!0,cancelable:!0})))}),t.addEventListener("pointerup",t=>{t.stopPropagation(),n()}),t.addEventListener("pointercancel",()=>{n()}),t.addEventListener("pointerleave",()=>{n()}),t.addEventListener("contextmenu",t=>t.preventDefault())}),()=>{document.removeEventListener("keydown",i),document.removeEventListener("keyup",s),a.forEach((t,e)=>{const o=n.get(e);o&&o.classList.remove("active"),document.dispatchEvent(new KeyboardEvent("keyup",{code:e,key:e.replace("Key","").toLowerCase(),bubbles:!0,cancelable:!0}))}),a.clear(),o.clear()}}function E(t,e){t.doubleTapHint&&(t.doubleTapHint.classList.add("fading"),setTimeout(()=>{t.doubleTapHint?.classList.remove("visible","fading")},400))}function C(t,e){if(!t.waypointListContainer)return;t.waypointListContainer.querySelectorAll(".storysplat-waypoint-item").forEach((t,n)=>{n===e?t.classList.add("active"):t.classList.remove("active")})}const T=new t.Vec3,A=new t.Vec3,P=new t.Pose,L=new t.InputFrame({move:[0,0,0],rotate:[0,0,0]}),k=(t,e,n)=>{const o=Math.sqrt(t[0]*t[0]+t[1]*t[1]);if(o<e)return void t.fill(0);const i=(o-e)/(n-e);t[0]*=i/o,t[1]*=i/o},z=(e,n,o,i,s=new t.Vec3)=>{const{fov:a,aspectRatio:r,horizontalFov:l,projection:c,orthoHeight:d}=e,p=e.system?.app,{width:h,height:u}=p?.graphicsDevice?.clientRect||{width:1920,height:1080};s.set(-n/h*2,o/u*2,0);const m=A.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(d*r,d,0);return s.mul(m),s};class R{constructor(e,n,o={}){this.enabled=!0,this._mode="orbit",this._enableOrbit=!0,this._enableFly=!0,this.enablePan=!0,this._pose=new t.Pose,this._preFocusMode="orbit",this._inputsDetached=!1,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.autoMoveForward=!1,this.autoMoveSpeedFactor=1,this.joystickEventName="joystick",this._collisionEntities=[],this._collisionRadius=.3,this._prevPosition=new t.Vec3,this._prevPositionValid=!1,this._collisionTestVec=new t.Vec3,this._voxelCollision=null,this._voxelSplatEntity=null,this._voxelSplatMatrix=new t.Mat4,this._voxelInvSplatMatrix=new t.Mat4,this._voxelFilePos=new t.Vec3,this._flyToActive=!1,this._flyToIsFocus=!1,this._flyToStartPos=new t.Vec3,this._flyToEndPos=new t.Vec3,this._flyToStartAngles=new t.Vec3,this._flyToEndAngles=new t.Vec3,this._flyToProgress=0,this._flyToDuration=.6,this._destroyHandler=null,this.camera=e,this.app=n;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=n.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._gamepadInput.attach(s),this._flyMobileInput.on("joystick:position:left",([t,e,n,o])=>{(t<0||"fly"===this._mode||!this.enabled)&&this.app.fire(`${this.joystickEventName}:left`,t,e,n,o)}),this._flyMobileInput.on("joystick:position:right",([t,e,n,o])=>{(t<0||"fly"===this._mode||!this.enabled)&&this.app.fire(`${this.joystickEventName}:right`,t,e,n,o)}),this._pose.look(this.camera.getPosition(),t.Vec3.ZERO),this._setMode("orbit"),this._controller=this._orbitController,void 0!==o.enableOrbit&&(this.enableOrbit=o.enableOrbit),void 0!==o.enableFly&&(this.enableFly=o.enableFly),void 0!==o.enablePan&&(this.enablePan=o.enablePan),o.focusPoint&&(this.focusPoint=o.focusPoint),void 0!==o.moveSpeed&&(this.moveSpeed=o.moveSpeed),void 0!==o.moveFastSpeed&&(this.moveFastSpeed=o.moveFastSpeed),void 0!==o.moveSlowSpeed&&(this.moveSlowSpeed=o.moveSlowSpeed),void 0!==o.rotateSpeed&&(this.rotateSpeed=o.rotateSpeed),void 0!==o.rotateTouchSens&&(this.rotateTouchSens=o.rotateTouchSens),void 0!==o.rotateJoystickSens&&(this.rotateJoystickSens=o.rotateJoystickSens),void 0!==o.zoomSpeed&&(this.zoomSpeed=o.zoomSpeed),void 0!==o.zoomPinchSens&&(this.zoomPinchSens=o.zoomPinchSens),void 0!==o.focusDamping&&(this.focusDamping=o.focusDamping),void 0!==o.rotateDamping&&(this.rotateDamping=o.rotateDamping),void 0!==o.moveDamping&&(this.moveDamping=o.moveDamping),void 0!==o.zoomDamping&&(this.zoomDamping=o.zoomDamping),o.pitchRange&&(this.pitchRange=o.pitchRange),o.yawRange&&(this.yawRange=o.yawRange),o.zoomRange&&(this.zoomRange=o.zoomRange),o.gamepadDeadZone&&(this.gamepadDeadZone=o.gamepadDeadZone),o.mobileInputLayout&&(this.mobileInputLayout=o.mobileInputLayout),void 0!==o.invertRotation&&(this.invertRotation=o.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(T)}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");this._desktopInput.read(),this._orbitMobileInput.read(),this._flyMobileInput.read(),this._gamepadInput.read();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,"fly"===e)this.syncFromCamera();else{const t=this.camera.getPosition();this._pose.look(t,this._lastFocusPoint),this._startZoomDist=t.distance(this._lastFocusPoint),this._pose.distance=this._startZoomDist}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}if(this._controller.attach(this._pose,!1),"orbit"===this._mode||"focus"===this._mode){const t=this.app.graphicsDevice.canvas;this._flyMobileInput.detach(),this._inputsDetached||this._orbitMobileInput.attach(t)}else if("fly"===this._mode){const t=this.app.graphicsDevice.canvas;this._orbitMobileInput.detach(),this._inputsDetached||this._flyMobileInput.attach(t)}this.app.fire("cameracontrols:modechange",this._mode)}}setMode(t){this._setMode(t)}_computeTweenAngles(e,n){const o=new t.Vec3(0,0,-1);this.camera.getRotation().transformVector(o,o);const i=Math.atan2(-o.y,Math.sqrt(o.x*o.x+o.z*o.z))*t.math.RAD_TO_DEG,s=Math.atan2(-o.x,-o.z)*t.math.RAD_TO_DEG;this._flyToStartAngles.set(-i,s,0);const a=(new t.Vec3).sub2(n,e);if(a.lengthSq()<1e-6)return void this._flyToEndAngles.set(-i,s,0);a.normalize();const r=Math.atan2(-a.y,Math.sqrt(a.x*a.x+a.z*a.z))*t.math.RAD_TO_DEG;let l=Math.atan2(-a.x,-a.z)*t.math.RAD_TO_DEG;const c=l-s;c>180?l-=360:c<-180&&(l+=360),this._flyToEndAngles.set(-r,l,0)}focus(t,e=!1){this._lastFocusPoint.copy(t);const n=this.camera.getPosition();this._startZoomDist=e?this._startZoomDist:n.distance(t),this._flyToStartPos.copy(n),this._flyToEndPos.lerp(n,t,.5),this._computeTweenAngles(this._flyToEndPos,t),this._flyToProgress=0,this._flyToActive=!0,this._flyToIsFocus=!0}flyTo(t,e=2){this._lastFocusPoint.copy(t);const n=this.camera.getPosition();this._flyToStartPos.copy(n);const o=n.distance(t),i=o>.001?Math.min(.5,50/o):0;this._flyToEndPos.lerp(n,t,i),this._computeTweenAngles(this._flyToEndPos,t),this._flyToProgress=0,this._flyToActive=!0,this._flyToIsFocus=!1}look(t,e=!1){"focus"!==this._mode&&(this._preFocusMode=this._mode),this._setMode("focus");const n=e?T.copy(this.camera.getPosition()).sub(t).normalize().mulScalar(this._startZoomDist).add(t):this.camera.getPosition();this._controller.attach(P.look(n,t))}reset(t,e){"focus"!==this._mode&&(this._preFocusMode=this._mode),this._setMode("focus"),this._controller.attach(P.look(e,t))}syncFromCamera(t){const e=this.camera.getPosition().clone();if(this._prevPositionValid=!1,t){this._lastFocusPoint.copy(t);const n=e.distance(t);this._startZoomDist=n,this._pose.distance=n,this._pose.look(e,t);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)),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 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));const o=this.camera.forward.clone();this._lastFocusPoint.copy(e).add(o.mulScalar(10));const i=10;this._startZoomDist=i,this._pose.distance=i}this._controller.attach(this._pose,!1)}syncFromPose(e,n,o){this._prevPositionValid=!1,this.camera.setPosition(e),this.camera.setRotation(n);const i=new t.Vec3(0,0,-1);n.transformVector(i,i);const s=Math.atan2(-i.y,Math.sqrt(i.x*i.x+i.z*i.z))*t.math.RAD_TO_DEG,a=Math.atan2(-i.x,-i.z)*t.math.RAD_TO_DEG;this._pose.position.copy(e),this._pose.angles.x=-s,this._pose.angles.y=a,this._pose.angles.z=0;let r=this._pose.angles.y;for(;r>180;)r-=360;for(;r<-180;)r+=360;if(this._pose.angles.y=r,this._lastYaw=r,this._pose.angles.x=Math.max(-89,Math.min(89,this._pose.angles.x)),o)this._lastFocusPoint.copy(o);else{const t=this.camera.forward.clone();this._lastFocusPoint.copy(e).add(t.mulScalar(10))}const l=e.distance(this._lastFocusPoint);this._startZoomDist=l,this._pose.distance=l,this._controller.attach(this._pose,!1)}setFocusDistance(t,e){this._lastFocusPoint.copy(t),this._startZoomDist=e,this._pose.distance=e}enable(){if(this._desktopInput.read(),this._orbitMobileInput.read(),this._flyMobileInput.read(),this._gamepadInput.read(),this.enabled=!0,this._inputsDetached){const t=this.app.graphicsDevice.canvas;this._desktopInput.attach(t),"fly"===this._mode?this._flyMobileInput.attach(t):this._orbitMobileInput.attach(t),this._gamepadInput.attach(t),this._inputsDetached=!1}}disable(){this.enabled=!1,this.autoMoveForward=!1,this._desktopInput.read(),this._orbitMobileInput.read(),this._flyMobileInput.read(),this._gamepadInput.read()}detachInputSources(){this._desktopInput.detach(),this._orbitMobileInput.detach(),this._flyMobileInput.detach(),this._gamepadInput.detach(),this._inputsDetached=!0}reattachMobileInput(){if(!this._inputsDetached)return;const t=this.app.graphicsDevice.canvas;this._flyMobileInput.attach(t)}update(e){if(!this.enabled)return;const{keyCode:n}=t.KeyboardMouseSource,{key:o,button:i,mouse:s,wheel:a}=this._desktopInput.read(),{touch:r,pinch:l,count:c}=this._orbitMobileInput.read(),{leftInput:d,rightInput:p}=this._flyMobileInput.read(),{leftStick:h,rightStick:u}=this._gamepadInput.read();k(h,this.gamepadDeadZone.x,this.gamepadDeadZone.y),k(u,this.gamepadDeadZone.x,this.gamepadDeadZone.y),this._state.axis.add(T.set(o[n.D]-o[n.A]+(o[n.RIGHT]-o[n.LEFT]),o[n.E]-o[n.Q],o[n.W]-o[n.S]+(o[n.UP]-o[n.DOWN])));for(let t=0;t<this._state.mouse.length;t++)this._state.mouse[t]+=i[t];this._state.shift+=o[n.SHIFT],this._state.ctrl+=o[n.CTRL],this._state.touches+=c[0];const m=+("orbit"===this._mode),g=+("fly"===this._mode),f=+(this._state.touches>1),y=+(this._state.shift||this._state.mouse[1]),v=+this._flyMobileInput.layout.endsWith("joystick"),x=(this._state.shift?this.moveFastSpeed:this._state.ctrl?this.moveSlowSpeed:this.moveSpeed)*e,b=60*this.zoomSpeed*e,w=b*this.zoomPinchSens,S=60*this.rotateSpeed*e,_=S*this.rotateTouchSens,M=this.rotateSpeed*this.rotateJoystickSens*60*e,{deltas:E}=L,C=T.set(0,0,0),P=this._state.axis.clone().normalize();C.add(P.mulScalar(g*x*this.keyboardSpeedMultiplier));const R=z(this.cameraComponent,s[0],s[1],this._pose.distance);C.add(R.mulScalar(m*y*+this.enablePan));const D=A.set(0,0,a[0]);C.add(D.mulScalar(m*b)),E.move.append([C.x,C.y,C.z]),C.set(0,0,0);const I=A.set(s[0],s[1],0);C.add(I.mulScalar((1-m*y)*S)),this.invertRotation&&(C.y=-C.y),E.rotate.append([C.x,C.y,C.z]),C.set(0,0,0);const F=A.set(d[0],0,-d[1]);C.add(F.mulScalar(g*x));const B=z(this.cameraComponent,r[0],r[1],this._pose.distance);C.add(B.mulScalar(m*f*+this.enablePan));const U=A.set(0,0,l[0]);C.add(U.mulScalar(m*f*w)),E.move.append([C.x,C.y,C.z]),C.set(0,0,0);const $=A.set(r[0],r[1],0);C.add($.mulScalar(m*(1-f)*_));const V=A.set(p[0],p[1],0);C.add(V.mulScalar(g*(v?M:_))),this.invertRotation&&(C.y=-C.y),E.rotate.append([C.x,C.y,C.z]),C.set(0,0,0);const O=A.set(h[0],0,-h[1]);if(C.add(O.mulScalar(g*x)),E.move.append([C.x,C.y,C.z]),this.autoMoveForward&&g&&E.rotate.length()>.001&&(this.autoMoveForward=!1),this.autoMoveForward&&g){const t=this.moveSpeed*this.autoMoveSpeedFactor*e;E.move.append([0,0,t])}C.set(0,0,0);const W=A.set(u[0],u[1],0);if(C.add(W.mulScalar(g*M)),this.invertRotation&&(C.y=-C.y),E.rotate.append([C.x,C.y,C.z]),this.app.xr?.active)return void L.read();if("focus"===this._mode){const t=E.move.length()+E.rotate.length()>0,e=this._focusController.complete?.()??!1;(t||e)&&this._setMode(this._preFocusMode)}this._pose.copy(this._controller.update(L,e));const N=this._collisionEntities.length>0||null!==this._voxelCollision;if("fly"!==this._mode&&"orbit"!==this._mode||!N)"fly"!==this._mode&&"orbit"!==this._mode&&(this._prevPositionValid=!1);else{if(this._prevPositionValid){const t=this._pose.position,e=this._prevPosition,n=this._collisionTestVec;let o=!1;n.set(t.x,e.y,e.z),this.checkCollision(n)&&(t.x=e.x,o=!0),n.set(t.x,t.y,e.z),this.checkCollision(n)&&(t.y=e.y,o=!0),n.set(t.x,t.y,t.z),this.checkCollision(n)&&(t.z=e.z,o=!0),o&&this._controller.attach(this._pose,!1)}this._prevPosition.copy(this._pose.position),this._prevPositionValid=!0}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}if(this._flyToActive){if(E.move.length()+E.rotate.length()>0)this._flyToActive=!1,this._flyToIsFocus?this.syncFromCamera(this._lastFocusPoint):this.syncFromCamera();else{this._flyToProgress=Math.min(1,this._flyToProgress+e/this._flyToDuration);const n=1-Math.pow(1-this._flyToProgress,3);if(this._pose.position.lerp(this._flyToStartPos,this._flyToEndPos,n),this._pose.angles.x=t.math.lerpAngle(this._flyToStartAngles.x,this._flyToEndAngles.x,n),this._pose.angles.y=t.math.lerpAngle(this._flyToStartAngles.y,this._flyToEndAngles.y,n),this._pose.angles.z=0,this._flyToProgress>=1){if(this._flyToActive=!1,this._prevPositionValid=!1,this._flyToIsFocus){const t=this._pose.position.distance(this._lastFocusPoint);this._pose.look(this._pose.position,this._lastFocusPoint),this._pose.distance=t,this._startZoomDist=t,this._lastYaw=this._pose.angles.y}this._controller.attach(this._pose,!1)}}}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}setVoxelCollision(t,e){this._voxelCollision=t,this._voxelSplatEntity=e,this._prevPositionValid=!1}checkCollision(t){const e=this._collisionRadius;for(const n of this._collisionEntities){const o=n.getPosition(),i=n.getLocalScale(),s=n._collisionMeshType;let a,r,l,c,d,p;const h=n._collisionBounds;h?(c=h.center.x,d=h.center.y,p=h.center.z,a=h.halfExtents.x,r=h.halfExtents.y,l=h.halfExtents.z):"floor"===s||"plane"===s?(c=o.x,d=o.y,p=o.z,a=i.x/2,r=.05,l=i.z/2):(c=o.x,d=o.y,p=o.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-d),g=Math.abs(t.z-p);if(u<a+e&&m<r+e&&g<l+e)return!0}if(this._voxelCollision){const n=this._voxelFilePos;if(this._voxelSplatEntity?(this._voxelSplatMatrix.copy(this._voxelSplatEntity.getWorldTransform()),this._voxelInvSplatMatrix.copy(this._voxelSplatMatrix).invert(),this._voxelInvSplatMatrix.transformPoint(t,n)):n.set(t.x,-t.y,-t.z),this._voxelCollision.checkSphereSolid(n.x,n.y,n.z,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 D(t,e,n){return t+4*e+16*n}function I(t,e,n){const o=2*e;return n<32?1==(t[o]>>>n&1):1==(t[o+1]>>>n-32&1)}function F(t,e){let n=t&(1<<e)-1,o=0;for(;n;)o++,n&=n-1;return o}class B{constructor(t,e,n){this.nodes=t,this.leafData=e,this.meta=n;const o=n.leafSize*n.voxelResolution,i=Math.pow(2,n.treeDepth)*o;this.gridMinX=n.gridBounds.min[0],this.gridMinY=n.gridBounds.min[1],this.gridMinZ=n.gridBounds.min[2],this.gridMaxX=this.gridMinX+i,this.gridMaxY=this.gridMinY+i,this.gridMaxZ=this.gridMinZ+i,this.voxelRes=n.voxelResolution,this.leafBlockSize=n.leafSize*n.voxelResolution}static async load(t){console.log(`[VoxelCollision.load] Fetching JSON: ${t.substring(0,100)}...`);const e=await fetch(t);if(!e.ok)throw new Error(`[VoxelCollision.load] Failed to fetch .voxel.json: ${e.status} ${e.statusText}`);const n=await e.json();console.log(`[VoxelCollision.load] JSON parsed — nodeCount: ${n.nodeCount}, leafDataCount: ${n.leafDataCount}, res: ${n.voxelResolution}, hasBinUrl: ${!!n.binUrl}`);const o=n.binUrl??t.replace(/\.voxel\.json(\?.*)?$/,(t,e)=>`.voxel.bin${e??""}`);console.log(`[VoxelCollision.load] Fetching BIN (${n.binUrl?"embedded URL":"derived URL"}): ${o.substring(0,100)}...`);const i=await fetch(o);if(!i.ok)throw new Error(`[VoxelCollision.load] Failed to fetch .voxel.bin: ${i.status} ${i.statusText} — URL: ${o.substring(0,120)}`);const s=await i.arrayBuffer();console.log(`[VoxelCollision.load] BIN fetched: ${s.byteLength} bytes`);const a=4*(n.nodeCount+n.leafDataCount);if(s.byteLength<a)throw new Error(`[VoxelCollision.load] Binary truncated: expected ${a} bytes, got ${s.byteLength}`);const r=4*n.nodeCount,l=new Uint32Array(s,0,n.nodeCount),c=new Uint32Array(s,r,n.leafDataCount),d=n.leafSize*n.voxelResolution,p=Math.pow(2,n.treeDepth)*d;return console.log(`[VoxelCollision.load] OK — ${n.nodeCount} nodes, ${n.numMixedLeaves} mixed leaves, grid: [${n.gridBounds.min}] → [${n.gridBounds.max}], octreeBounds: [${n.gridBounds.min[0]},${n.gridBounds.min[1]},${n.gridBounds.min[2]}] → [${n.gridBounds.min[0]+p},${n.gridBounds.min[1]+p},${n.gridBounds.min[2]+p}] (extent=${p})`),new B(l,c,n)}getMetadata(){return this.meta}checkPointSolid(t,e,n){return!(t<this.gridMinX||t>=this.gridMaxX||e<this.gridMinY||e>=this.gridMaxY||n<this.gridMinZ||n>=this.gridMaxZ)&&(0!==this.nodes.length&&this._descendPoint(0,t,e,n,this.gridMinX,this.gridMinY,this.gridMinZ,this.gridMaxX,this.gridMaxY,this.gridMaxZ))}_descendPoint(t,e,n,o,i,s,a,r,l,c){const d=this.nodes[t];if(-16777216==(0|d))return!0;const p=d>>>24&255,h=16777215&d;if(0===p){const t=Math.max(0,Math.min(3,Math.floor((e-i)/this.voxelRes))),r=Math.max(0,Math.min(3,Math.floor((n-s)/this.voxelRes))),l=Math.max(0,Math.min(3,Math.floor((o-a)/this.voxelRes)));return I(this.leafData,h,D(t,r,l))}const u=.5*(i+r),m=.5*(s+l),g=.5*(a+c),f=(e>=u?1:0)|(n>=m?2:0)|(o>=g?4:0);if(!(p>>f&1))return!1;const y=h+F(p,f);return this._descendPoint(y,e,n,o,1&f?u:i,2&f?m:s,4&f?g:a,1&f?r:u,2&f?l:m,4&f?c:g)}checkSphereSolid(t,e,n,o){return!(t+o<this.gridMinX||t-o>=this.gridMaxX||e+o<this.gridMinY||e-o>=this.gridMaxY||n+o<this.gridMinZ||n-o>=this.gridMaxZ)&&(0!==this.nodes.length&&this._descendSphere(0,t,e,n,o*o,this.gridMinX,this.gridMinY,this.gridMinZ,this.gridMaxX,this.gridMaxY,this.gridMaxZ))}_descendSphere(t,e,n,o,i,s,a,r,l,c,d){const p=this.nodes[t];if(-16777216==(0|p))return!0;const h=p>>>24&255,u=16777215&p;if(0===h){const t=s,l=a,c=r,d=this.voxelRes;for(let s=0;s<4;s++)for(let a=0;a<4;a++)for(let r=0;r<4;r++){if(!I(this.leafData,u,D(r,a,s)))continue;const p=t+r*d,h=l+a*d,m=c+s*d,g=p+d,f=h+d,y=m+d,v=Math.max(p-e,0,e-g),x=Math.max(h-n,0,n-f),b=Math.max(m-o,0,o-y);if(v*v+x*x+b*b<=i)return!0}return!1}const m=.5*(s+l),g=.5*(a+c),f=.5*(r+d);for(let t=0;t<8;t++){if(!(h>>t&1))continue;const p=1&t?m:s,y=2&t?g:a,v=4&t?f:r,x=1&t?l:m,b=2&t?c:g,w=4&t?d:f,S=Math.max(p-e,0,e-x),_=Math.max(y-n,0,n-b),M=Math.max(v-o,0,o-w);if(S*S+_*_+M*M<=i){const s=u+F(h,t);if(this._descendSphere(s,e,n,o,i,p,y,v,x,b,w))return!0}}return!1}findGround(t,e,n){const o=n??this.gridMaxY;return t<this.gridMinX||t>=this.gridMaxX||e<this.gridMinZ||e>=this.gridMaxZ||0===this.nodes.length?null:this._findGround(0,t,e,o,this.gridMinX,this.gridMinY,this.gridMinZ,this.gridMaxX,this.gridMaxY,this.gridMaxZ)}_findGround(t,e,n,o,i,s,a,r,l,c){if(s>=o)return null;const d=this.nodes[t];if(-16777216==(0|d))return Math.min(l,o);const p=d>>>24&255,h=16777215&d;if(0===p){const t=Math.max(0,Math.min(3,Math.floor((e-i)/this.voxelRes))),r=Math.max(0,Math.min(3,Math.floor((n-a)/this.voxelRes)));for(let e=3;e>=0;e--){const n=s+(e+1)*this.voxelRes;if(!(n>o)&&I(this.leafData,h,D(t,e,r)))return n}return null}const u=.5*(i+r),m=.5*(s+l),g=.5*(a+c),f=e>=u?1:0,y=n>=g?4:0,v=2|f|y,x=0|f|y;for(const t of[v,x]){if(!(p>>t&1))continue;const d=1&t?u:i,f=2&t?m:s,y=4&t?g:a,v=1&t?r:u,x=2&t?l:m,b=4&t?c:g,w=h+F(p,t),S=this._findGround(w,e,n,o,d,f,y,v,x,b);if(null!==S)return S}return null}findGroundWorld(t,e,n,o){const i=t,s=-e,a=-n;if(i<this.gridMinX||i>=this.gridMaxX||s<this.gridMinZ||s>=this.gridMaxZ)return null;if(0===this.nodes.length)return null;const r=a-o,l=this._findCeiling(0,i,s,r,this.gridMinX,this.gridMinY,this.gridMinZ,this.gridMaxX,this.gridMaxY,this.gridMaxZ);return null!==l?-l:null}_findCeiling(t,e,n,o,i,s,a,r,l,c){if(l<o)return null;const d=this.nodes[t];if(-16777216==(0|d))return Math.max(s,o);const p=d>>>24&255,h=16777215&d;if(0===p){const t=Math.max(0,Math.min(3,Math.floor((e-i)/this.voxelRes))),r=Math.max(0,Math.min(3,Math.floor((n-a)/this.voxelRes)));for(let e=0;e<4;e++){const n=s+(e+1)*this.voxelRes;if(!(n<o)&&I(this.leafData,h,D(t,e,r)))return n}return null}const u=.5*(i+r),m=.5*(s+l),g=.5*(a+c),f=e>=u?1:0,y=n>=g?4:0,v=0|f|y,x=2|f|y;for(const t of[v,x]){if(!(p>>t&1))continue;const d=1&t?u:i,f=2&t?m:s,y=4&t?g:a,v=1&t?r:u,x=2&t?l:m,b=4&t?c:g,w=h+F(p,t),S=this._findCeiling(w,e,n,o,d,f,y,v,x,b);if(null!==S)return S}return null}isVoxelSolid(t,e,n){if(t<this.gridMinX||e<this.gridMinY||n<this.gridMinZ||t>=this.gridMaxX||e>=this.gridMaxY||n>=this.gridMaxZ)return!1;if(0===this.nodes.length)return!1;const o=t-this.gridMinX,i=e-this.gridMinY,s=n-this.gridMinZ;let a=0,r=this.gridMaxX-this.gridMinX;for(;;){const l=this.nodes[a];if(-16777216==(0|l))return!0;const c=l>>>24&255,d=16777215&l;if(0===c){const a=this.meta.leafSize*this.voxelRes,r=t-o%a,l=e-i%a,c=n-s%a,p=Math.floor((t-r)/this.voxelRes),h=Math.floor((e-l)/this.voxelRes),u=Math.floor((n-c)/this.voxelRes);return I(this.leafData,d,D(Math.max(0,Math.min(3,p)),Math.max(0,Math.min(3,h)),Math.max(0,Math.min(3,u))))}r/=2;const p=(o&r?1:0)|(i&r?1:0)<<1|(s&r?1:0)<<2;if(!(c>>p&1))return!1;a=d+F(c,p)}}querySphere(t,e,n,o){const i=this.voxelRes;let s=t,a=e,r=n,l=0,c=0,d=0,p=!1,h=0,u=0,m=0,g=0;for(let t=0;t<4;t++){const t=Math.floor((s-o)/i)*i,e=Math.floor((a-o)/i)*i,n=Math.floor((r-o)/i)*i,f=Math.floor((s+o)/i)*i,y=Math.floor((a+o)/i)*i,v=Math.floor((r+o)/i)*i;let x=-1/0,b=0,w=0,S=0;for(let l=n;l<=v;l+=i)for(let n=e;n<=y;n+=i)for(let e=t;e<=f;e+=i){if(!this.isVoxelSolid(Math.round(e),Math.round(n),Math.round(l)))continue;const t=e,c=n,d=l,p=e+i,h=n+i,u=l+i,m=s-Math.max(t,Math.min(s,p)),g=a-Math.max(c,Math.min(a,h)),f=r-Math.max(d,Math.min(r,u)),y=m*m+g*g+f*f;if(y>=o*o)continue;const v=(p-t)/2+o-Math.abs(s-(t+p)/2),_=(h-c)/2+o-Math.abs(a-(c+h)/2),M=(u-d)/2+o-Math.abs(r-(d+u)/2);if(v<=0||_<=0||M<=0)continue;const E=o-Math.sqrt(y);E>x&&(x=E,v<=_&&v<=M?(b=Math.sign(s-(t+p)/2)*v,w=0,S=0):_<=M?(b=0,w=Math.sign(a-(c+h)/2)*_,S=0):(b=0,w=0,S=Math.sign(r-(d+u)/2)*M))}if(x<=0)break;if(g>0){const t=b*h+w*u+S*m;t<0&&(b-=t*h,w-=t*u,S-=t*m)}s+=b,a+=w,r+=S,l+=b,c+=w,d+=S,p=!0;const _=Math.sqrt(b*b+w*w+S*S);_>1e-6&&(h=b/_,u=w/_,m=S/_,g++)}return p?{x:l,y:c,z:d}:null}collectSolidAABBs(t=2e5){const e=[];return this._collectSolid(0,this.gridMinX,this.gridMinY,this.gridMinZ,this.gridMaxX,this.gridMaxY,this.gridMaxZ,e,t),new Float32Array(e)}collectLeafAABBs(t=5e5){if(this.nodes.length>0){const t=this.nodes[0],e=t>>>24&255,n=16777215&t,o=.5*(this.gridMinX+this.gridMaxX),i=.5*(this.gridMinY+this.gridMaxY),s=.5*(this.gridMinZ+this.gridMaxZ),a=["---","X--","-Y-","XY-","--Z","X-Z","-YZ","XYZ"],r=[];for(let t=0;t<8;t++)e&1<<t&&r.push(`${t}(${a[t]})`);console.log(`[VoxelCollision] Root: mask=0b${e.toString(2).padStart(8,"0")} base=${n} mid=(${o.toFixed(1)},${i.toFixed(1)},${s.toFixed(1)}) active=[${r.join(",")}]`)}const e=[];return this._collectLeaves(0,this.gridMinX,this.gridMinY,this.gridMinZ,this.gridMaxX,this.gridMaxY,this.gridMaxZ,e,t),new Float32Array(e)}_collectLeaves(t,e,n,o,i,s,a,r,l){if(r.length/4>=l)return;const c=this.nodes[t];if(-16777216==(0|c))return void r.push(.5*(e+i),.5*(n+s),.5*(o+a),.5*(i-e));const d=c>>>24&255;if(0===d)return void r.push(.5*(e+i),.5*(n+s),.5*(o+a),.5*(i-e));const p=.5*(e+i),h=.5*(n+s),u=.5*(o+a);for(let t=0;t<8;t++){if(!(d>>t&1))continue;if(r.length/4>=l)return;const m=1&t?p:e,g=2&t?h:n,f=4&t?u:o,y=1&t?i:p,v=2&t?s:h,x=4&t?a:u,b=(16777215&c)+F(d,t);this._collectLeaves(b,m,g,f,y,v,x,r,l)}}_collectSolid(t,e,n,o,i,s,a,r,l){if(r.length/4>=l)return;const c=this.nodes[t];if(-16777216==(0|c)){const t=.5*(e+i),l=.5*(n+s),c=.5*(o+a),d=.5*(i-e);return void r.push(t,l,c,d)}const d=c>>>24&255,p=16777215&c;if(0===d){const t=this.voxelRes,i=.5*t;for(let s=0;s<4;s++)for(let a=0;a<4;a++)for(let c=0;c<4;c++){if(r.length/4>=l)return;I(this.leafData,p,D(c,a,s))&&r.push(e+c*t+i,n+a*t+i,o+s*t+i,i)}return}const h=.5*(e+i),u=.5*(n+s),m=.5*(o+a);for(let t=0;t<8;t++){if(!(d>>t&1))continue;if(r.length/4>=l)return;const c=1&t?h:e,g=2&t?u:n,f=4&t?m:o,y=1&t?i:h,v=2&t?s:u,x=4&t?a:m,b=p+F(d,t);this._collectSolid(b,c,g,f,y,v,x,r,l)}}}function U(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,n,o={}){this.enabled=!1,this.velocity=new t.Vec3,this.isGrounded=!1,this.yaw=0,this.pitch=0,this.keys={},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.autoMoveForward=!1,this.autoMoveSpeedFactor=1,this.headBobEnabled=!0,this.horizontalVelocity=new t.Vec3,this.targetVelocity=new t.Vec3,this.bobTimer=0,this.bobOffsetY=0,this.bobOffsetX=0,this.currentTime=0,this.lastGroundedTime=-1/0,this.lastJumpPressTime=-1/0,this.coyoteTime=.15,this.jumpBufferTime=.15,this.landingDipAmount=0,this.previousVelocityY=0,this.wasGroundedLastFrame=!1,this.collisionEntities=[],this.floorEntity=null,this.voxelCollision=null,this._lastVoxelUrl=null,this._splatEntity=null,this._invSplatMatrix=new t.Mat4,this._splatMatrix=new t.Mat4,this.keydownHandler=null,this.keyupHandler=null,this.mousemoveHandler=null,this.mousedownHandler=null,this.mouseupHandler=null,this.mouseIsDown=!1,this.lastMouseX=0,this.lastMouseY=0,this.joystickMoveX=0,this.joystickMoveZ=0,this.joystickLookX=0,this.joystickLookY=0,this.tmpVec=new t.Vec3,this.tmpVec2=new t.Vec3,this.forward=new t.Vec3,this.right=new t.Vec3,this._debugMaterial=null,this._debugVisible=!1,this._originalMaterials=new Map,this._voxelDebugEntity=null,this._voxelDebugVisible=!1,this.camera=e,this.app=n,void 0!==o.moveSpeed&&(this.moveSpeed=o.moveSpeed),void 0!==o.sprintMultiplier&&(this.sprintMultiplier=o.sprintMultiplier),void 0!==o.lookSensitivity&&(this.lookSensitivity=o.lookSensitivity),void 0!==o.playerHeight&&(this.playerHeight=o.playerHeight),void 0!==o.gravity&&(this.gravity=o.gravity),void 0!==o.maxFallSpeed&&(this.maxFallSpeed=o.maxFallSpeed),void 0!==o.jumpVelocity&&(this.jumpVelocity=o.jumpVelocity),void 0!==o.collisionRadius&&(this.collisionRadius=o.collisionRadius),void 0!==o.stepHeight&&(this.stepHeight=o.stepHeight),void 0!==o.groundCheckDistance&&(this.groundCheckDistance=o.groundCheckDistance),void 0!==o.moveDamping&&(this.moveDamping=o.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 n=[];e.forEach((e,o)=>{if("custom"===e.meshType&&e.customMeshUrl){const t=this.loadCustomCollisionMesh(e,o);return void n.push(t)}if("custom"===e.meshType&&e.meshData&&e.meshData.length>=9){const n=new t.Entity(`collision-custom-meshdata-${o}`);return this.configureCollisionEntity(n,e),void this.computeBoundsFromMeshData(n,e.meshData)}let i=null;switch(e.meshType){case"cube":i=new t.Entity(`collision-cube-${o}`),i.addComponent("render",{type:"box"});break;case"sphere":i=new t.Entity(`collision-sphere-${o}`),i.addComponent("render",{type:"sphere"});break;case"floor":i=new t.Entity(`collision-floor-${o}`),i.addComponent("render",{type:"plane"}),this.floorEntity=i;break;default:i=new t.Entity(`collision-plane-${o}`),i.addComponent("render",{type:"plane"})}i&&this.configureCollisionEntity(i,e)}),n.length>0&&await Promise.all(n),console.log("[CharacterController] Created",this.collisionEntities.length,"collision entities")}async loadCustomCollisionMesh(e,n){const o=e.customMeshUrl;if(o){console.log("[CharacterController] Loading custom collision mesh:",o);try{const i=o.split("?")[0].split(".").pop()?.toLowerCase()||"glb",s="gltf"===i||"glb"===i?"container":"model",a=new t.Asset(`collision-custom-${n}`,s,{url:o});await new Promise((i,r)=>{a.ready(()=>{try{const o=new t.Entity(`collision-custom-${n}`);if("container"===s){const t=a.resource;if(t&&t.instantiateRenderEntity){const e=t.instantiateRenderEntity();for(;e.children.length>0;)o.addChild(e.children[0]);e.destroy()}}else o.addComponent("model",{asset:a});this.configureCollisionEntity(o,e),this.computeAndStoreBounds(o),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:",o,t),r(t)}),this.app.assets.add(a),this.app.assets.load(a)})}catch(t){console.error("[CharacterController] Failed to load custom collision mesh:",o,t)}}}computeAndStoreBounds(e){const n=new t.BoundingBox;let o=!1;const i=e=>{if(e.render&&e.render.meshInstances)for(const t of e.render.meshInstances)t.aabb&&(o?n.add(t.aabb):(n.copy(t.aabb),o=!0));for(const n of e.children)n instanceof t.Entity&&i(n)};i(e),o&&(e._collisionBounds=n)}computeBoundsFromMeshData(e,n){const o=new t.Vec3(1/0,1/0,1/0),i=new t.Vec3(-1/0,-1/0,-1/0);for(let t=0;t<n.length;t+=3){const e=n[t],s=n[t+1],a=-n[t+2];o.x=Math.min(o.x,e),o.y=Math.min(o.y,s),o.z=Math.min(o.z,a),i.x=Math.max(i.x,e),i.y=Math.max(i.y,s),i.z=Math.max(i.z,a)}const s=e.getWorldTransform(),a=[new t.Vec3(o.x,o.y,o.z),new t.Vec3(i.x,o.y,o.z),new t.Vec3(o.x,i.y,o.z),new t.Vec3(i.x,i.y,o.z),new t.Vec3(o.x,o.y,i.z),new t.Vec3(i.x,o.y,i.z),new t.Vec3(o.x,i.y,i.z),new t.Vec3(i.x,i.y,i.z)],r=new t.Vec3(1/0,1/0,1/0),l=new t.Vec3(-1/0,-1/0,-1/0);for(const t of a)s.transformPoint(t,t),r.x=Math.min(r.x,t.x),r.y=Math.min(r.y,t.y),r.z=Math.min(r.z,t.z),l.x=Math.max(l.x,t.x),l.y=Math.max(l.y,t.y),l.z=Math.max(l.z,t.z);const c=new t.BoundingBox;c.center.set((r.x+l.x)/2,(r.y+l.y)/2,(r.z+l.z)/2),c.halfExtents.set((l.x-r.x)/2,(l.y-r.y)/2,(l.z-r.z)/2),e._collisionBounds=c}configureCollisionEntity(e,n){const o=U(n.position,[0,0,0]);e.setPosition(o[0],o[1],-o[2]);const i=U(n.rotation,[0,0,0]),s=i[0],a=i[1]/2,r=s/2,l=i[2]/2,c=Math.cos(a),d=Math.sin(a),p=Math.cos(r),h=Math.sin(r),u=Math.cos(l),m=Math.sin(l),g=c*p*u+d*h*m,f=c*h*u+d*p*m,y=d*p*u-c*h*m,v=c*p*m-d*h*u,x=new t.Quat(-f,-y,v,g);if("plane"===n.meshType){const n=(new t.Quat).setFromEulerAngles(90,0,0);e.setRotation((new t.Quat).mul2(x,n))}else e.setRotation(x);const b=U(n.scaling,[1,1,1]),w=n.meshType;if("plane"===w){const t=.01;e.setLocalScale(3*b[0],b[2]*t,3*b[1])}else"cube"===w||"sphere"===w?e.setLocalScale(3*b[0],3*b[1],3*b[2]):"floor"===w?e.setLocalScale(100*b[0],1*b[1],100*b[2]):e.setLocalScale(b[0],b[1],b[2]);this.setEntityVisibility(e,!1),e._collisionMeshType=n.meshType,this.app.root.addChild(e),"custom"!==w&&this.computePrimitiveBounds(e),this.collisionEntities.push(e)}computePrimitiveBounds(e){const n=e.getWorldTransform().data,o=new t.BoundingBox;o.center.set(n[12],n[13],n[14]),o.halfExtents.set(.5*(Math.abs(n[0])+Math.abs(n[4])+Math.abs(n[8])),.5*(Math.abs(n[1])+Math.abs(n[5])+Math.abs(n[9])),.5*(Math.abs(n[2])+Math.abs(n[6])+Math.abs(n[10]))),e._collisionBounds=o}setEntityVisibility(e,n){e.render&&(e.render.enabled=n);for(const o of e.children)o instanceof t.Entity&&this.setEntityVisibility(o,n)}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.bobTimer=0,this.bobOffsetY=0,this.bobOffsetX=0,this.currentTime=0,this.lastGroundedTime=-1/0,this.lastJumpPressTime=-1/0,this.landingDipAmount=0,this.previousVelocityY=0,this.wasGroundedLastFrame=!1,this.setupInputHandlers(),console.log("[CharacterController] Enabled")}disable(){this.enabled&&(this.enabled=!1,this.autoMoveForward=!1,this.joystickMoveX=0,this.joystickMoveZ=0,this.joystickLookX=0,this.joystickLookY=0,this.removeInputHandlers(),this.mouseIsDown=!1,console.log("[CharacterController] Disabled"))}setupInputHandlers(){const t=this.app.graphicsDevice.canvas;this.keydownHandler=t=>{this.keys[t.code]=!0,"Space"===t.code&&(this.lastJumpPressTime=this.currentTime)},this.keyupHandler=t=>{this.keys[t.code]=!1,"Space"===t.code&&this.velocity.y>0&&(this.velocity.y*=.5)},this.mousemoveHandler=t=>{if(!this.mouseIsDown)return;const e=t.clientX-this.lastMouseX,n=t.clientY-this.lastMouseY;this.lastMouseX=t.clientX,this.lastMouseY=t.clientY,this.autoMoveForward&&Math.abs(e)+Math.abs(n)>2&&(this.autoMoveForward=!1),this.yaw-=e*this.lookSensitivity*100,this.pitch-=n*this.lookSensitivity*100,this.pitch=Math.max(-89,Math.min(89,this.pitch))},this.mousedownHandler=t=>{0===t.button&&(this.mouseIsDown=!0,this.lastMouseX=t.clientX,this.lastMouseY=t.clientY)},this.mouseupHandler=t=>{0===t.button&&(this.mouseIsDown=!1)},document.addEventListener("keydown",this.keydownHandler),document.addEventListener("keyup",this.keyupHandler),document.addEventListener("mousemove",this.mousemoveHandler),t.addEventListener("mousedown",this.mousedownHandler),document.addEventListener("mouseup",this.mouseupHandler)}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.mousedownHandler&&t.removeEventListener("mousedown",this.mousedownHandler),this.mouseupHandler&&document.removeEventListener("mouseup",this.mouseupHandler),this.keys={}}checkCollision(t,e){for(const n of this.collisionEntities){const o=n.getPosition(),i=n.getLocalScale();if("floor"===n._collisionMeshType)continue;let s,a,r,l,c,d;const p=n._collisionBounds;p?(l=p.center.x,c=p.center.y,d=p.center.z,s=p.halfExtents.x,a=p.halfExtents.y,r=p.halfExtents.z):(l=o.x,c=o.y,d=o.z,s=i.x/2,a=i.y/2,r=i.z/2);const h=Math.abs(t.x-l),u=Math.abs(t.y-c),m=Math.abs(t.z-d);if(h<s+e&&u<a+this.playerHeight/2&&m<r+e)return!0}return!1}resolveVoxelCollision(t,e){if(!this.voxelCollision)return!1;const n=this.tmpVec;this.worldToFile(t,n);const o=this.voxelCollision.querySphere(n.x,n.y,n.z,e);if(!o)return!1;const i=this.tmpVec2;i.set(o.x,o.y,o.z);const s=this.tmpVec;return this.fileVecToWorld(i,s),t.x+=s.x,t.y+=s.y,t.z+=s.z,!0}checkGround(t){if(this.floorEntity){const e=this.floorEntity.getPosition(),n=this.floorEntity.getLocalScale(),o=n.x/2,i=n.z/2;if(Math.abs(t.x-e.x)<o&&Math.abs(t.z-e.z)<i)return e.y}let e=null;for(const n of this.collisionEntities){const o=n.getPosition(),i=n.getLocalScale(),s=n._collisionMeshType;if("floor"===s||"plane"===s||"sphere"===s)continue;let a,r,l,c,d,p;const h=n._collisionBounds;h?(c=h.center.x,d=h.center.y,p=h.center.z,a=h.halfExtents.x,r=h.halfExtents.y,l=h.halfExtents.z):(c=o.x,d=o.y,p=o.z,a=i.x/2,r=i.y/2,l=i.z/2);const u=d+r;Math.abs(t.x-c)<a+this.collisionRadius&&Math.abs(t.z-p)<l+this.collisionRadius&&t.y>=u-this.stepHeight&&(null===e||u>e)&&(e=u)}if(this.voxelCollision){const n=this.tmpVec;this.worldToFile(t,n);const o=this.voxelCollision.findGround(n.x,n.z,n.y+this.stepHeight);if(null!==o){const t=this.tmpVec2;t.set(n.x,o,n.z);const i=this._splatEntity?this._splatMatrix.data:null;let s;s=i?i[1]*t.x+i[5]*t.y+i[9]*t.z+i[13]:-o,(null===e||s>e)&&(e=s)}}return e}update(e){if(!this.enabled)return;if(this.updateSplatTransform(),this.currentTime+=e,0!==this.joystickLookX||0!==this.joystickLookY){const t=120;this.yaw-=this.joystickLookX*t*e,this.pitch-=this.joystickLookY*t*e,this.pitch=Math.max(-89,Math.min(89,this.pitch))}let n=(this.keys.KeyD||this.keys.ArrowRight?1:0)-(this.keys.KeyA||this.keys.ArrowLeft?1:0),o=(this.keys.KeyW||this.keys.ArrowUp?1:0)-(this.keys.KeyS||this.keys.ArrowDown?1:0);n=Math.max(-1,Math.min(1,n+this.joystickMoveX)),o=Math.max(-1,Math.min(1,o+this.joystickMoveZ)),this.autoMoveForward&&0===n&&0===o&&(o=this.autoMoveSpeedFactor);const 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(o*a)),this.targetVelocity.add(this.tmpVec2.copy(this.right).mulScalar(n*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.bobOffsetY-this.landingDipAmount,l.x-=this.bobOffsetX*this.right.x,l.z-=this.bobOffsetX*this.right.z;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),this.resolveVoxelCollision(c,this.collisionRadius);const d=this.wasGroundedLastFrame,p=this.checkGround(c),h=c.y-this.playerHeight;null!==p&&h<=p+this.groundCheckDistance?(c.y=p+this.playerHeight,this.velocity.y=0,this.isGrounded=!0):(null===p||h>p+this.stepHeight)&&(this.isGrounded=!1),this.isGrounded&&(this.lastGroundedTime=this.currentTime);const u=this.currentTime-this.lastGroundedTime<=this.coyoteTime,m=this.currentTime-this.lastJumpPressTime<=this.jumpBufferTime;u&&m&&(this.velocity.y=this.jumpVelocity,this.isGrounded=!1,this.lastGroundedTime=-1/0,this.lastJumpPressTime=-1/0),this.isGrounded&&!d&&(this.landingDipAmount=Math.min(.02*Math.abs(this.previousVelocityY),$.MAX_LANDING_DIP)),this.landingDipAmount*=1-Math.min(1,e*$.LANDING_DIP_DECAY),this.landingDipAmount<.001&&(this.landingDipAmount=0);const g=Math.sqrt(this.horizontalVelocity.x*this.horizontalVelocity.x+this.horizontalVelocity.z*this.horizontalVelocity.z),f=this.isGrounded&&g>.5;if(this.headBobEnabled&&f){const t=i?$.SPRINT_BOB_FREQ:$.WALK_BOB_FREQ,n=i?$.SPRINT_BOB_AMP_Y:$.WALK_BOB_AMP_Y,o=i?$.SPRINT_BOB_AMP_X:$.WALK_BOB_AMP_X;this.bobTimer+=e*t,this.bobOffsetY=Math.sin(this.bobTimer)*n,this.bobOffsetX=Math.cos(.5*this.bobTimer)*o}else{const t=1-Math.min(1,8*e);this.bobOffsetY*=t,this.bobOffsetX*=t,Math.abs(this.bobOffsetY)<1e-4&&(this.bobOffsetY=0),Math.abs(this.bobOffsetX)<1e-4&&(this.bobOffsetX=0),0===this.bobOffsetY&&0===this.bobOffsetX&&(this.bobTimer=0)}const y=c.x+this.bobOffsetX*this.right.x,v=c.y+this.bobOffsetY-this.landingDipAmount,x=c.z+this.bobOffsetX*this.right.z;this.camera.setPosition(y,v,x),this.camera.setEulerAngles(this.pitch,this.yaw,0),this.previousVelocityY=this.velocity.y,this.wasGroundedLastFrame=this.isGrounded}destroy(){this.disable();for(const t of this.collisionEntities)t.destroy();this.collisionEntities=[],this.floorEntity=null,this.voxelCollision=null}async initVoxelCollision(t){if(t!==this._lastVoxelUrl||!this.voxelCollision)try{console.log("[CharacterController] Loading voxel collision:",t),this.voxelCollision=await B.load(t),this._lastVoxelUrl=t,console.log("[CharacterController] Voxel collision ready")}catch(t){console.warn("[CharacterController] Voxel collision load failed (falling back to primitives):",t),this.voxelCollision=null}}setSplatEntity(t){this._splatEntity=t}updateSplatTransform(){this._splatEntity&&(this._splatMatrix.copy(this._splatEntity.getWorldTransform()),this._invSplatMatrix.copy(this._splatMatrix).invert())}worldToFile(t,e){return this._splatEntity?this._invSplatMatrix.transformPoint(t,e):e.set(t.x,-t.y,-t.z),e}fileVecToWorld(t,e){if(this._splatEntity){const n=this._splatMatrix.data;e.set(n[0]*t.x+n[4]*t.y+n[8]*t.z,n[1]*t.x+n[5]*t.y+n[9]*t.z,n[2]*t.x+n[6]*t.y+n[10]*t.z)}else e.set(t.x,-t.y,-t.z);return e}get voxelCollisionInstance(){return this.voxelCollision}get collisionMeshEntities(){return this.collisionEntities}get grounded(){return this.isGrounded}getVelocity(){return this.velocity.clone()}setPosition(t,e,n){this.camera.setPosition(t,e,n)}setRotation(t,e){this.pitch=t,this.yaw=e,this.camera.setEulerAngles(t,e,0)}setJoystickMove(t,e){this.joystickMoveX=t,this.joystickMoveZ=e}setJoystickLook(t,e){this.joystickLookX=t,this.joystickLookY=e}setCollisionDebug(e){if(this._debugVisible=e,e&&!this._debugMaterial){const e=new t.StandardMaterial;e.diffuse=new t.Color(0,1,0),e.opacity=1,e.cull=t.CULLFACE_NONE,e.update(),this._debugMaterial=e}for(const t of this.collisionEntities)this._setDebugVisibility(t,e)}get collisionDebugVisible(){return this._debugVisible}_setDebugVisibility(e,n){if(e.render)if(e.render.enabled=!0,n&&this._debugMaterial)for(const t of e.render.meshInstances)this._originalMaterials.has(t.id)||this._originalMaterials.set(t.id,t.material),t.material=this._debugMaterial;else{for(const t of e.render.meshInstances){const e=this._originalMaterials.get(t.id);e&&(t.material=e)}e.render.enabled=!1}for(const o of e.children)o instanceof t.Entity&&this._setDebugVisibility(o,n)}get voxelDebugVisible(){return this._voxelDebugVisible}setVoxelDebug(e){if(this._voxelDebugVisible=e,!e)return void(this._voxelDebugEntity&&(this._voxelDebugEntity.enabled=!1));if(this._voxelDebugEntity)return void(this._voxelDebugEntity.enabled=!0);if(!this.voxelCollision)return void console.warn("[CharacterController] No voxel collision loaded, cannot show debug");const n=this.voxelCollision.collectSolidAABBs(1e5),o=n.length/4;if(0===o)return void console.warn("[CharacterController] Voxel octree has no solid voxels");console.log(`[CharacterController] Building voxel debug mesh: ${o} solid voxels`);const i=new Float32Array(24*o*3),s=[[0,0,0,1,0,0],[1,0,0,1,0,1],[1,0,1,0,0,1],[0,0,1,0,0,0],[0,1,0,1,1,0],[1,1,0,1,1,1],[1,1,1,0,1,1],[0,1,1,0,1,0],[0,0,0,0,1,0],[1,0,0,1,1,0],[1,0,1,1,1,1],[0,0,1,0,1,1]];for(let t=0;t<o;t++){const e=n[4*t],o=n[4*t+1],a=n[4*t+2],r=n[4*t+3],l=24*t*3;for(let t=0;t<12;t++){const n=s[t],c=l+6*t;i[c]=e+(2*n[0]-1)*r,i[c+1]=o+(2*n[1]-1)*r,i[c+2]=a+(2*n[2]-1)*r,i[c+3]=e+(2*n[3]-1)*r,i[c+4]=o+(2*n[4]-1)*r,i[c+5]=a+(2*n[5]-1)*r}}const a=new t.Mesh(this.app.graphicsDevice);a.setPositions(i),a.update(t.PRIMITIVE_LINES);const r=new t.StandardMaterial;r.diffuse=new t.Color(0,.8,.2),r.emissive=new t.Color(0,.8,.2),r.useLighting=!1,r.update();const l=new t.MeshInstance(a,r),c=new t.Entity("voxel-debug");c.addComponent("render",{type:"asset",meshInstances:[l],castShadows:!1,receiveShadows:!1}),this._splatEntity?this._splatEntity.addChild(c):this.app.root.addChild(c),this._voxelDebugEntity=c}clearVoxelDebug(){this._voxelDebugEntity&&(this._voxelDebugEntity.destroy(),this._voxelDebugEntity=null),this._voxelDebugVisible=!1}}$.WALK_BOB_FREQ=10,$.WALK_BOB_AMP_Y=.025,$.WALK_BOB_AMP_X=.012,$.SPRINT_BOB_FREQ=14,$.SPRINT_BOB_AMP_Y=.04,$.SPRINT_BOB_AMP_X=.02,$.LANDING_DIP_DECAY=8,$.MAX_LANDING_DIP=.15;const V="\n#define MAX_RELIGHT_LIGHTS 16\n\nuniform float uRelightFade;\nuniform vec3 uRelightAmbient;\nuniform vec3 uRelightLightPos[MAX_RELIGHT_LIGHTS];\nuniform vec3 uRelightLightColor[MAX_RELIGHT_LIGHTS];\nuniform float uRelightLightRange[MAX_RELIGHT_LIGHTS];\nuniform vec3 uRelightLightDir[MAX_RELIGHT_LIGHTS];\nuniform vec2 uRelightLightCosAngles[MAX_RELIGHT_LIGHTS];\n\nvec3 rl_gammaToLinear(vec3 c) { return pow(c, vec3(2.2)); }\nvec3 rl_linearToGamma(vec3 c) { return pow(max(c, vec3(0.0)), vec3(1.0/2.2)); }\n\nvoid applyRelighting(vec3 center, inout vec4 color) {\n if (uRelightFade <= 0.0) return;\n\n // center is effectively world-space in both paths:\n // - Unified (CopyToWorkBuffer): explicitly world-space\n // - Non-unified: model-space, but splat entities have identity transform\n vec3 worldPos = center;\n\n vec3 lightSum = vec3(0.0);\n for (int i = 0; i < MAX_RELIGHT_LIGHTS; ++i) {\n vec3 lc = uRelightLightColor[i];\n if (lc.r + lc.g + lc.b <= 0.0) continue;\n float range = uRelightLightRange[i];\n if (range <= 0.0) {\n // Directional / hemispheric: uniform contribution\n lightSum += lc;\n } else {\n // Point / spot: inverse-square falloff\n float dist = length(uRelightLightPos[i] - worldPos);\n dist = max(dist, 0.5); // Prevent singularity near light\n float atten = 1.0 / (dist * dist);\n // Soft range cutoff so light doesn't bleed infinitely\n float rangeFade = clamp(1.0 - dist / range, 0.0, 1.0);\n atten *= rangeFade;\n // Spotlight cone attenuation (point lights have dir=vec3(0), skipped)\n vec3 lightDir = uRelightLightDir[i];\n if (dot(lightDir, lightDir) > 0.0) {\n vec3 toSplat = normalize(worldPos - uRelightLightPos[i]);\n float cosAngle = dot(toSplat, lightDir);\n float cosOuter = uRelightLightCosAngles[i].y;\n float cosInner = uRelightLightCosAngles[i].x;\n float spotFade = clamp((cosAngle - cosOuter) / max(cosInner - cosOuter, 0.001), 0.0, 1.0);\n atten *= spotFade;\n }\n lightSum += lc * atten;\n }\n }\n\n vec3 linear = rl_gammaToLinear(color.rgb);\n vec3 relit = linear * (uRelightAmbient + lightSum);\n color.rgb = rl_linearToGamma(mix(linear, relit, uRelightFade));\n}\n",O="\nconst MAX_RELIGHT_LIGHTS: i32 = 16;\n\nuniform uRelightFade: f32;\nuniform uRelightAmbient: vec3f;\nuniform uRelightLightPos: array<vec3f, 16>;\nuniform uRelightLightColor: array<vec3f, 16>;\nuniform uRelightLightRange: array<f32, 16>;\nuniform uRelightLightDir: array<vec3f, 16>;\nuniform uRelightLightCosAngles: array<vec2f, 16>;\n\nfn rl_gammaToLinear(c: vec3f) -> vec3f { return pow(c, vec3f(2.2)); }\nfn rl_linearToGamma(c: vec3f) -> vec3f { return pow(max(c, vec3f(0.0)), vec3f(1.0/2.2)); }\n\nfn applyRelighting(center: vec3f, color: ptr<function, vec4f>) {\n if (uniform.uRelightFade <= 0.0) { return; }\n\n // center is effectively world-space in both paths:\n // - Unified (CopyToWorkBuffer): explicitly world-space\n // - Non-unified: model-space, but splat entities have identity transform\n let worldPos = center;\n\n var lightSum = vec3f(0.0);\n for (var i: i32 = 0; i < MAX_RELIGHT_LIGHTS; i++) {\n let lc = uniform.uRelightLightColor[i];\n if (lc.r + lc.g + lc.b <= 0.0) { continue; }\n let range = uniform.uRelightLightRange[i];\n if (range <= 0.0) {\n // Directional / hemispheric: uniform contribution\n lightSum += lc;\n } else {\n // Point / spot: inverse-square falloff\n let dist = length(uniform.uRelightLightPos[i] - worldPos);\n let safeDist = max(dist, 0.5); // Prevent singularity near light\n var atten = 1.0 / (safeDist * safeDist);\n // Soft range cutoff so light doesn't bleed infinitely\n let rangeFade = clamp(1.0 - dist / range, 0.0, 1.0);\n atten *= rangeFade;\n // Spotlight cone attenuation (point lights have dir=vec3(0), skipped)\n let lightDir = uniform.uRelightLightDir[i];\n if (dot(lightDir, lightDir) > 0.0) {\n let toSplat = normalize(worldPos - uniform.uRelightLightPos[i]);\n let cosAngle = dot(toSplat, lightDir);\n let cosOuter = uniform.uRelightLightCosAngles[i].y;\n let cosInner = uniform.uRelightLightCosAngles[i].x;\n let spotFade = clamp((cosAngle - cosOuter) / max(cosInner - cosOuter, 0.001), 0.0, 1.0);\n atten *= spotFade;\n }\n lightSum += lc * atten;\n }\n }\n\n let linear = rl_gammaToLinear((*color).rgb);\n let relit = linear * (uniform.uRelightAmbient + lightSum);\n (*color) = vec4f(rl_linearToGamma(mix(linear, relit, uniform.uRelightFade)), (*color).a);\n}\n",W=V+"\nvoid modifySplatCenter(inout vec3 center) {}\n\nvoid modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) {}\n\nvoid modifySplatColor(vec3 center, inout vec4 color) {\n applyRelighting(center, color);\n}\n",N=O+"\nfn modifySplatCenter(center: ptr<function, vec3f>) {}\n\nfn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr<function, vec4f>, scale: ptr<function, vec3f>) {}\n\nfn modifySplatColor(center: vec3f, color: ptr<function, vec4f>) {\n applyRelighting(center, color);\n}\n";let G=null;const H=16;function X(){if(G)return G;const e=t.createScript("gsplatRelighting");return Object.assign(e.prototype,{material:null,_effectInitialized:!1,_shadersNeedApplication:!1,_shaderManagedExternally:!1,_ambientArray:[1,1,1],_posArray:null,_colorArray:null,_rangeArray:null,_dirArray:null,_cosAnglesArray:null,relightFade:1,ambientR:1,ambientG:1,ambientB:1,initialize(){this.material=null,this._effectInitialized=!1,this._shadersNeedApplication=!1,this._ambientArray=[1,1,1],this._posArray=new Float32Array(48),this._colorArray=new Float32Array(48),this._rangeArray=new Float32Array(H),this._dirArray=new Float32Array(48),this._cosAnglesArray=new Float32Array(32),this.on("enable",()=>{this._shaderManagedExternally?this._discoverMaterial():this._effectInitialized?this._applyShaders():this._shadersNeedApplication=!0}),this.on("disable",()=>{this._shaderManagedExternally||this._removeShaders()}),this.entity.gsplat&&(this._effectInitialized=!0,this.enabled&&(this._shaderManagedExternally?this._discoverMaterial():this._applyShaders()))},update(t){this._effectInitialized?(!this._shaderManagedExternally&&this._shadersNeedApplication&&(this._applyShaders(),this._shadersNeedApplication=!1),(this.material||(this._shaderManagedExternally?this._discoverMaterial():this._applyShaders(),this.material))&&(this._ambientArray[0]=this.ambientR,this._ambientArray[1]=this.ambientG,this._ambientArray[2]=this.ambientB,this._setUniform("uRelightAmbient",this._ambientArray),this._setUniform("uRelightFade",this.relightFade),this.material.update())):this.entity.gsplat&&(this._effectInitialized=!0,this.enabled&&!this._shaderManagedExternally&&this._shadersNeedApplication?(this._applyShaders(),this._shadersNeedApplication=!1):this.enabled&&this._shaderManagedExternally&&!this.material&&this._discoverMaterial())},setRelightFade(t){this.relightFade=Math.max(0,Math.min(1,t))},setAmbientColor(t){const e=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(t);e&&(this.ambientR=parseInt(e[1],16)/255,this.ambientG=parseInt(e[2],16)/255,this.ambientB=parseInt(e[3],16)/255)},updateLights(t){const e=Math.min(t.length,H);for(let n=0;n<e;n++){const e=t[n];this._posArray[3*n]=e.position.x,this._posArray[3*n+1]=e.position.y,this._posArray[3*n+2]=e.position.z,this._colorArray[3*n]=e.color.r*e.intensity,this._colorArray[3*n+1]=e.color.g*e.intensity,this._colorArray[3*n+2]=e.color.b*e.intensity,this._rangeArray[n]=e.range,e.direction?(this._dirArray[3*n]=e.direction.x,this._dirArray[3*n+1]=e.direction.y,this._dirArray[3*n+2]=e.direction.z):(this._dirArray[3*n]=0,this._dirArray[3*n+1]=0,this._dirArray[3*n+2]=0),this._cosAnglesArray[2*n]=e.cosInnerAngle??1,this._cosAnglesArray[2*n+1]=e.cosOuterAngle??1}for(let t=e;t<H;t++)this._colorArray[3*t]=0,this._colorArray[3*t+1]=0,this._colorArray[3*t+2]=0,this._dirArray[3*t]=0,this._dirArray[3*t+1]=0,this._dirArray[3*t+2]=0;this._setUniform("uRelightLightPos[0]",this._posArray),this._setUniform("uRelightLightColor[0]",this._colorArray),this._setUniform("uRelightLightRange[0]",this._rangeArray),this._setUniform("uRelightLightDir[0]",this._dirArray),this._setUniform("uRelightLightCosAngles[0]",this._cosAnglesArray)},getShaderGLSL:()=>W,getShaderWGSL:()=>N,_discoverMaterial(){const t=this.entity.gsplat;if(t)if(t.unified)this.material=this.app.scene.gsplat?.material??null;else{const e=t.material;e?this.material=e:t.once("load",()=>{this.material=t.material??null})}},_applyShaders(){const t=this.entity.gsplat;if(t)if(t.unified){if(this.material=this.app.scene.gsplat?.material??null,!this.material)return void console.warn("[Relighting] Unified gsplat template material not available");this._applyShaderToMaterial(this.material)}else{const e=t.material;e?(this.material=e,this._applyShaderToMaterial(e)):t.once("load",()=>{this.material=t.material??null,this.material&&this._applyShaderToMaterial(this.material)})}},_applyShaderToMaterial(t){const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl",o="wgsl"===n?this.getShaderWGSL():this.getShaderGLSL();t.getShaderChunks(n).set("gsplatModifyVS",o),t.update()},_removeShaders(){if(!this.material)return;const t=this.app.graphicsDevice,e=t?.isWebGPU?"wgsl":"glsl";this.material.getShaderChunks(e).delete("gsplatModifyVS"),this.material.update(),this.material=null},_setUniform(t,e){this.material?.setParameter(t,e)},destroy(){this._shaderManagedExternally||this._removeShaders()}}),G=e,e}const q={get class(){return X()}};let j=null;function Y(){if(j)return j;const e=t.createScript("gsplatRevealRadial");return Object.assign(e.prototype,{effectTime:0,material:null,_effectInitialized:!1,_shadersNeedApplication:!1,_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:500,_autoEndRadius:0,bandWidth:1,initialize(){this.effectTime=0,this.material=null,this._effectInitialized=!1,this._shadersNeedApplication=!1,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",()=>{this.effectTime=0,this._effectInitialized?this._applyShaders():this._shadersNeedApplication=!0}),this.on("disable",()=>{this._removeShaders()}),this.entity.gsplat&&(this._effectInitialized=!0,this._detectSceneRadius(),this.enabled&&this._applyShaders())},update(t){if(this._effectInitialized){if(this._shadersNeedApplication&&(this._applyShaders(),this.material&&(this._shadersNeedApplication=!1)),this.material){if(this.effectTime+=t,this._isEffectComplete())return console.log("[RevealEffect] Effect complete, disabling"),void(this.enabled=!1);this._updateUniforms(),this.material.update()}}else this.entity.gsplat&&(this._effectInitialized=!0,this._detectSceneRadius(),this.enabled&&this._shadersNeedApplication&&(this._applyShaders(),this._shadersNeedApplication=!1))},_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);const t=this._autoEndRadius>0?this._autoEndRadius:this.endRadius;this._setUniform("uEndRadius",t),this._setUniform("uBandWidth",this.bandWidth)},_detectSceneRadius(){try{const t=this.entity.gsplat,e=t&&t.instance,n=e?.meshInstance??e?.meshInstances?.[0],o=n?.aabb;if(o){const t=o.halfExtents,e=Math.sqrt(t.x*t.x+t.y*t.y+t.z*t.z);this._autoEndRadius=1.2*e}}catch(t){}},_getCompletionTime(){const t=this._autoEndRadius>0?this._autoEndRadius:this.endRadius,e=this.delay;if(0===this.acceleration)return e+t/this.speed;const n=this.speed*this.speed+2*this.acceleration*t;if(n<0)return 1/0;return e+(-this.speed+Math.sqrt(n))/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;\nuniform float uBandWidth;\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 modifySplatCenter(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 * uBandWidth;\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 * uBandWidth && g_liftTime > 0.0) {\n float normalizedDist = distToLiftWave / uBandWidth;\n float liftAmount = (1.0 - normalizedDist) * sin(normalizedDist * 3.14159);\n center.y += liftAmount * uOscillationIntensity * 0.9;\n }\n}\n\nvoid modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) {\n\n // Store original scale for shape preservation\n vec3 origScale = scale;\n float origSize = gsplatGetSizeFromScale(scale);\n\n // Determine scale factor and phase\n float scaleFactor;\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 scaleFactor = (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 scale = vec3(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 scaleFactor = (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 scaleFactor = 0.1;\n }\n\n // Apply scale\n if (scaleFactor >= 1.0) {\n // Fully revealed: original shape and size (no-op)\n return;\n } else if (isLiftWave) {\n // Lift wave: lerp from spherical dots to original shape\n float t = (scaleFactor - 0.1) * 1.111111; // normalize [0.1, 1.0] to [0, 1]\n float dotSize = scaleFactor * 0.05;\n float finalSize = mix(dotSize, origSize, t);\n\n // Lerp between spherical (uniform) and scaled original\n vec3 sphericalScale = vec3(finalSize);\n vec3 scaledOrig = origScale * scaleFactor;\n scale = mix(sphericalScale, scaledOrig, t);\n } else {\n // Dot phase: spherical with absolute size, but don't make small splats larger\n float targetSize = min(scaleFactor * 0.05, origSize);\n gsplatMakeSpherical(scale, targetSize);\n }\n}\n\nvoid revealColorEffect(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 * uBandWidth && g_dist <= g_liftWavePos + 0.5 * uBandWidth) {\n float distToLift = abs(g_dist - g_liftWavePos);\n float liftIntensity = smoothstep(1.5 * uBandWidth, 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 * uBandWidth)) {\n float distToDot = abs(g_dist - g_dotWavePos);\n float dotIntensity = smoothstep(1.0 * uBandWidth, 0.0, distToDot);\n color.rgb += uDotTint * dotIntensity;\n }\n}\n\nvoid modifySplatColor(vec3 center, inout vec4 color) {\n // EFFECT_HOOK\n revealColorEffect(center, color);\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;\nuniform uBandWidth: 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 modifySplatCenter(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 * uniform.uBandWidth;\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 * uniform.uBandWidth && g_liftTime > 0.0) {\n let normalizedDist = distToLiftWave / uniform.uBandWidth;\n let liftAmount = (1.0 - normalizedDist) * sin(normalizedDist * 3.14159);\n (*center).y += liftAmount * uniform.uOscillationIntensity * 0.9;\n }\n}\n\nfn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr<function, vec4f>, scale: ptr<function, vec3f>) {\n // Store original scale for shape preservation\n let origScale = *scale;\n let origSize = gsplatGetSizeFromScale(*scale);\n\n // Determine scale factor and phase\n var scaleFactor: 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 scaleFactor = 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 *scale = vec3f(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 scaleFactor = 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 scaleFactor = 0.1;\n }\n\n // Apply scale\n if (scaleFactor >= 1.0) {\n // Fully revealed: original shape and size (no-op)\n return;\n } else if (isLiftWave) {\n // Lift wave: lerp from spherical dots to original shape\n let t = (scaleFactor - 0.1) * 1.111111; // normalize [0.1, 1.0] to [0, 1]\n let dotSize = scaleFactor * 0.05;\n let finalSize = mix(dotSize, origSize, t);\n\n // Lerp between spherical (uniform) and scaled original\n let sphericalScale = vec3f(finalSize);\n let scaledOrig = origScale * scaleFactor;\n *scale = mix(sphericalScale, scaledOrig, t);\n } else {\n // Dot phase: spherical with absolute size, but don't make small splats larger\n let targetSize = min(scaleFactor * 0.05, origSize);\n gsplatMakeSpherical(scale, targetSize);\n }\n}\n\nfn revealColorEffect(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 * uniform.uBandWidth && g_dist <= g_liftWavePos + 0.5 * uniform.uBandWidth) {\n let distToLift = abs(g_dist - g_liftWavePos);\n let liftIntensity = smoothstep(1.5 * uniform.uBandWidth, 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 * uniform.uBandWidth)) {\n let distToDot = abs(g_dist - g_dotWavePos);\n let dotIntensity = smoothstep(1.0 * uniform.uBandWidth, 0.0, distToDot);\n (*color) = vec4f((*color).rgb + uniform.uDotTint * dotIntensity, (*color).a);\n }\n}\n\nfn modifySplatColor(center: vec3f, color: ptr<function, vec4f>) {\n // EFFECT_HOOK\n revealColorEffect(center, color);\n}\n",_applyShaders(){const t=this.entity.gsplat;if(t)if(t.unified){if(this.material=this.app.scene.gsplat?.material??null,!this.material)return void console.warn("[RevealEffect] Unified gsplat template material not available");this._applyShaderToMaterial(this.material)}else{const e=t.material;e?(this.material=e,this._applyShaderToMaterial(e)):t.once("load",()=>{this.material=t.material??null,this.material&&this._applyShaderToMaterial(this.material)})}},_applyShaderToMaterial(t){const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl";let o="wgsl"===n?this.getShaderWGSL():this.getShaderGLSL();const i=this.entity.script&&this.entity.script.gsplatRelighting;if(i&&i.enabled){i._shaderManagedExternally=!0;o=("wgsl"===n?O:V)+"\n"+o.replace("// EFFECT_HOOK","applyRelighting(center, color);"),console.log("[RevealEffect] Combined shader with relighting")}t.getShaderChunks(n).set("gsplatModifyVS",o),t.update()},_removeShaders(){if(!this.material)return;const t=this.entity.script&&this.entity.script.gsplatRelighting;if(t&&t.enabled){t.material=this.material;const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl",o="wgsl"===n?t.getShaderWGSL():t.getShaderGLSL();return this.material.getShaderChunks(n).set("gsplatModifyVS",o),this.material.update(),t._shaderManagedExternally=!1,this.material=null,void console.log("[RevealEffect] Handed material back to relighting")}const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl";this.material.getShaderChunks(n).delete("gsplatModifyVS"),this.material.update(),this.material=null},_setUniform(t,e){this.material?.setParameter(t,e)},destroy(){this._removeShaders()}}),j=e,e}let Z=null;let Q=null;let K=null;function J(){if(K)return K;const e=t.createScript("gsplatRevealBloom");return Object.assign(e.prototype,{effectTime:0,material:null,_effectInitialized:!1,_shadersNeedApplication:!1,_bloomTintArray:[0,0,0],_settleTintArray:[0,0,0],_centerArray:[0,0,0],center:null,speed:25,acceleration:8,delay:0,oscillationIntensity:.2,dotTint:null,waveTint:null,endRadius:500,_autoEndRadius:0,initialize(){this.effectTime=0,this.material=null,this._effectInitialized=!1,this._shadersNeedApplication=!1,this._bloomTintArray=[0,0,0],this._settleTintArray=[0,0,0],this._centerArray=[0,0,0],this.center||(this.center=new t.Vec3(0,0,0)),this.dotTint||(this.dotTint=new t.Color(1,.6,.15)),this.waveTint||(this.waveTint=new t.Color(.2,.5,1)),this.on("enable",()=>{this.effectTime=0,this._effectInitialized?this._applyShaders():this._shadersNeedApplication=!0}),this.on("disable",()=>{this._removeShaders()}),this.entity.gsplat&&(this._effectInitialized=!0,this._detectSceneRadius(),this.enabled&&this._applyShaders())},update(t){if(this._effectInitialized){if(this._shadersNeedApplication&&(this._applyShaders(),this.material&&(this._shadersNeedApplication=!1)),this.material){if(this.effectTime+=t,this._isEffectComplete())return console.log("[BloomReveal] Effect complete, disabling"),void(this.enabled=!1);this._updateUniforms(),this.material.update()}}else this.entity.gsplat&&(this._effectInitialized=!0,this._detectSceneRadius(),this.enabled&&this._shadersNeedApplication&&(this._applyShaders(),this.material&&(this._shadersNeedApplication=!1)))},_detectSceneRadius(){try{const t=this.entity.gsplat,e=t&&t.instance,n=e?.meshInstance??e?.meshInstances?.[0],o=n?.aabb;if(o){const t=o.halfExtents,e=Math.sqrt(t.x*t.x+t.y*t.y+t.z*t.z);this._autoEndRadius=1.2*e}}catch(t){}},_getCompletionTime(){const t=this._autoEndRadius>0?this._autoEndRadius:this.endRadius;if(0===this.acceleration)return t/this.speed+.5;const e=this.speed*this.speed+2*this.acceleration*t;if(e<0)return 1/0;return(-this.speed+Math.sqrt(e))/this.acceleration+.5},_isEffectComplete(){return this.effectTime>=this._getCompletionTime()},_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("uWaveSpeed",this.speed),this._setUniform("uAcceleration",this.acceleration),this._setUniform("uBandWidth",3),this._setUniform("uOvershoot",1+Math.max(0,this.oscillationIntensity)),this._bloomTintArray[0]=this.dotTint.r,this._bloomTintArray[1]=this.dotTint.g,this._bloomTintArray[2]=this.dotTint.b,this._setUniform("uBloomTint",this._bloomTintArray),this._settleTintArray[0]=this.waveTint.r,this._settleTintArray[1]=this.waveTint.g,this._settleTintArray[2]=this.waveTint.b,this._setUniform("uSettleTint",this._settleTintArray)},getShaderGLSL:()=>"\nuniform float uTime;\nuniform vec3 uCenter;\nuniform float uWaveSpeed;\nuniform float uAcceleration;\nuniform float uBandWidth;\nuniform float uOvershoot; // e.g. 1.2 = 20% overshoot\nuniform vec3 uBloomTint; // warm color at wavefront\nuniform vec3 uSettleTint; // cool color trailing behind\n\n// Shared per-splat state\nfloat g_dist;\nfloat g_bloom; // <0.01 = hidden, 0..1 = blooming, 1..overshoot = overshoot, settled = 1.0\n\nfloat hash(vec3 p) {\n return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453);\n}\n\nvoid initBloom(vec3 center) {\n g_dist = length(center - uCenter);\n\n // Wave position with acceleration\n float wavePos = uWaveSpeed * uTime + 0.5 * uAcceleration * uTime * uTime;\n\n // Per-splat jitter so the wavefront isn't a perfect circle\n float jitter = (hash(center) - 0.5) * uBandWidth * 0.4;\n float effectiveDist = g_dist + jitter;\n\n if (effectiveDist > wavePos + uBandWidth * 0.5) {\n // Ahead of wave: hidden\n g_bloom = 0.0;\n } else if (effectiveDist > wavePos - uBandWidth * 0.5) {\n // Inside the bloom band: transitioning 0 → 1\n g_bloom = smoothstep(wavePos + uBandWidth * 0.5, wavePos - uBandWidth * 0.5, effectiveDist);\n } else {\n // Behind the wave: bloom complete, calculate overshoot settle\n float distBehind = (wavePos - uBandWidth * 0.5) - effectiveDist;\n // Time since this splat's bloom completed (approximate)\n float settleTime = distBehind / max(uWaveSpeed + uAcceleration * uTime, 1.0);\n // Overshoot: peaks at settle start, decays to 1.0 quickly\n float overshootDecay = exp(-settleTime * 6.0);\n g_bloom = 1.0 + (uOvershoot - 1.0) * overshootDecay;\n }\n}\n\nvoid modifySplatCenter(inout vec3 center) {\n initBloom(center);\n}\n\nvoid modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) {\n if (g_bloom < 0.01) {\n scale = vec3(0.0);\n return;\n }\n\n // Fully settled\n if (g_bloom >= 0.99 && g_bloom <= 1.01) {\n return;\n }\n\n float origSize = gsplatGetSizeFromScale(scale);\n\n if (g_bloom <= 1.0) {\n // Bloom phase: transition from dot to full size\n float t = g_bloom;\n\n if (t < 0.15) {\n // Tiny spherical spark\n float dotSize = min(t * 0.4 * origSize, origSize);\n gsplatMakeSpherical(scale, dotSize);\n } else if (t < 0.5) {\n // Growing spherical dot\n float size = mix(0.06, 0.5, (t - 0.15) / 0.35) * origSize;\n gsplatMakeSpherical(scale, min(size, origSize));\n } else {\n // Transitioning from spherical to original shape\n float morphT = (t - 0.5) / 0.5; // 0→1\n float sizeFactor = mix(0.5, 1.0, morphT);\n scale *= sizeFactor;\n }\n } else {\n // Overshoot phase: slightly larger than original\n scale *= g_bloom;\n }\n}\n\nvoid bloomColorEffect(vec3 center, inout vec4 color) {\n if (g_bloom < 0.01) return;\n\n if (g_bloom <= 1.0) {\n // Bloom phase: bright tint, strongest at wavefront (g_bloom 0.2-0.6)\n float waveFrontIntensity = sin(clamp(g_bloom, 0.0, 1.0) * 3.14159);\n color.rgb += uBloomTint * waveFrontIntensity * 0.5;\n } else if (g_bloom > 1.005) {\n // Overshoot phase: brief cool flash as splat settles\n float overshootAmount = (g_bloom - 1.0) / max(uOvershoot - 1.0, 0.01);\n color.rgb += uSettleTint * overshootAmount * 0.3;\n }\n}\n\nvoid modifySplatColor(vec3 center, inout vec4 color) {\n // EFFECT_HOOK\n bloomColorEffect(center, color);\n}\n",getShaderWGSL:()=>"\nuniform uTime: f32;\nuniform uCenter: vec3f;\nuniform uWaveSpeed: f32;\nuniform uAcceleration: f32;\nuniform uBandWidth: f32;\nuniform uOvershoot: f32;\nuniform uBloomTint: vec3f;\nuniform uSettleTint: vec3f;\n\nvar<private> g_dist: f32;\nvar<private> g_bloom: f32;\n\nfn hash(p: vec3f) -> f32 {\n return fract(sin(dot(p, vec3f(127.1, 311.7, 74.7))) * 43758.5453);\n}\n\nfn initBloom(center: vec3f) {\n g_dist = length(center - uniform.uCenter);\n\n let wavePos = uniform.uWaveSpeed * uniform.uTime + 0.5 * uniform.uAcceleration * uniform.uTime * uniform.uTime;\n\n let jitter = (hash(center) - 0.5) * uniform.uBandWidth * 0.4;\n let effectiveDist = g_dist + jitter;\n\n if (effectiveDist > wavePos + uniform.uBandWidth * 0.5) {\n g_bloom = 0.0;\n } else if (effectiveDist > wavePos - uniform.uBandWidth * 0.5) {\n g_bloom = smoothstep(wavePos + uniform.uBandWidth * 0.5, wavePos - uniform.uBandWidth * 0.5, effectiveDist);\n } else {\n let distBehind = (wavePos - uniform.uBandWidth * 0.5) - effectiveDist;\n let settleTime = distBehind / max(uniform.uWaveSpeed + uniform.uAcceleration * uniform.uTime, 1.0);\n let overshootDecay = exp(-settleTime * 6.0);\n g_bloom = 1.0 + (uniform.uOvershoot - 1.0) * overshootDecay;\n }\n}\n\nfn modifySplatCenter(center: ptr<function, vec3f>) {\n initBloom(*center);\n}\n\nfn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr<function, vec4f>, scale: ptr<function, vec3f>) {\n if (g_bloom < 0.01) {\n *scale = vec3f(0.0);\n return;\n }\n\n if (g_bloom >= 0.99 && g_bloom <= 1.01) {\n return;\n }\n\n let origSize = gsplatGetSizeFromScale(*scale);\n\n if (g_bloom <= 1.0) {\n let t = g_bloom;\n\n if (t < 0.15) {\n let dotSize = min(t * 0.4 * origSize, origSize);\n gsplatMakeSpherical(scale, dotSize);\n } else if (t < 0.5) {\n let size = mix(0.06, 0.5, (t - 0.15) / 0.35) * origSize;\n gsplatMakeSpherical(scale, min(size, origSize));\n } else {\n let morphT = (t - 0.5) / 0.5;\n let sizeFactor = mix(0.5, 1.0, morphT);\n *scale *= sizeFactor;\n }\n } else {\n *scale *= g_bloom;\n }\n}\n\nfn bloomColorEffect(center: vec3f, color: ptr<function, vec4f>) {\n if (g_bloom < 0.01) {\n return;\n }\n\n if (g_bloom <= 1.0) {\n let waveFrontIntensity = sin(clamp(g_bloom, 0.0, 1.0) * 3.14159);\n (*color) = vec4f((*color).rgb + uniform.uBloomTint * waveFrontIntensity * 0.5, (*color).a);\n } else if (g_bloom > 1.005) {\n let overshootAmount = (g_bloom - 1.0) / max(uniform.uOvershoot - 1.0, 0.01);\n (*color) = vec4f((*color).rgb + uniform.uSettleTint * overshootAmount * 0.3, (*color).a);\n }\n}\n\nfn modifySplatColor(center: vec3f, color: ptr<function, vec4f>) {\n // EFFECT_HOOK\n bloomColorEffect(center, color);\n}\n",_applyShaders(){const t=this.entity.gsplat;if(t)if(t.unified){if(this.material=this.app.scene.gsplat?.material??null,!this.material)return void(this._shadersNeedApplication=!0);this._applyShaderToMaterial(this.material)}else{const e=t.material;e?(this.material=e,this._applyShaderToMaterial(e)):this._shadersNeedApplication=!0}},_applyShaderToMaterial(t){const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl";let o="wgsl"===n?this.getShaderWGSL():this.getShaderGLSL();const i=this.entity.script&&this.entity.script.gsplatRelighting;if(i&&i.enabled){i._shaderManagedExternally=!0;o=("wgsl"===n?O:V)+"\n"+o.replace("// EFFECT_HOOK","applyRelighting(center, color);")}t.getShaderChunks(n).set("gsplatModifyVS",o),t.update()},_removeShaders(){if(!this.material)return;const t=this.entity.script&&this.entity.script.gsplatRelighting;if(t&&t.enabled){t.material=this.material;const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl",o="wgsl"===n?t.getShaderWGSL():t.getShaderGLSL();return this.material.getShaderChunks(n).set("gsplatModifyVS",o),this.material.update(),t._shaderManagedExternally=!1,void(this.material=null)}const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl";this.material.getShaderChunks(n).delete("gsplatModifyVS"),this.material.update(),this.material=null},_setUniform(t,e){this.material?.setParameter(t,e)},destroy(){this._removeShaders()}}),K=e,e}const tt={fast:{speed:10,acceleration:2,delay:.5,oscillationIntensity:.1,dotTint:{r:0,g:1,b:1},waveTint:{r:1,g:.5,b:0},endRadius:500},medium:{speed:5,acceleration:0,delay:2,oscillationIntensity:.2,dotTint:{r:0,g:1,b:1},waveTint:{r:1,g:.5,b:0},endRadius:500},slow:{speed:3,acceleration:0,delay:3,oscillationIntensity:.25,dotTint:{r:0,g:1,b:1},waveTint:{r:1,g:.5,b:0},endRadius:500}},et={fast:{speed:8,acceleration:25,delay:0,oscillationIntensity:.15,dotTint:{r:1,g:.6,b:.15},waveTint:{r:.2,g:.5,b:1},endRadius:500},medium:{speed:5,acceleration:18,delay:0,oscillationIntensity:.2,dotTint:{r:1,g:.6,b:.15},waveTint:{r:.2,g:.5,b:1},endRadius:500},slow:{speed:3,acceleration:10,delay:0,oscillationIntensity:.25,dotTint:{r:1,g:.6,b:.15},waveTint:{r:.2,g:.5,b:1},endRadius:500}};function nt(t,e="bloom"){if("none"===t)return;return("bloom"===e?et:tt)[t]}var ot,it={},st={},at={};function rt(){if(ot)return at;ot=1,Object.defineProperty(at,"__esModule",{value:!0}),at.loop=at.conditional=at.parse=void 0;at.parse=function t(e,n){var o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:o;if(Array.isArray(n))n.forEach(function(n){return t(e,n,o,i)});else if("function"==typeof n)n(e,o,i,t);else{var s=Object.keys(n)[0];Array.isArray(n[s])?(i[s]={},t(e,n[s],o,i[s])):i[s]=n[s](e,o,i,t)}return o};at.conditional=function(t,e){return function(n,o,i,s){e(n,o,i)&&s(n,t,o,i)}};return at.loop=function(t,e){return function(n,o,i,s){for(var a=[],r=n.pos;e(n,o,i);){var l={};if(s(n,t,o,l),n.pos===r)break;r=n.pos,a.push(l)}return a}},at}var lt,ct,dt={};function pt(){if(lt)return dt;lt=1,Object.defineProperty(dt,"__esModule",{value:!0}),dt.readBits=dt.readArray=dt.readUnsigned=dt.readString=dt.peekBytes=dt.readBytes=dt.peekByte=dt.readByte=dt.buildStream=void 0;dt.buildStream=function(t){return{data:t,pos:0}};var t=function(){return function(t){return t.data[t.pos++]}};dt.readByte=t;dt.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)}};dt.readBytes=e;dt.peekBytes=function(t){return function(e){return e.data.subarray(e.pos,e.pos+t)}};dt.readString=function(t){return function(n){return Array.from(e(t)(n)).map(function(t){return String.fromCharCode(t)}).join("")}};dt.readUnsigned=function(t){return function(n){var o=e(2)(n);return t?(o[1]<<8)+o[0]:(o[0]<<8)+o[1]}};dt.readArray=function(t,n){return function(o,i,s){for(var a="function"==typeof n?n(o,i,s):n,r=e(t),l=new Array(a),c=0;c<a;c++)l[c]=r(o);return l}};return dt.readBits=function(t){return function(e){for(var n=function(t){return t.data[t.pos++]}(e),o=new Array(8),i=0;i<8;i++)o[7-i]=!!(n&1<<i);return Object.keys(t).reduce(function(e,n){var i=t[n];return i.length?e[n]=function(t,e,n){for(var o=0,i=0;i<n;i++)o+=t[e+i]&&Math.pow(2,n-i-1);return o}(o,i.index,i.length):e[n]=o[i.index],e},{})}},dt}var ht,ut={};var mt,gt,ft={};var yt=function(){if(gt)return it;gt=1,Object.defineProperty(it,"__esModule",{value:!0}),it.decompressFrames=it.decompressFrame=it.parseGIF=void 0;var t,e=(ct||(ct=1,function(t){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var e=rt(),n=pt(),o={blocks:function(t){for(var e=[],o=t.data.length,i=0,s=(0,n.readByte)()(t);0!==s&&s;s=(0,n.readByte)()(t)){if(t.pos+s>=o){var a=o-t.pos;e.push((0,n.readBytes)(a)(t)),i+=a;break}e.push((0,n.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,n.readBytes)(2)},{byteSize:(0,n.readByte)()},{extras:(0,n.readBits)({future:{index:0,length:3},disposal:{index:3,length:3},userInput:{index:6},transparentColorGiven:{index:7}})},{delay:(0,n.readUnsigned)(!0)},{transparentColorIndex:(0,n.readByte)()},{terminator:(0,n.readByte)()}]},function(t){var e=(0,n.peekBytes)(2)(t);return 33===e[0]&&249===e[1]}),s=(0,e.conditional)({image:[{code:(0,n.readByte)()},{descriptor:[{left:(0,n.readUnsigned)(!0)},{top:(0,n.readUnsigned)(!0)},{width:(0,n.readUnsigned)(!0)},{height:(0,n.readUnsigned)(!0)},{lct:(0,n.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,n.readArray)(3,function(t,e,n){return Math.pow(2,n.descriptor.lct.size+1)})},function(t,e,n){return n.descriptor.lct.exists}),{data:[{minCodeSize:(0,n.readByte)()},o]}]},function(t){return 44===(0,n.peekByte)()(t)}),a=(0,e.conditional)({text:[{codes:(0,n.readBytes)(2)},{blockSize:(0,n.readByte)()},{preData:function(t,e,o){return(0,n.readBytes)(o.text.blockSize)(t)}},o]},function(t){var e=(0,n.peekBytes)(2)(t);return 33===e[0]&&1===e[1]}),r=(0,e.conditional)({application:[{codes:(0,n.readBytes)(2)},{blockSize:(0,n.readByte)()},{id:function(t,e,o){return(0,n.readString)(o.blockSize)(t)}},o]},function(t){var e=(0,n.peekBytes)(2)(t);return 33===e[0]&&255===e[1]}),l=(0,e.conditional)({comment:[{codes:(0,n.readBytes)(2)},o]},function(t){var e=(0,n.peekBytes)(2)(t);return 33===e[0]&&254===e[1]}),c=[{header:[{signature:(0,n.readString)(3)},{version:(0,n.readString)(3)}]},{lsd:[{width:(0,n.readUnsigned)(!0)},{height:(0,n.readUnsigned)(!0)},{gct:(0,n.readBits)({exists:{index:0},resolution:{index:1,length:3},sort:{index:4},size:{index:5,length:3}})},{backgroundColorIndex:(0,n.readByte)()},{pixelAspectRatio:(0,n.readByte)()}]},(0,e.conditional)({gct:(0,n.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,n.peekByte)()(t);return 33===e||44===e})}];t.default=c}(st)),(t=st)&&t.__esModule?t:{default:t}),n=rt(),o=pt(),i=(ht||(ht=1,Object.defineProperty(ut,"__esModule",{value:!0}),ut.deinterlace=void 0,ut.deinterlace=function(t,e){for(var n=new Array(t.length),o=t.length/e,i=function(o,i){var s=t.slice(i*e,(i+1)*e);n.splice.apply(n,[o*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<o;c+=a[l])i(c,r),r++;return n}),ut),s=(mt||(mt=1,Object.defineProperty(ft,"__esModule",{value:!0}),ft.lzw=void 0,ft.lzw=function(t,e,n){var o,i,s,a,r,l,c,d,p,h,u,m,g,f,y,v,x=4096,b=n,w=new Array(n),S=new Array(x),_=new Array(x),M=new Array(4097);for(r=1+(i=1<<(h=t)),o=i+2,c=-1,s=(1<<(a=h+1))-1,d=0;d<i;d++)S[d]=0,_[d]=d;for(u=m=g=f=y=v=0,p=0;p<b;){if(0===f){if(m<a){u+=e[v]<<m,m+=8,v++;continue}if(d=u&s,u>>=a,m-=a,d>o||d==r)break;if(d==i){s=(1<<(a=h+1))-1,o=i+2,c=-1;continue}if(-1==c){M[f++]=_[d],c=d,g=d;continue}for(l=d,d==o&&(M[f++]=g,d=c);d>i;)M[f++]=_[d],d=S[d];g=255&_[d],M[f++]=g,o<x&&(S[o]=c,_[o]=g,0===(++o&s)&&o<x&&(a++,s+=o)),c=l}f--,w[y++]=M[f],p++}for(p=y;p<b;p++)w[p]=0;return w}),ft);it.parseGIF=function(t){var i=new Uint8Array(t);return(0,n.parse)((0,o.buildStream)(i),e.default)};var a=function(t,e,n){if(t.image){var o=t.image,a=o.descriptor.width*o.descriptor.height,r=(0,s.lzw)(o.data.minCodeSize,o.data.blocks,a);o.descriptor.lct.interlaced&&(r=(0,i.deinterlace)(r,o.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 o.descriptor.lct&&o.descriptor.lct.exists?l.colorTable=o.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)),n&&(l.patch=function(t){for(var e=t.pixels.length,n=new Uint8ClampedArray(4*e),o=0;o<e;o++){var i=4*o,s=t.pixels[o],a=t.colorTable[s]||[0,0,0];n[i]=a[0],n[i+1]=a[1],n[i+2]=a[2],n[i+3]=s!==t.transparentIndex?255:0}return n}(l)),l}console.warn("gif frame does not have associated image.")};return it.decompressFrame=a,it.decompressFrames=function(t,e){return t.frames.filter(function(t){return t.image}).map(function(n){return a(n,t.gct,e)})},it}();class vt{constructor(t,e,n={}){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=n,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 n=await e.arrayBuffer(),o=yt.parseGIF(n);if(this.frames=yt.decompressFrames(o,!0),0===this.frames.length)throw new Error("GIF has no frames");this.gifWidth=o.lsd.width,this.gifHeight=o.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],n=t>0?this.frames[t-1]:null;n&&2===n.disposalType&&this.ctx.clearRect(n.dims.left,n.dims.top,n.dims.width,n.dims.height);const o=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(o,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}}function xt(t,e){return t?{x:t._x??t.x??e.x,y:t._y??t.y??e.y,z:t._z??t.z??e.z}:e}function bt(t){return Math.abs(t)<1e-10?0:t}class wt{constructor(e){this.meshes=new Map,this.onUpdate=()=>{const e=function(t){const e=t.root.findComponent("camera");return e?e.entity:null}(this.app);if(!e?.camera)return;const n=e.camera,o=this.canvas.clientHeight,i=n.projectionMatrix.data[5]*(o/2);this.overlayContainer.style.perspective=`${i}px`;const s=n.viewMatrix.data;var a;this.cameraEl.style.transform=`translateZ(${i}px) ${a=s,`matrix3d(${bt(a[0])},${bt(-a[1])},${bt(a[2])},${bt(a[3])},${bt(a[4])},${bt(-a[5])},${bt(a[6])},${bt(a[7])},${bt(a[8])},${bt(-a[9])},${bt(a[10])},${bt(a[11])},${bt(a[12])},${bt(-a[13])},${bt(a[14])},${bt(a[15])})`}`;const r=e.getPosition();this.meshes.forEach(e=>{if(e.config.billboard){if(e.entity._billboardActive??!0){const n=e.entity.getLocalScale().clone();e.entity.lookAt(r);const o=e.entity.getRotation().clone(),i=(new t.Quat).setFromEulerAngles(0,180,0);e.entity.setRotation((new t.Quat).mul2(o,i)),e.entity.setLocalScale(n)}}if(!e.entity.enabled)return void(e.htmlElement.style.display="none");e.htmlElement.style.display="";const n=e.entity.getWorldTransform().data;e.htmlElement.style.transform=`translate(-50%, -50%) ${function(t){return`matrix3d(${bt(t[0])},${bt(t[1])},${bt(t[2])},${bt(t[3])},${bt(-t[4])},${bt(-t[5])},${bt(-t[6])},${bt(-t[7])},${bt(t[8])},${bt(t[9])},${bt(t[10])},${bt(t[11])},${bt(t[12])},${bt(t[13])},${bt(t[14])},${bt(t[15])})`}(n)} scale(${bt(1/e.pixelWidth)}, ${bt(1/e.pixelHeight)})`})},this.app=e,this.canvas=e.graphicsDevice.canvas,this.overlayContainer=document.createElement("div"),Object.assign(this.overlayContainer.style,{position:"absolute",top:"0",left:"0",width:"100%",height:"100%",overflow:"hidden",pointerEvents:"none",zIndex:"1"}),this.cameraEl=document.createElement("div"),Object.assign(this.cameraEl.style,{position:"absolute",left:"50%",top:"50%",width:"0",height:"0",transformStyle:"preserve-3d"}),this.overlayContainer.appendChild(this.cameraEl);const n=this.canvas.parentElement;n&&("static"===getComputedStyle(n).position&&(n.style.position="relative"),n.appendChild(this.overlayContainer)),e.on("update",this.onUpdate,this),console.log("[HtmlMeshManager] CSS3D overlay initialised")}createMesh(t){let e=t.width||512,n=t.height||512;e<4&&(e=512),n<4&&(n=512),e=Math.round(e),n=Math.round(n);const o=e/n,i=xt(t.scale,{x:1,y:1,z:1}),s=i.x*o,a=i.y,r=this.createEntity(t,s,a),l=this.createDomElement(t,e,n);this.cameraEl.appendChild(l);const c={entity:r,htmlElement:l,config:t,pixelWidth:e,pixelHeight:n,destroy:()=>this.destroyMesh(t.id),update:()=>{}};return this.meshes.set(t.id,c),console.log(`[HtmlMeshManager] Created CSS3D mesh: ${t.id}, pixels=${e}x${n}, world=${s.toFixed(2)}x${a.toFixed(2)}`),c}destroyMesh(t){const e=this.meshes.get(t);e&&(e.entity.destroy(),e.htmlElement.remove(),this.meshes.delete(t))}getMesh(t){return this.meshes.get(t)}getAllMeshes(){return this.meshes}updateVisibility(t,e){this.meshes.forEach(n=>{const o=n.config;if(o.visibilityRange){const i=o.visibilityRange;let s=!0;"percentage"===i.type?s=t>=i.start&&t<=i.end:"waypoint"===i.type&&(s=e>=i.start&&e<=i.end),n.entity.enabled=s}if(o.billboard&&o.billboardRange){const i=o.billboardRange;let s=!1;"percentage"===i.type?s=t>=i.start&&t<=i.end:"waypoint"===i.type&&(s=e>=i.start&&e<=i.end),n.entity._billboardActive=s}else o.billboard&&(n.entity._billboardActive=!0)})}updateMeshTexture(t){}destroy(){this.meshes.forEach((t,e)=>this.destroyMesh(e)),this.app.off("update",this.onUpdate,this),this.overlayContainer.remove()}createEntity(e,n,o){const i=new t.Entity(`htmlMesh-${e.id}`),s=xt(e.position,{x:0,y:0,z:0});i.setPosition(s.x,s.y,-s.z);const a=xt(e.rotation,{x:0,y:0,z:0}),r=a.y/2,l=a.x/2,c=a.z/2,d=Math.cos(r),p=Math.sin(r),h=Math.cos(l),u=Math.sin(l),m=Math.cos(c),g=Math.sin(c),f=d*h*m+p*u*g,y=d*u*m+p*h*g,v=p*h*m-d*u*g,x=d*h*g-p*u*m;return i.setRotation(-y,-v,x,f),i.setLocalScale(n,o,1),this.app.root.addChild(i),i}createDomElement(t,e,n){const o=document.createElement("div");o.id=`html-mesh-${t.id}`,Object.assign(o.style,{position:"absolute",width:`${e}px`,height:`${n}px`,transformStyle:"preserve-3d",backfaceVisibility:"hidden",pointerEvents:"auto",overflow:"hidden",opacity:String(t.opacity??1)});const i=t.contentType||"html",s=t.iframeUrl,a=t.html||t.htmlContent||"";if("iframe"===i&&s){const t=document.createElement("iframe");t.src=s,Object.assign(t.style,{width:"100%",height:"100%",border:"none",display:"block",background:"#fff"}),t.setAttribute("allow","fullscreen; autoplay; encrypted-media"),t.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-popups allow-presentation"),o.appendChild(t)}else if(a){if(t.css){const e=document.createElement("style");e.textContent=t.css,o.appendChild(e)}const e=document.createElement("div");e.style.width="100%",e.style.height="100%",e.innerHTML=a,o.appendChild(e)}else{Object.assign(o.style,{background:"rgba(26, 26, 46, 0.9)",display:"flex",alignItems:"center",justifyContent:"center",color:"#fff",fontFamily:"sans-serif"});const t=document.createElement("div");t.style.textAlign="center";const e=document.createElement("div");e.style.fontSize="24px",e.style.marginBottom="8px",e.textContent="</>";const n=document.createElement("div");n.textContent="HTML Content",t.appendChild(e),t.appendChild(n),o.appendChild(t)}return o}}function St(t,e){const n=new wt(t);console.log(`[HtmlMeshManager] Setting up ${e.length} HTML meshes (CSS3D)`);for(const t of e)console.log(`[HtmlMeshManager] Creating mesh: ${t.id}`,{html:(t.html||t.htmlContent||"").substring(0,80),contentType:t.contentType,iframeUrl:t.iframeUrl,position:t.position,scale:t.scale,width:t.width,height:t.height}),n.createMesh(t);return n}const _t=new Set(["goToWaypoint","nextWaypoint","prevWaypoint","getCurrentWaypointIndex","getWaypointCount","setCameraMode","getCameraMode","setExploreMode","setPosition","setRotation","getPosition","getRotation","play","pause","stop","isPlaying","setProgress","getProgress","muteAll","unmuteAll","isMuted","getHotspots","triggerHotspot","closeHotspot","goToOriginalSplat","goToSplat","getCurrentSplatUrl","isShowingOriginalSplat","getAdditionalSplats","setFrame","getCurrentFrame","getTotalFrames","setFps","getFps","getFrameProgress","setFrameProgress","setButtonLabels","on","off","resize"]),Mt=new Set(["width","height","clientWidth","clientHeight","getBoundingClientRect","addEventListener","removeEventListener"]),Et=new Set(["ownerDocument","parentElement","parentNode","getRootNode","closest","querySelector","querySelectorAll","children","childNodes","firstChild","lastChild","nextSibling","previousSibling","nextElementSibling","previousElementSibling","innerHTML","outerHTML","insertAdjacentHTML","insertAdjacentElement","append","appendChild","removeChild","replaceChild","remove","constructor","__proto__","prototype"]),Ct=["window","document","self","globalThis","top","parent","frames","opener","exports","module","require","define","fetch","XMLHttpRequest","WebSocket","EventSource","localStorage","sessionStorage","indexedDB","caches","Worker","SharedWorker","ServiceWorker","eval","Function","importScripts","Blob","File","FormData","URL","URLSearchParams","Image","Audio","MediaStream","alert","confirm","prompt","location","history","MutationObserver","IntersectionObserver","ResizeObserver","postMessage","BroadcastChannel","Proxy","Reflect","Symbol","close","stop","getComputedStyle","matchMedia","btoa","atob","structuredClone","reportError","createImageBitmap","navigator","screen","crypto","requestIdleCallback"];class Tt{constructor(t){this.isInitialized=!1,this.scriptCleanup=[],this.lastError=null,this.updateCallbacks=[],this.app=null,this.cancelWatchdog=null,this.customScript=t,this.api={}}initialize(t,e){this.api={...t,registerCleanup:t=>this.addCleanup(t)},this.app=e,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||!this.app)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);var e,n;if(/\bimport\s*\(/.test(t))console.warn("[Custom Script] Dynamic import() is not allowed in custom scripts.");else try{const o=this.app;let i=!1;this.cancelWatchdog=()=>{i=!0};const s=()=>{i||(this.updateCallbacks.length>200?(console.warn("[Custom Script] Too many update callbacks; further callbacks ignored."),i=!0):requestAnimationFrame(s))};requestAnimationFrame(s);const a=t=>{if("function"!=typeof t||i)return;const e=()=>{try{t()}catch(t){console.error("[Custom Script] Error in update callback:",t)}};this.updateCallbacks.push(e),o.on("update",e),this.addCleanup(()=>{o.off("update",e);const t=this.updateCallbacks.indexOf(e);-1!==t&&this.updateCallbacks.splice(t,1)})},r=(n=this.api.viewer,new Proxy(n,{get(t,e){if("constructor"!==e&&"__proto__"!==e&&"prototype"!==e&&"string"==typeof e&&_t.has(e)){const n=t[e];return"function"==typeof n?n.bind(t):n}},set:()=>!1,has:(t,e)=>"string"==typeof e&&_t.has(e),ownKeys:()=>[..._t],getPrototypeOf:()=>null})),l=(e=this.api.canvas,new Proxy(e,{get(t,e){if(!Et.has(e)&&"string"==typeof e&&Mt.has(e)){const n=t[e];return"function"==typeof n?n.bind(t):n}},set:()=>!1,getPrototypeOf:()=>null})),c=function(){const t={log:console.log.bind(console),warn:console.warn.bind(console),error:console.error.bind(console),info:console.info.bind(console),debug:console.debug.bind(console)};return Object.freeze(t),t}(),{getScrollPercentage:d,getCurrentWaypointIndex:p}=this.api,h=["viewer","canvas","getScrollPercentage","getCurrentWaypointIndex","registerUpdate","registerCleanup","console","setTimeout","setInterval","clearTimeout","clearInterval","requestAnimationFrame","cancelAnimationFrame","queueMicrotask",...Ct],u="'use strict';\n"+t,m=function(){const t=[],e=[Object.prototype,Array.prototype,String.prototype,Number.prototype,Boolean.prototype,RegExp.prototype,Error.prototype,TypeError.prototype,RangeError.prototype,Map.prototype,Set.prototype,WeakMap.prototype,WeakSet.prototype,Promise.prototype];try{e.push((async()=>{}).constructor.prototype)}catch{}try{e.push(function*(){}.constructor.prototype)}catch{}try{e.push(async function*(){}.constructor.prototype)}catch{}for(const n of e){const e=Object.getOwnPropertyDescriptor(n,"constructor");t.push([n,e]),Object.defineProperty(n,"constructor",{get(){},configurable:!0})}return()=>{for(const[e,n]of t)n?Object.defineProperty(e,"constructor",n):delete e.constructor}}();try{const t=new Function(...h,u),e=[r,l,d,p,a,t=>this.addCleanup(t),c,setTimeout,setInterval,clearTimeout,clearInterval,requestAnimationFrame,cancelAnimationFrame,queueMicrotask,...Ct.map(()=>{})],n=t.apply(null,e);"function"==typeof n&&this.addCleanup(n)}finally{m()}console.log("[Custom Script] Executed successfully")}catch(t){this.lastError=t,console.error("[Custom Script] Execution error:",t)}}cleanup(){this.cancelWatchdog?.(),this.cancelWatchdog=null,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,this.app=null}}function At(t,e,n,o,i,s){if(!o||""===o.trim())return null;console.log("[Custom Script] Initializing with hardened sandbox...");const a=new Tt(o);return a.initialize({viewer:t,canvas:n,getScrollPercentage:i,getCurrentWaypointIndex:s},e),a.execute(),a}class Pt{constructor(t,e,n={}){this.options=n,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 n=new t.Entity(e);return n.addComponent("gsplat",{}),this.app.root.addChild(n),n}async preloadInitialFrames(){const t=Math.min(this.preloadCount,this.frameUrls.length),e=[];for(let n=0;n<t;n++)e.push(this.preloadFrame(n));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(n=>{const o=this.frameUrls[e],i=new t.Asset(`frame_${e}`,"gsplat",{url:o});i.on("load",()=>{this.destroyed||(this.frameAssets.set(e,i),this.loadingFrames.delete(e)),n(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}`),n(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 n=(this.activeEntityIndex+1)%2,o=this.entities[this.activeEntityIndex],i=this.entities[n],s=i.gsplat;s&&(s.asset=e),i.enabled=!0,o.enabled=!1,this.activeEntityIndex=n,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(),n=e-this.lastFrameTime;if(n>=this.frameInterval){this.lastFrameTime=e-n%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,n){this.entities[0].setPosition(t,e,n),this.entities[1].setPosition(t,e,n)}setRotation(t,e,n){this.entities[0].setEulerAngles(t,e,n),this.entities[1].setEulerAngles(t,e,n)}setScale(t,e,n){this.entities[0].setLocalScale(t,e,n),this.entities[1].setLocalScale(t,e,n)}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 Lt{constructor(t,e,n,o){this.viewer=t,this.baseUrl=e,this.sceneId=n,this.ownerId=o,this.timeline=[],this.pendingEvents=[],this.sessionStart=Date.now(),this.chunkIndex=0,this.lastPos={x:0,y:0,z:0},this.lastRot={x:0,y:0,z:0},this.cameraIntervalId=null,this.flushIntervalId=null,this.maxDurationMs=18e5,this.destroyed=!1,this.handleCanvasClick=null,this.sessionId=crypto.randomUUID();try{this.visitorId=sessionStorage.getItem("storysplat_visitor_id")||"",this.visitorId||(this.visitorId=crypto.randomUUID(),sessionStorage.setItem("storysplat_visitor_id",this.visitorId))}catch{this.visitorId=crypto.randomUUID()}this.handleVisibility=()=>{"hidden"===document.visibilityState&&this.beaconFlush(!1)},this.handleUnload=()=>this.beaconFlush(!0),this.setupEventListeners(),this.startCameraSampling(),this.startFlushInterval(),document.addEventListener("visibilitychange",this.handleVisibility),window.addEventListener("beforeunload",this.handleUnload),t.on("error",()=>this.destroy())}ts(){return Date.now()-this.sessionStart}quantize(t,e){return Math.round(t/e)*e}startCameraSampling(){this.cameraIntervalId=setInterval(()=>{if(this.destroyed||this.ts()>this.maxDurationMs)return;const t=this.viewer.getPosition(),e=this.viewer.getRotation(),n=Math.abs(t.x-this.lastPos.x)+Math.abs(t.y-this.lastPos.y)+Math.abs(t.z-this.lastPos.z),o=Math.abs(e.x-this.lastRot.x)+Math.abs(e.y-this.lastRot.y)+Math.abs(e.z-this.lastRot.z);n<.1&&o<2||(this.lastPos={...t},this.lastRot={...e},this.timeline.push({t:this.ts(),k:"c",p:[this.quantize(t.x,.01),this.quantize(t.y,.01),this.quantize(t.z,.01)],r:[this.quantize(e.x,.1),this.quantize(e.y,.1),this.quantize(e.z,.1)]}))},1e3)}setupEventListeners(){const t=this.viewer;t.on("waypointChange",t=>{const e=t?.waypoint?.name||t?.name||`waypoint-${t?.index??0}`;this.pendingEvents.push({type:"waypoint",name:e}),this.timeline.push({t:this.ts(),k:"w",n:e,i:t?.index??0})}),t.on("modeChange",t=>{const e=t?.mode||"unknown";this.pendingEvents.push({type:"mode",name:e}),this.timeline.push({t:this.ts(),k:"m",m:e})}),t.on("hotspotClick",t=>{const e=t?.hotspot?.title||t?.hotspot?.name||"unknown";this.pendingEvents.push({type:"hotspot",name:e});const n=t?.hotspot?.position;this.timeline.push({t:this.ts(),k:"h",n:e,id:t?.hotspot?.id||"",p:n?[n.x,n.y,n.z]:[0,0,0]})}),t.on("portalActivated",t=>{const e=t?.targetSceneName||t?.portal?.name||"unknown";this.pendingEvents.push({type:"portal",name:e}),this.timeline.push({t:this.ts(),k:"P",n:e,tid:t?.targetSceneId||t?.portal?.targetSceneId||""})}),t.on("progressUpdate",t=>{void 0!==t?.progress&&this.timeline.push({t:this.ts(),k:"s",v:Math.round(1e3*t.progress)/1e3})}),t.on("playbackStart",()=>{this.timeline.push({t:this.ts(),k:"pb",a:"start"})}),t.on("playbackStop",()=>{this.timeline.push({t:this.ts(),k:"pb",a:"stop"})}),t.on("playbackComplete",()=>{this.timeline.push({t:this.ts(),k:"pb",a:"complete"})});try{this.handleCanvasClick=t=>{this.timeline.push({t:this.ts(),k:"x",sx:t.clientX,sy:t.clientY})},this.viewer.canvas.addEventListener("click",this.handleCanvasClick)}catch{}}startFlushInterval(){this.flushIntervalId=setInterval(()=>this.flush(),3e4)}buildAnalyticsPayload(){const t=Math.round((Date.now()-this.sessionStart)/1e3),e=this.pendingEvents.splice(0,100);return{sceneId:this.sceneId,ownerId:this.ownerId,type:"analytics",visitorId:this.visitorId,sessionDuration:t,events:e}}buildSessionChunkPayload(t){const e=this.timeline.splice(0),n={sceneId:this.sceneId,ownerId:this.ownerId,type:t?"session-end":"session-chunk",sessionId:this.sessionId,visitorId:this.visitorId,chunkIndex:this.chunkIndex,timeline:e};return 0===this.chunkIndex&&(n.meta={device:/Mobi|Android/i.test(navigator.userAgent)?"mobile":"desktop",viewport:{w:window.innerWidth,h:window.innerHeight},userAgent:navigator.userAgent.slice(0,200),initialMode:this.viewer.getCameraMode()}),t&&(n.duration=Math.round((Date.now()-this.sessionStart)/1e3)),this.chunkIndex++,n}async flush(){if(this.destroyed)return;const t=`${this.baseUrl}/api/track-embed`,e={"Content-Type":"application/json"};if(this.pendingEvents.length>0)try{await fetch(t,{method:"POST",headers:e,body:JSON.stringify(this.buildAnalyticsPayload())})}catch{}if(this.timeline.length>0)try{await fetch(t,{method:"POST",headers:e,body:JSON.stringify(this.buildSessionChunkPayload(!1))})}catch{}}beaconFlush(t){if(this.destroyed)return;const e=`${this.baseUrl}/api/track-embed`;try{const t=this.buildAnalyticsPayload();navigator.sendBeacon(e,new Blob([JSON.stringify(t)],{type:"text/plain"}))}catch{}try{const n=this.buildSessionChunkPayload(t);navigator.sendBeacon(e,new Blob([JSON.stringify(n)],{type:"text/plain"}))}catch{}}destroy(){if(!this.destroyed&&(this.beaconFlush(!0),this.destroyed=!0,this.cameraIntervalId&&clearInterval(this.cameraIntervalId),this.flushIntervalId&&clearInterval(this.flushIntervalId),document.removeEventListener("visibilitychange",this.handleVisibility),window.removeEventListener("beforeunload",this.handleUnload),this.handleCanvasClick))try{this.viewer.canvas.removeEventListener("click",this.handleCanvasClick)}catch{}}}function kt(t,e,n,o){const i=new Lt(t,e,n,o);return{destroy:()=>i.destroy()}}class zt{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))}listenerCount(t){return this.listeners.get(t)?.size||0}}function Rt(){const t=navigator.userAgent||navigator.vendor||window.opera||"";return/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(t)}const Dt={"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 It(t){const e=t.includes("lod-meta.json");return e&&console.log("[SPLAT] Detected LOD streaming format (lod-meta.json)"),e}function Ft(e,n,o={}){if(console.log("[StorySplat Viewer] BUILD 2025-01-22-orbit-fly-v3"),o.lazyLoad??n.uiOptions?.lazyLoad){const t=new zt;let i,s=null;const a=o.lazyLoadThumbnail||n.uiOptions?.lazyLoadThumbnailUrl||n.thumbnailUrl,r=o.lazyLoadButtonText||n.uiOptions?.lazyLoadButtonText||l(n.uiOptions?.buttonLabels,"startExperience"),c=n.uiColor||"#CC5833";console.log("[StorySplat Viewer] Lazy loading enabled, showing start button"),function(t,e){const{thumbnailUrl:n,thumbnailType:o,buttonText:i="Start Experience",uiColor:s="#4CAF50",onStart:a}=e;u(s),t.classList.add("storysplat-viewer-container");const r=document.createElement("div");r.className="storysplat-lazy-load-container";const l=(n?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"}(n):null)||o||"image";let c="";n&&(c+="video"===l?`<video class="storysplat-lazy-load-thumbnail-video" src="${n}" autoplay muted loop playsinline webkit-playsinline></video>`:`<img class="storysplat-lazy-load-thumbnail" src="${n}" alt="Scene preview" />`),c+='<div class="storysplat-lazy-load-overlay"></div>',c+=`\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=c,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:o.lazyLoadThumbnailType||n.uiOptions?.lazyLoadThumbnailType,buttonText:r,uiColor:c,onStart:()=>{console.log("[StorySplat Viewer] User clicked start, initializing viewer..."),s=Ft(e,n,{...o,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",e=>t.emit("loaded",e)),s.on("progress",e=>t.emit("progress",e)),s.on("modeChange",e=>t.emit("modeChange",e)),s.on("hotspotClick",e=>t.emit("hotspotClick",e)),s.on("portalActivated",e=>t.emit("portalActivated",e)),s.on("progressUpdate",e=>t.emit("progressUpdate",e)),s.on("playbackComplete",()=>t.emit("playbackComplete"))}});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,n)=>s?.setPosition(t,e,n),setRotation:(t,e,n)=>s?.setRotation(t,e,n),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,n)=>t.on(e,n),off:(e,n)=>t.off(e,n)}}const i=new zt;if(!o.allowParentStyles){const t=e.style.width,n=e.style.height;e.style.all="initial",e.style.position="relative",e.style.display="block",e.style.width=t||"100%",e.style.height=n||"100%",e.style.overflow="hidden",e.style.fontFamily="system-ui, -apple-system, sans-serif"}const c=!0===o.editor;c&&e.setAttribute("data-storysplat-editor","true"),i.on("error",t=>{console.error("[StorySplat Viewer] Error event:",t.message),c||function(t,e){if(t.hasAttribute("data-storysplat-editor"))return;t.querySelector(".storysplat-error-popup")?.remove();const n=t.querySelector(".storysplat-preloader");n&&g(n);const o=e.includes("Failed to load splat from any URL"),i=document.createElement("div");i.className="storysplat-error-popup",i.innerHTML=o?'\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 d=a(s(n));console.log("[StorySplat Viewer] Creating viewer with config:",d),console.log("[StorySplat Viewer] Scale config:",d.scale),console.log("[StorySplat Viewer] Raw scene data:",{splatScale:n.splatScale,scale:n.scale});let p=null;const h=c?!o.editorSkipUI&&!1!==o.showUI:!1!==o.showUI,m=d.uiColor||"#CC5833",T=d.uiOptions||{},A=o.template||T.uiType||"minimal";let P=T.buttonLabels;const L=t=>"first-person"===t?"tour":"drone"===t?"explore":t,k=d.collisionMeshesData&&d.collisionMeshesData.length>0||!!d.voxelCollisionUrl;let z=(d.allowedCameraModes||["orbit","first-person","drone"]).map(L).filter((t,e,n)=>n.indexOf(t)===e);k&&!z.includes("walk")&&z.push("walk"),!k&&z.includes("walk")&&(z=z.filter(t=>"walk"!==t));let D=L(d.defaultCameraMode||"orbit");z.includes(D)||(D=z[0]||"tour");const I=!!(d.audioEmitters?.length||d.hotspots?.some(t=>t.audioUrl)||d.hotspots?.some(t=>"video"===t.type&&!0!==t.videoMuted)||d.waypoints?.some(t=>t.interactions?.some(t=>"audio"===t.type)));let F={};h&&(F=f(e,d,{uiColor:m,showScrollControls:!T.hideNavigator&&d.waypoints&&d.waypoints.length>0,showModeToggle:z.length>1,showFullscreenButton:!T.hideFullscreenButton,showHelpButton:!T.hideHelpButton&&!T.hideInfoButton,showMuteButton:!T.hideMuteButton&&I,showPreloader:!0,allowedCameraModes:z,defaultCameraMode:D,buttonLabels:P,customPreloaderLogoUrl:T.customPreloaderLogoUrl,hideWatermark:T.hideWatermark,watermarkText:T.watermarkText,watermarkLink:T.watermarkLink,sceneId:n.sceneId,showWaypointList:T.showWaypointList,template:A,debugMode:T.debugMode,hideProgressText:T.hideProgressText,viewerTheme:T.viewerTheme,showRelightingToggle:!!(d.splatRelighting?.allowViewerToggle&&d.lights&&d.lights.length>0),showSceneMenu:!!(d.portals&&d.portals.length>0)}),M(F));const B=document.createElement("canvas");let U;B.id="storysplat-viewer-canvas",B.style.width="100%",B.style.height="100%",B.style.display="block",e.appendChild(B);const W={antialias:!1,alpha:!1,powerPreference:"high-performance"};try{U=new t.Application(B,{graphicsDeviceOptions:W,mouse:new t.Mouse(B),touch:new t.TouchDevice(B),keyboard:new t.Keyboard(window)}),console.log("[StorySplat Viewer] Graphics initialized successfully")}catch(n){console.warn("[StorySplat Viewer] WebGL2 initialization failed, trying WebGL1 fallback:",n);try{U=new t.Application(B,{graphicsDeviceOptions:{...W,preferWebGl2:!1},mouse:new t.Mouse(B),touch:new t.TouchDevice(B),keyboard:new t.Keyboard(window)}),console.log("[StorySplat Viewer] WebGL1 fallback successful")}catch(t){console.error("[StorySplat Viewer] WebGL initialization failed completely:",t);const n=document.createElement("div");n.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 o=document.createElement("h3");o.style.cssText="margin:0 0 10px 0;",o.textContent=l(P,"errorWebGLTitle");const i=document.createElement("p");throw i.style.cssText="margin:0;",i.textContent=l(P,"errorWebGLMessage"),n.appendChild(o),n.appendChild(i),e.appendChild(n),new Error("WebGL initialization failed - browser may not support WebGL")}}B.addEventListener("webglcontextlost",t=>{t.preventDefault(),console.error("[StorySplat Viewer] WebGL context lost"),i.emit("error",new Error("WebGL context lost"))},!1),B.addEventListener("webglcontextrestored",()=>{console.log("[StorySplat Viewer] WebGL context restored")},!1),U.setCanvasFillMode(t.FILLMODE_FILL_WINDOW),U.setCanvasResolution(t.RESOLUTION_AUTO),U.scene.ambientLight=new t.Color(0,0,0),U.start(),console.log("[StorySplat Viewer] App started");try{const t=U.scene.layers.getLayerByName("World"),e=t&&Object.getPrototypeOf(t);if(e&&!e._splitLightsPatched){const t=Object.getOwnPropertyDescriptor(e,"splitLights");if(t?.get){const n=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)),n.call(this)},configurable:!0}),e._splitLightsPatched=!0}}}catch(t){console.warn("[StorySplat Viewer] Could not patch Layer.splitLights:",t)}const N=Rt(),G=d.lodSettings;let H,q,j;if(G&&"auto"!==G.preset)if("custom"===G.preset)q=[G.lodRangeMin??0,G.lodRangeMax??5],j=G.lodDistances??[15,30,80,250,300],H="desktop",console.log("[SPLAT] Using custom LOD settings from scene");else{H=G.preset;const t=Dt[H];q=[...t.range],j=t.lodDistances,console.log("[SPLAT] Using scene-configured LOD preset:",H)}else{N&&G?.mobilePreset?(H=G.mobilePreset,console.log("[SPLAT] Using scene mobile override preset:",H)):!N&&G?.desktopPreset?(H=G.desktopPreset,console.log("[SPLAT] Using scene desktop override preset:",H)):H=function(t){const e=navigator.deviceMemory,n=navigator.hardwareConcurrency||4,o=window.devicePixelRatio||1;let i;i=t?null!=e&&e>=6&&n>=6||null==e&&n>=6&&o>=3?"mobile-max":"mobile":null!=e&&e>=8&&n>=8||null==e&&n>=8?"desktop-max":"desktop";return console.log("[SPLAT] Auto LOD preset resolved:",i,{memory:e,cores:n,dpr:o,isMobile:t}),i}(N);const t=Dt[H];q=[...t.range],j=t.lodDistances}console.log("[SPLAT] Initializing LOD system for device:",N?"mobile":"desktop"),U.scene.gsplat?(U.scene.gsplat.lodUpdateAngle=90,U.scene.gsplat.lodBehindPenalty=2,U.scene.gsplat.radialSorting=!0,U.scene.gsplat.lodUpdateDistance=1,U.scene.gsplat.lodUnderfillLimit=10,U.scene.gsplat.lodRangeMin=q[0],U.scene.gsplat.lodRangeMax=q[1],U.scene.gsplat.colorUpdateDistance=1,U.scene.gsplat.colorUpdateAngle=4,U.scene.gsplat.colorUpdateDistanceLodScale=2,U.scene.gsplat.colorUpdateAngleLodScale=2,console.log("[SPLAT] LOD system configured:",{preset:G?.preset??"auto",lodRangeMin:q[0],lodRangeMax:q[1],lodDistances:j,lodUpdateAngle:90,lodUpdateDistance:1,isMobile:N})):console.warn("[SPLAT] GSplat scene settings not available - LOD may not work optimally");let K=0,tt=!1,et=null,ot=null,it=null;const st=new Set;let at=!1,rt=!1,lt=!1,ct=null;const dt=d.additionalSplats||[],pt=d.keepMeshesInMemory??!1,ht=d.initialSplatExploreMode||"fly";let ut=null,mt=!1;const gt=new Map;let ft=-1,yt=-1,xt=0;const bt=new t.Entity("camera");let wt=new t.Color(.1,.1,.1);if(d.backgroundColor){const e=d.backgroundColor.replace("#","");if(6===e.length){const n=parseInt(e.substring(0,2),16)/255,o=parseInt(e.substring(2,4),16)/255,i=parseInt(e.substring(4,6),16)/255;wt=new t.Color(n,o,i),console.log("[StorySplat Viewer] Background color set from config:",d.backgroundColor)}}bt.addComponent("camera",{clearColor:wt,fov:d.fov||60,nearClip:d.nearClip||.1,farClip:d.farClip||1e4}),bt.addComponent("audiolistener"),console.log("[StorySplat Viewer] Camera settings:",{fov:d.fov,nearClip:d.nearClip,farClip:d.farClip,playerHeight:d.playerHeight});const _t=d.playerHeight||1.6;if(d.waypoints&&d.waypoints.length>0){const t=d.waypoints[0];if(console.log("[StorySplat Viewer] First waypoint raw:",t),t.position){const e=t.position;bt.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 bt.setPosition(0,_t,5);if(t.rotation){const e=ke(t.rotation);bt.setRotation(e),console.log("[StorySplat Viewer] Camera rotation set from waypoint")}}else bt.setPosition(0,_t,5),bt.lookAt(new t.Vec3(0,0,0)),console.log("[StorySplat Viewer] Camera set to default position (0, playerHeight, 5)");U.root.addChild(bt);const Mt=5*(d.cameraMovementSpeed??1),Et=new R(bt,U,{moveSpeed:Mt,moveFastSpeed:2.5*Mt,moveSlowSpeed:.5*Mt,rotateSpeed:800/(d.cameraRotationSensitivity||4e3),enableOrbit:!0,enableFly:!0,enablePan:!0,invertRotation:d.invertCameraRotation,moveDamping:d.cameraDamping??.75,rotateDamping:d.cameraDamping??.75,zoomDamping:.8});let Ct=null;const Tt=d.collisionMeshesData&&d.collisionMeshesData.length>0;if(Tt||!!d.voxelCollisionUrl){const t=null!=d.walkSpeed?d.walkSpeed:Mt;Ct=new $(bt,U,{moveSpeed:t,sprintMultiplier:2,lookSensitivity:1/(d.cameraRotationSensitivity||4e3)*10,playerHeight:d.playerHeight||1.6,gravity:20,jumpVelocity:8,collisionRadius:.3,stepHeight:.3}),Tt&&Ct.createCollisionMeshes(d.collisionMeshesData).then(()=>{console.log("[StorySplat Viewer] Collision meshes loaded for walk mode"),Et.setCollisionEntities(Ct.collisionMeshEntities)}).catch(t=>{console.error("[StorySplat Viewer] Failed to load collision meshes:",t)}),d.voxelCollisionUrl&&Ct.initVoxelCollision(d.voxelCollisionUrl).then(()=>{const t=Ct.voxelCollisionInstance;t&&Et.setVoxelCollision(t,et)}).catch(t=>{console.warn("[StorySplat Viewer] Voxel collision init failed:",t)})}Ct&&null!=d.headBobEnabled&&(Ct.headBobEnabled=d.headBobEnabled),null!=d.doubleTapMoveSpeed&&(Et.autoMoveSpeedFactor=d.doubleTapMoveSpeed,Ct&&(Ct.autoMoveSpeedFactor=d.doubleTapMoveSpeed));let Lt=D,Bt=!1;function $t(t){Bt=t,"walk"===Lt&&Ct?Ct.autoMoveForward=t:"explore"===Lt&&(Et.autoMoveForward=t)}function Vt(){Bt&&$t(!1)}let Ot=!0;"tour"===D&&Et.disable();const Wt=new t.Picker(U,1,1,!0),Nt=new t.Picker(U,1,1,!0),Gt=new t.Layer({name:"Reticle"});U.scene.layers.push(Gt);const Ht=bt.camera.layers;bt.camera.layers=[...Ht,Gt.id];const Xt=new t.Entity("reticle"),qt=new t.StandardMaterial;qt.emissive=new t.Color(1,1,1),qt.diffuse=new t.Color(0,0,0),qt.useLighting=!1,qt.blendType=t.BLEND_NORMAL,qt.opacity=1,qt.depthTest=!1,qt.depthWrite=!1,qt.cull=t.CULLFACE_NONE,qt.update();const jt=new t.TorusGeometry({tubeRadius:.048,ringRadius:.193,segments:24,sides:10}),Yt=t.Mesh.fromGeometry(U.graphicsDevice,jt),Zt=new t.MeshInstance(Yt,qt);Zt.drawOrder=9999,Xt.addComponent("render",{meshInstances:[Zt],castShadows:!1,layers:[Gt.id]}),Xt.enabled=!1,U.root.addChild(Xt);let Qt=0,Kt=0,Jt=new t.Vec3,te=new t.Vec3,ee=!1,ne=0,oe=!1,ie=new t.Vec3(0,1,0),se=new t.Vec3(0,1,0);const ae=new t.Quat,re=new t.Vec3,le=new t.Vec3,ce=new t.Vec3,de=new t.Vec3,pe=new t.Vec3;let he=!0,ue=!1,me=0;let ge=!1,fe=null;let ye=null,ve=null,xe=!1;function be(){ye&&(ye.enabled=!1,xe=!1)}let we=0;U.on("update",e=>{var n,o;F.fpsCounter&&(we+=e,we>=.5&&(we=0,o=1/e,(n=F).fpsCounter&&(n.fpsCounter.textContent=`${Math.round(o)} FPS`))),"walk"===Lt&&Ct?Ct.update(e):Et.update(e),Ot||function(){const t=bt.getPosition();Bn.forEach(e=>{const n=e.hotspotData;if(!n)return;if("proximity"!==(e.mediaTriggerMode||"click"))return;const o=e.getPosition(),i=t.distance(o)<=(e.proximityDistance||5),s=e.wasInProximity||!1;if(i&&!s){if("video"===n.type&&e.videoElement)pi(e,n);else if("gif"===n.type)e.enabled=!0;else if(("sphere"===n.type||"image"===n.type)&&e.audioElements){const t=e.audioElements,o=t.audio;o&&o.paused&&(t.audioCtx&&"suspended"===t.audioCtx.state&&t.audioCtx.resume(),o.play().catch(t=>console.error("[Audio] Hotspot audio play failed (proximity):",t)),console.log("[Audio] Hotspot audio started (proximity):",n.title))}e.wasInProximity=!0}else if(!i&&s){if("video"===n.type&&e.videoElement)hi(e);else if("gif"===n.type)e.enabled=!1;else if(("sphere"===n.type||"image"===n.type)&&e.audioElements){const t=e.audioElements.audio;t&&!t.paused&&(t.pause(),console.log("[Audio] Hotspot audio stopped (proximity):",n.title))}e.wasInProximity=!1}})}(),function(){if(0===qn.size)return;const t=bt.getPosition();qn.forEach((e,n)=>{const{entity:o,config:i,slotId:s,assetReady:a,playing:r}=e;if(!a)return;const l=o.sound?.slot(s);if(l&&i.spatialSound){const s=o.getPosition(),a=t.distance(s),c=i.maxDistance||20;a<=c&&!r?(console.log(`[Audio] Proximity play: ${n}, distance=${a.toFixed(2)}, maxDistance=${c}`),l.play(),e.playing=!0):a>c&&r&&i.stopOnExit&&(console.log(`[Audio] Proximity stop: ${n}, distance=${a.toFixed(2)}`),l.stop(),e.playing=!1)}})}(),function(){if(0===ei.size)return;const t=bt.getPosition();ei.forEach((e,n)=>{const{entity:o,config:i,slotId:s,assetReady:a,playing:r}=e;if(!a)return;const l=o.sound?.slot(s);if(l&&!1!==i.spatialSound){const n=o.getPosition();t.distance(n)<=(i.maxDistance||100)&&!r&&!1!==i.autoplay&&(l.isPlaying||(l.play(),e.playing=!0))}})}(),function(){if(!d.waypoints?.length)return;const e=bt.getPosition();d.waypoints.forEach((n,o)=>{const i=new t.Vec3(n.position?.x??0,n.position?.y??0,-(n.position?.z??0)),s=e.distance(i),a=n.triggerDistance??1;s<=a?ni.has(o)||(ni.add(o),console.log(`[StorySplat] Waypoint ${o} 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 n=qn.get(e);if(n&&n.assetReady&&!n.playing){const t=n.entity.sound?.slot(n.slotId);t&&(t.play(),n.playing=!0)}}})}(n)):ni.has(o)&&(ni.delete(o),console.log(`[StorySplat] Waypoint ${o} 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 n=qn.get(e);if(n&&n.playing){const t=n.entity.sound?.slot(n.slotId);t&&(t.stop(),n.playing=!1)}}}})}(n))})}(),ge&&xe&&function(){if(ye?.enabled&&bt){const t=bt.forward.clone(),e=bt.getPosition(),n=e.clone().add(t.mulScalar(1.5));n.y-=.1,ye.setPosition(n),ye.lookAt(e),ye.rotateLocal(90,0,0)}}(),function(t){if(0===Bo.length)return;const e=[];for(let n=0;n<Bo.length;n++){const o=Bo[n];if(o.age+=t,o.age>=o.lifetime){e.push(n);continue}o.velocity.y-=Vo*t,o.velocity.x*=1-.3*t,o.velocity.z*=1-.3*t;const i=o.entity.getPosition().clone();i.x+=o.velocity.x*t,i.y+=o.velocity.y*t,i.z+=o.velocity.z*t;const s=jo(i,o.radius);if(null!==s){const t=s+o.radius;i.y<t&&o.velocity.y<0&&(i.y=t,o.velocity.y=-o.velocity.y*Oo,Math.abs(o.velocity.y)<.3&&(o.velocity.y=0))}Yo(i,o.velocity,o.radius),o.entity.setPosition(i.x,i.y,i.z);const a=o.lifetime-o.age;a<2&&o.entity.light&&(o.entity.light.intensity=o.baseIntensity*(a/2))}for(let t=e.length-1;t>=0;t--)Zo(Bo[e[t]]),Bo.splice(e[t],1)}(e),(!he||N||"explore"!==Lt&&"walk"!==Lt)&&(ee=!1),ue&&(ee=!1),wi&&(ee=!1);const i=ee?1:0;if(me+=(i-me)*Math.min(1,8*e),Math.abs(me-i)<.01&&(me=i),me>0){te.distance(Jt)<.001?te.copy(Jt):te.lerp(te,Jt,Math.min(1,15*e)),se.lerp(se,ie,Math.min(1,10*e)),se.normalize();const n=bt.getPosition().distance(te),o=Math.max(.24,.12*n);pe.copy(te).addScaled(se,.01),Xt.setPosition(pe),Xt.setLocalScale(o,.45*o,o),qt.opacity=me,qt.emissive.set(1,1,1),qt.update(),ae.setFromDirections(t.Vec3.UP,se),Xt.setRotation(ae),Xt.enabled=!0}else Xt.enabled=!1;at&&st.size>0&&(!function(){if(0===st.size)return;Co.length=0;for(const t of xo){if(!t.enabled||!t.light)continue;const e=t.light,n=t.getPosition(),o=e.type,i="directional"===o,s=i?0:e.range||10;Po||console.log("[Relighting] Light:",t.name,"rawType:",o,"(typeof:",typeof o,")","isDir:",i,"color:",e.color.r.toFixed(3),e.color.g.toFixed(3),e.color.b.toFixed(3),"intensity:",e.intensity,"range:",s,"pos:",n.x.toFixed(2),n.y.toFixed(2),n.z.toFixed(2));const a="spot"===o,r=a?t.forward:void 0;Co.push({position:n,color:e.color,intensity:e.intensity,range:s,direction:a&&r?{x:r.x,y:r.y,z:r.z}:void 0,cosInnerAngle:a?Math.cos((e.innerConeAngle??36)*Math.PI/180):void 0,cosOuterAngle:a?Math.cos((e.outerConeAngle??45)*Math.PI/180):void 0})}!Po&&Co.length>0&&(Po=!0,console.log("[Relighting] Total lights synced:",Co.length));for(const t of st)t.enabled&&t.updateLights(Co)}(),!rt&&it&&(rt=!0,console.log("[StorySplat Viewer] Relighting sync active, lights:",Co.length,"fade:",it.relightFade,"ambient:",[it.ambientR,it.ambientG,it.ambientB],"material:",!!it.material,"totalScripts:",st.size)))}),U.on("postrender",()=>{const e=he&&!N&&("explore"===Lt||"walk"===Lt),n=U.graphicsDevice.canvas;if(!e||oe||!n)return;if(ne++,ne<4)return;ne=0,oe=!0;const o=.25,i=Math.max(1,Math.floor(n.clientWidth*o)),s=Math.max(1,Math.floor(n.clientHeight*o)),a=!!document.pointerLockElement,r=a?Math.floor(.5*n.clientWidth*o):Math.floor(Qt*o),l=a?Math.floor(.5*n.clientHeight*o):Math.floor(Kt*o);try{Nt.resize(i,s);const e=U.scene.layers.getLayerByName("World");if(!e)return void(oe=!1);Nt.prepare(bt.camera,U.scene,[e]);const n=4,o=Math.min(r+n,i-1),a=Math.max(r-n,0),c=Math.min(l+n,s-1),d=Math.max(l-n,0);Promise.all([Nt.getWorldPointAsync(r,l),Nt.getWorldPointAsync(o,l),Nt.getWorldPointAsync(r,c),Nt.getWorldPointAsync(a,l),Nt.getWorldPointAsync(r,d)]).then(e=>{if(oe=!1,lt)return;const n=bt.getPosition(),o=t=>{if(!(t&&isFinite(t.x)&&isFinite(t.y)&&isFinite(t.z)))return!1;const e=n.distance(t);return e>.1&&e<500};let i=null;if(o(e[0]))i=e[0];else for(let t=1;t<e.length;t++)if(o(e[t])){i=e[t];break}if(i){Jt.copy(i);let s=!1;const a=e.filter((t,e)=>t!==i&&o(t));a.length>=2&&(re.sub2(a[0],i),le.sub2(a[1],i),re.lengthSq()>1e-8&&le.lengthSq()>1e-8&&(ce.cross(re,le).normalize(),de.sub2(n,i),ce.dot(de)<0&&ce.mulScalar(-1),ce.lengthSq()>.5&&(ie.copy(ce),s=!0))),!s&&ie.lengthSq()<.5&&ie.copy(t.Vec3.UP),ee=!0}}).catch(()=>{oe=!1})}catch(t){oe=!1}});const Se=60;U.on("joystick:left",(t,e,n,o)=>{if(t<0||e<0)b(F,!1,0,0,Se),"walk"===Lt&&Ct&&Ct.setJoystickMove(0,0);else{const i=n-t,s=o-e;b(F,!0,i,s,Se),"walk"===Lt&&Ct&&Ct.setJoystickMove(i/Se,-s/Se)}}),U.on("joystick:right",(t,e,n,o)=>{t<0||e<0?(w(F,!1),"walk"===Lt&&Ct&&Ct.setJoystickLook(0,0)):(w(F,!0),"walk"===Lt&&Ct&&Ct.setJoystickLook((n-t)/Se,(o-e)/Se))});const _e=F.scrollControls?.querySelectorAll(".storysplat-explore-btn");let Me=!1;const Ee=t=>{_e?.forEach(e=>{const n=e.getAttribute("data-explore-mode");e.classList.toggle("selected",n===t)})};function Ce(e){Vt(),"tour"===Lt&&"tour"!==e&&tt&&In(),Xt.enabled=!1,ee=!1,me=0,"walk"===Lt&&Ct?Ct.disable():"explore"===Lt&&Et.disable(),Lt=e,console.log("[StorySplat Viewer] Switching to mode:",e);const n=Rt();if("walk"===e&&Ct)Ot=!1,Et.disable(),n?(Et.detachInputSources(),Et.reattachMobileInput()):Et.detachInputSources(),Ct.enable(),Ee("walk"),n&&x(F,!0),S(F,!1),_(F,!1),E(F);else if("explore"===e){Ct&&Ct.disable(),un=0,mn=0,gn=!1,Ot=!1,Et.disable(),Et.enable(),Et.enableOrbit=!0,Et.enableFly=!0,Et.enablePan=!0;const t=ht,e=bt.getPosition().clone(),o=bt.getRotation().clone();Et.syncFromPose(e,o),console.log("[StorySplat Viewer] Synced camera from actual camera state for explore mode");(async()=>{try{const t=.25;Wt.resize(Math.floor(vi.clientWidth*t),Math.floor(vi.clientHeight*t));const e=U.scene.layers.getLayerByName("World");if(e){Wt.prepare(bt.camera,U.scene,[e]);const n=Math.floor(.5*vi.clientWidth*t),o=Math.floor(.5*vi.clientHeight*t),i=await Wt.getWorldPointAsync(n,o);if(i){const t=bt.getPosition().distance(i);t>.5&&t<500&&(Et.setFocusDistance(i,t),console.log("[StorySplat Viewer] Updated focus distance:",t.toFixed(2)))}}}catch(t){}})();const i=t;Et.setMode(i),Ee(i),n?x(F,"fly"===i):(S(F,"fly"===i),_(F,"orbit"===i))}else Et.enable(),Et.disable(),Ct&&Ct.disable(),Ot=!0,0===xn.length&&d.waypoints&&d.waypoints.length>0&&(d.waypoints.forEach(e=>{const n=e.position?new t.Vec3(e.position.x,e.position.y,-e.position.z):new t.Vec3(0,d.playerHeight||1.6,0);xn.push(n);const o=e.rotation?ke(e.rotation):new t.Quat;bn.push(o);let i=e.fov||Sn;i<3.5&&(i*=180/Math.PI),wn.push(i)}),console.log("[StorySplat Viewer] Rebuilt waypoint arrays from config:",xn.length)),Mn(Je),dn&&pn&&(bt.setPosition(dn),bt.setRotation(pn),bt.camera&&wn.length>0&&(bt.camera.fov=_n)),x(F,!1),S(F,!1),_(F,!1),E(F);i.emit("modeChange",{mode:e})}_e?.forEach(t=>{t.addEventListener("click",()=>{if("explore"!==Lt&&"walk"!==Lt)return;const e=t.getAttribute("data-explore-mode");"walk"===e?("walk"!==Lt&&Ce("walk"),Ee("walk")):("walk"===Lt&&Ce("explore"),Et.setMode(e),Ee(e),N?x(F,"fly"===e):(S(F,"fly"===e),_(F,"orbit"===e)),E(F))})}),U.on("cameracontrols:modechange",e=>{if("orbit"!==e&&"fly"!==e||(Ee(e),"explore"!==Lt&&"walk"!==Lt||(N?x(F,"fly"===e):(S(F,"fly"===e),_(F,"orbit"===e)),E(F))),"orbit"===e){let e=!1;if(ee&&Jt){const n=bt.getPosition(),o=n.distance(Jt);if(o>.3&&o<500){const i=bt.forward,s=new t.Vec3(n.x+i.x*o,n.y+i.y*o,n.z+i.z*o);Et.syncFromCamera(s),e=!0}}if(!e)try{const t=.25,e=Math.floor(.5*vi.clientWidth*t),n=Math.floor(.5*vi.clientHeight*t);Wt.resize(Math.floor(vi.clientWidth*t),Math.floor(vi.clientHeight*t));const o=U.scene.layers.getLayerByName("World");o&&(Wt.prepare(bt.camera,U.scene,[o]),Wt.getWorldPointAsync(e,n).then(t=>{if(!lt&&t&&isFinite(t.x)&&isFinite(t.y)&&isFinite(t.z)){const e=bt.getPosition().distance(t);e>.3&&e<500&&Et.syncFromCamera(t)}}).catch(()=>{}))}catch(t){}}});const Te=(t,e)=>{F.preloader&&function(t,e,n,o){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=n||`${o||r.loading} ${Math.round(a)}%`)}(F.preloader,t,e,l(P,"loading")),i.emit("progress",{progress:t,text:e})};function Ae(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 Pe(e,n){const o=new t.Entity("splat");o.addComponent("gsplat",{asset:e,unified:!0});const i=o.gsplat;i&&It(n)&&(i.lodDistances=[...j]);const s=d.scale||{x:1,y:1,z:1},a=d.invertXScale||!1,r=d.invertYScale||!1,l={x:a?-s.x:s.x,y:r?s.y:-s.y,z:a!==r?s.z:-s.z};o.setLocalScale(l.x,l.y,l.z);const c=d.position||[0,0,0];o.setPosition(c[0],c[1],-c[2]);const p=d.rotation||[0,0,0],h=[p[0]*(180/Math.PI),p[1]*(180/Math.PI),-p[2]*(180/Math.PI)];return o.setEulerAngles(h[0],h[1],h[2]),U.root.addChild(o),o.gsplat?.material&&o.gsplat.material.setParameter("alphaClip",.01),o}function Le(t,e){if(U.assets.add(t),e.includes("lod-meta.json")){const n=U.loader.getHandler("gsplat"),o=n?.parsers?.octree;o?o.load({load:e,original:e},(e,n)=>{e?t.fire("error",e):(t.resource=n,t.loaded=!0,t.fire("load",t))},t):U.assets.load(t)}else U.assets.load(t)}function ke(e){if("_w"in e||"w"in e){const n=e._x??e.x??0,o=e._y??e.y??0,i=e._z??e.z??0,s=e._w??e.w??1;return new t.Quat(-n,-o,i,s)}if("x"in e&&"y"in e&&"z"in e){const n=new t.Quat;return n.setFromEulerAngles(e.x||0,e.y||0,e.z||0),n}return new t.Quat}const ze=nt(o.revealEffect||n.revealEffect||d.revealEffect||"none",o.revealStyle||n.revealStyle||d.revealStyle||"bloom"),Re=.75,De=1.5,Ie=50,Fe=-50,Be=1;function Ue(e,n,o,i){if(!ze)return void(i&&i());e.script||e.addComponent("script");let s=e.script?.gsplatWipeTransition;if(!s){const n=function(){if(Z)return Z;const e=t.createScript("gsplatWipeTransition");return Object.assign(e.prototype,{effectTime:0,material:null,_effectInitialized:!1,_shadersNeedApplication:!1,_edgeTintArray:[0,0,0],mode:1,duration:1.5,bandWidth:3,wipeTop:30,wipeBottom:-30,edgeTint:null,onComplete:null,initialize(){this.effectTime=0,this.material=null,this._effectInitialized=!1,this._shadersNeedApplication=!1,this._edgeTintArray=[0,0,0],this.edgeTint||(this.edgeTint=new t.Color(.2,.6,1)),this.on("enable",()=>{this.effectTime=0,this._effectInitialized?this._applyShaders():this._shadersNeedApplication=!0}),this.on("disable",()=>{this._removeShaders()}),this.entity.gsplat&&(this._effectInitialized=!0,this.enabled&&this._applyShaders())},update(t){if(this._effectInitialized){if(this._shadersNeedApplication&&(this._applyShaders(),this.material&&(this._shadersNeedApplication=!1)),this.material){if(this.effectTime+=t,Math.min(this.effectTime/this.duration,1)>=1){const t=this.entity.script&&this.entity.script.gsplatRelighting;if(t&&t.enabled){if(this.onComplete){const t=this.onComplete;this.onComplete=null,t()}return}const e=this.onComplete;return this.enabled=!1,void(e&&e())}this._updateUniforms(),this.material.update()}}else this.entity.gsplat&&(this._effectInitialized=!0,this.enabled&&this._shadersNeedApplication&&(this._applyShaders(),this.material&&(this._shadersNeedApplication=!1)))},_updateUniforms(){const t=Math.min(this.effectTime/this.duration,1);this._setUniform("uWipeProgress",t),this._setUniform("uWipeTop",this.wipeTop),this._setUniform("uWipeBottom",this.wipeBottom),this._setUniform("uBandWidth",this.bandWidth),this._setUniform("uMode",this.mode),this._setUniform("uTime",this.effectTime),this._edgeTintArray[0]=this.edgeTint.r,this._edgeTintArray[1]=this.edgeTint.g,this._edgeTintArray[2]=this.edgeTint.b,this._setUniform("uEdgeTint",this._edgeTintArray)},getShaderGLSL:()=>'\nuniform float uWipeProgress; // 0.0 = start, 1.0 = fully wiped\nuniform float uWipeTop; // Y coordinate of scene top\nuniform float uWipeBottom; // Y coordinate of scene bottom\nuniform float uBandWidth; // Transition band width in world units\nuniform float uMode; // 1.0 = reveal (show from top), -1.0 = hide (hide from top)\nuniform vec3 uEdgeTint; // Color tint at the wipe edge\nuniform float uTime; // Animation time for edge effects\n\n// Per-splat shared state\nfloat g_visibility; // 0 = hidden, 1 = visible\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 initWipe(vec3 center) {\n float wipeY = mix(uWipeTop, uWipeBottom, uWipeProgress);\n\n // Subtle per-splat noise for organic edge (kept small to avoid dead areas between two splats)\n float noise = (hash(center) - 0.5) * uBandWidth * 0.15;\n float threshold = wipeY + noise;\n float halfBand = uBandWidth * 0.5;\n\n // Compute signed distance from threshold, accounting for sweep direction.\n // For downward sweep (uWipeTop > uWipeBottom): positive = above threshold (already passed).\n // For upward sweep (uWipeTop < uWipeBottom): flip so positive = below threshold (already passed).\n float sweepDir = sign(uWipeTop - uWipeBottom);\n float dist = (center.y - threshold) * sweepDir;\n float raw = smoothstep(-halfBand, halfBand, dist);\n\n if (uMode > 0.0) {\n // Reveal: splats in the "already passed" region are visible\n g_visibility = raw;\n } else {\n // Hide: splats in the "not yet reached" region are visible\n g_visibility = 1.0 - raw;\n }\n}\n\nvoid modifySplatCenter(inout vec3 center) {\n initWipe(center);\n // No position modification for clean wipe\n}\n\nvoid modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) {\n if (g_visibility < 0.01) {\n // Fully hidden\n scale = vec3(0.0);\n return;\n }\n\n if (g_visibility >= 0.99) {\n // Fully visible - keep original\n return;\n }\n\n // Transition band: shrink splats toward the edge\n float origSize = gsplatGetSizeFromScale(scale);\n float t = g_visibility;\n\n if (t < 0.3) {\n // Near-invisible: tiny spherical dots\n float dotSize = min(t * 0.15 * origSize, origSize);\n gsplatMakeSpherical(scale, dotSize);\n } else {\n // Partial visibility: scale down proportionally\n scale *= t;\n }\n}\n\nvoid wipeColorEffect(vec3 center, inout vec4 color) {\n if (g_visibility < 0.01 || g_visibility >= 0.99) return;\n\n // Tint at the wipe edge - strongest at the middle of the band\n float edgeIntensity = sin(g_visibility * 3.14159);\n color.rgb += uEdgeTint * edgeIntensity * 0.4;\n}\n\nvoid modifySplatColor(vec3 center, inout vec4 color) {\n wipeColorEffect(center, color);\n}\n',getShaderWGSL:()=>"\nuniform uWipeProgress: f32;\nuniform uWipeTop: f32;\nuniform uWipeBottom: f32;\nuniform uBandWidth: f32;\nuniform uMode: f32;\nuniform uEdgeTint: vec3f;\nuniform uTime: f32;\n\nvar<private> g_visibility: f32;\n\nfn hash(p: vec3f) -> f32 {\n return fract(sin(dot(p, vec3f(127.1, 311.7, 74.7))) * 43758.5453);\n}\n\nfn initWipe(center: vec3f) {\n let wipeY = mix(uniform.uWipeTop, uniform.uWipeBottom, uniform.uWipeProgress);\n let noise = (hash(center) - 0.5) * uniform.uBandWidth * 0.15;\n let threshold = wipeY + noise;\n let halfBand = uniform.uBandWidth * 0.5;\n\n // Signed distance from threshold, accounting for sweep direction\n let sweepDir = sign(uniform.uWipeTop - uniform.uWipeBottom);\n let dist = (center.y - threshold) * sweepDir;\n let raw = smoothstep(-halfBand, halfBand, dist);\n\n if (uniform.uMode > 0.0) {\n g_visibility = raw;\n } else {\n g_visibility = 1.0 - raw;\n }\n}\n\nfn modifySplatCenter(center: ptr<function, vec3f>) {\n initWipe(*center);\n}\n\nfn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr<function, vec4f>, scale: ptr<function, vec3f>) {\n if (g_visibility < 0.01) {\n *scale = vec3f(0.0);\n return;\n }\n\n if (g_visibility >= 0.99) {\n return;\n }\n\n let origSize = gsplatGetSizeFromScale(*scale);\n let t = g_visibility;\n\n if (t < 0.3) {\n let dotSize = min(t * 0.15 * origSize, origSize);\n gsplatMakeSpherical(scale, dotSize);\n } else {\n *scale *= t;\n }\n}\n\nfn wipeColorEffect(center: vec3f, color: ptr<function, vec4f>) {\n if (g_visibility < 0.01 || g_visibility >= 0.99) {\n return;\n }\n\n let edgeIntensity = sin(g_visibility * 3.14159);\n (*color) = vec4f((*color).rgb + uniform.uEdgeTint * edgeIntensity * 0.4, (*color).a);\n}\n\nfn modifySplatColor(center: vec3f, color: ptr<function, vec4f>) {\n wipeColorEffect(center, color);\n}\n",_applyShaders(){const t=this.entity.gsplat;if(t)if(t.unified){if(this.material=this.app.scene.gsplat?.material??null,!this.material)return void(this._shadersNeedApplication=!0);this._applyShaderToMaterial(this.material)}else{const e=t.material;e?(this.material=e,this._applyShaderToMaterial(e)):this._shadersNeedApplication=!0}},_applyShaderToMaterial(t){const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl";let o="wgsl"===n?this.getShaderWGSL():this.getShaderGLSL();const i=this.entity.script&&this.entity.script.gsplatRelighting;if(i&&i.enabled){const t="wgsl"===n?O:V;o="wgsl"===n?t+"\n"+o.replace(/fn modifySplatColor\(center: vec3f, color: ptr<function, vec4f>\) \{\s*wipeColorEffect\(center, color\);\s*\}/,"fn modifySplatColor(center: vec3f, color: ptr<function, vec4f>) {\n applyRelighting(center, color);\n wipeColorEffect(center, color);\n}"):t+"\n"+o.replace(/void modifySplatColor\(vec3 center, inout vec4 color\) \{\s*wipeColorEffect\(center, color\);\s*\}/,"void modifySplatColor(vec3 center, inout vec4 color) {\n applyRelighting(center, color);\n wipeColorEffect(center, color);\n}"),i._shaderManagedExternally=!0,console.log("[WipeTransition] Combined shader with relighting")}t.getShaderChunks(n).set("gsplatModifyVS",o),t.update()},_removeShaders(){if(!this.material)return;const t=this.entity.script&&this.entity.script.gsplatRelighting;if(t&&t.enabled){t._shaderManagedExternally=!1,t.material=this.material;const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl",o="wgsl"===n?t.getShaderWGSL():t.getShaderGLSL();return this.material.getShaderChunks(n).set("gsplatModifyVS",o),this.material.update(),this.material=null,void console.log("[WipeTransition] Handed material back to relighting")}const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl";this.material.getShaderChunks(n).delete("gsplatModifyVS"),this.material.update(),this.material=null},_setUniform(t,e){this.material?.setParameter(t,e)},destroy(){this._removeShaders()}}),Z=e,e}();s=e.script?.create?.(n)??void 0}s?(s.enabled=!1,s.mode=n,s.duration=Re,s.bandWidth=De,s.wipeTop=o?Ie:Fe,s.wipeBottom=o?Fe:Ie,s.onComplete=i||null,s.effectTime=0,n>0?s.edgeTint.set(.2,.6,1):s.edgeTint.set(1,.4,.1),s.enabled=!0):i&&i()}function $e(e,n,o){if(!ze)return void(o&&o());e.script||e.addComponent("script");let i=e.script?.gsplatDissolveTransition;if(!i){const n=function(){if(Q)return Q;const e=t.createScript("gsplatDissolveTransition");return Object.assign(e.prototype,{effectTime:0,material:null,_effectInitialized:!1,_shadersNeedApplication:!1,_edgeTintArray:[0,0,0],mode:1,duration:1,edgeTint:null,onComplete:null,initialize(){this.effectTime=0,this.material=null,this._effectInitialized=!1,this._shadersNeedApplication=!1,this._edgeTintArray=[0,0,0],this.edgeTint||(this.edgeTint=new t.Color(.3,.5,.8)),this.on("enable",()=>{this.effectTime=0,this._effectInitialized?this._applyShaders():this._shadersNeedApplication=!0}),this.on("disable",()=>{this._removeShaders()}),this.entity.gsplat&&(this._effectInitialized=!0,this.enabled&&this._applyShaders())},update(t){if(this._effectInitialized){if(this._shadersNeedApplication&&(this._applyShaders(),this.material&&(this._shadersNeedApplication=!1)),this.material){if(this.effectTime+=t,Math.min(this.effectTime/this.duration,1)>=1){const t=this.entity.script&&this.entity.script.gsplatRelighting;if(t&&t.enabled){if(this.onComplete){const t=this.onComplete;this.onComplete=null,t()}return}const e=this.onComplete;return this.enabled=!1,void(e&&e())}this._updateUniforms(),this.material.update()}}else this.entity.gsplat&&(this._effectInitialized=!0,this.enabled&&this._shadersNeedApplication&&(this._applyShaders(),this.material&&(this._shadersNeedApplication=!1)))},_updateUniforms(){const t=Math.min(this.effectTime/this.duration,1);this._setUniform("uDissolveProgress",t),this._setUniform("uMode",this.mode),this._setUniform("uTime",this.effectTime),this._edgeTintArray[0]=this.edgeTint.r,this._edgeTintArray[1]=this.edgeTint.g,this._edgeTintArray[2]=this.edgeTint.b,this._setUniform("uEdgeTint",this._edgeTintArray)},getShaderGLSL:()=>"\nuniform float uDissolveProgress; // 0.0 = start, 1.0 = fully dissolved\nuniform float uMode; // 1.0 = dissolve in (reveal), -1.0 = dissolve out (hide)\nuniform vec3 uEdgeTint; // Color tint at dissolve edge\nuniform float uTime; // Animation time\n\n// Per-splat shared state\nfloat g_visibility; // 0 = hidden, 1 = visible\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 initDissolve(vec3 center) {\n // Each splat gets a random threshold (0-1) based on its position\n float noise = hash(center);\n // Softness controls how gradual each individual splat's transition is\n float softness = 0.08;\n\n if (uMode > 0.0) {\n // Dissolve in: splats appear as progress exceeds their noise threshold\n g_visibility = smoothstep(noise - softness, noise + softness, uDissolveProgress);\n } else {\n // Dissolve out: splats disappear as progress exceeds their noise threshold\n g_visibility = 1.0 - smoothstep(noise - softness, noise + softness, uDissolveProgress);\n }\n}\n\nvoid modifySplatCenter(inout vec3 center) {\n initDissolve(center);\n}\n\nvoid modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) {\n if (g_visibility < 0.01) {\n scale = vec3(0.0);\n return;\n }\n\n if (g_visibility >= 0.99) {\n return;\n }\n\n // Transition: shrink splats as they dissolve\n float origSize = gsplatGetSizeFromScale(scale);\n float t = g_visibility;\n\n if (t < 0.2) {\n // Nearly dissolved: tiny spherical dots\n float dotSize = min(t * 0.2 * origSize, origSize);\n gsplatMakeSpherical(scale, dotSize);\n } else {\n scale *= t;\n }\n}\n\nvoid dissolveColorEffect(vec3 center, inout vec4 color) {\n if (g_visibility < 0.01 || g_visibility >= 0.99) return;\n\n // Tint at the dissolve edge - strongest in the middle of the transition\n float edgeIntensity = sin(g_visibility * 3.14159);\n color.rgb += uEdgeTint * edgeIntensity * 0.25;\n}\n\nvoid modifySplatColor(vec3 center, inout vec4 color) {\n dissolveColorEffect(center, color);\n}\n",getShaderWGSL:()=>"\nuniform uDissolveProgress: f32;\nuniform uMode: f32;\nuniform uEdgeTint: vec3f;\nuniform uTime: f32;\n\nvar<private> g_visibility: f32;\n\nfn hash(p: vec3f) -> f32 {\n return fract(sin(dot(p, vec3f(127.1, 311.7, 74.7))) * 43758.5453);\n}\n\nfn initDissolve(center: vec3f) {\n let noise = hash(center);\n let softness: f32 = 0.08;\n\n if (uniform.uMode > 0.0) {\n g_visibility = smoothstep(noise - softness, noise + softness, uniform.uDissolveProgress);\n } else {\n g_visibility = 1.0 - smoothstep(noise - softness, noise + softness, uniform.uDissolveProgress);\n }\n}\n\nfn modifySplatCenter(center: ptr<function, vec3f>) {\n initDissolve(*center);\n}\n\nfn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr<function, vec4f>, scale: ptr<function, vec3f>) {\n if (g_visibility < 0.01) {\n *scale = vec3f(0.0);\n return;\n }\n\n if (g_visibility >= 0.99) {\n return;\n }\n\n let origSize = gsplatGetSizeFromScale(*scale);\n let t = g_visibility;\n\n if (t < 0.2) {\n let dotSize = min(t * 0.2 * origSize, origSize);\n gsplatMakeSpherical(scale, dotSize);\n } else {\n *scale *= t;\n }\n}\n\nfn dissolveColorEffect(center: vec3f, color: ptr<function, vec4f>) {\n if (g_visibility < 0.01 || g_visibility >= 0.99) {\n return;\n }\n\n let edgeIntensity = sin(g_visibility * 3.14159);\n (*color) = vec4f((*color).rgb + uniform.uEdgeTint * edgeIntensity * 0.25, (*color).a);\n}\n\nfn modifySplatColor(center: vec3f, color: ptr<function, vec4f>) {\n dissolveColorEffect(center, color);\n}\n",_applyShaders(){const t=this.entity.gsplat;if(t)if(t.unified){if(this.material=this.app.scene.gsplat?.material??null,!this.material)return void(this._shadersNeedApplication=!0);this._applyShaderToMaterial(this.material)}else{const e=t.material;e?(this.material=e,this._applyShaderToMaterial(e)):this._shadersNeedApplication=!0}},_applyShaderToMaterial(t){const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl";let o="wgsl"===n?this.getShaderWGSL():this.getShaderGLSL();const i=this.entity.script&&this.entity.script.gsplatRelighting;if(i&&i.enabled){const t="wgsl"===n?O:V;o="wgsl"===n?t+"\n"+o.replace(/fn modifySplatColor\(center: vec3f, color: ptr<function, vec4f>\) \{\s*dissolveColorEffect\(center, color\);\s*\}/,"fn modifySplatColor(center: vec3f, color: ptr<function, vec4f>) {\n applyRelighting(center, color);\n dissolveColorEffect(center, color);\n}"):t+"\n"+o.replace(/void modifySplatColor\(vec3 center, inout vec4 color\) \{\s*dissolveColorEffect\(center, color\);\s*\}/,"void modifySplatColor(vec3 center, inout vec4 color) {\n applyRelighting(center, color);\n dissolveColorEffect(center, color);\n}"),i._shaderManagedExternally=!0}t.getShaderChunks(n).set("gsplatModifyVS",o),t.update()},_removeShaders(){if(!this.material)return;const t=this.entity.script&&this.entity.script.gsplatRelighting;if(t&&t.enabled){t._shaderManagedExternally=!1,t.material=this.material;const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl",o="wgsl"===n?t.getShaderWGSL():t.getShaderGLSL();return this.material.getShaderChunks(n).set("gsplatModifyVS",o),this.material.update(),void(this.material=null)}const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl";this.material.getShaderChunks(n).delete("gsplatModifyVS"),this.material.update(),this.material=null},_setUniform(t,e){this.material?.setParameter(t,e)},destroy(){this._removeShaders()}}),Q=e,e}();i=e.script?.create?.(n)??void 0}i?(i.enabled=!1,i.mode=n,i.duration=Be,i.onComplete=o||null,i.effectTime=0,n>0?i.edgeTint.set(.3,.5,.8):i.edgeTint.set(.8,.4,.2),i.enabled=!0):o&&o()}const Ve=d.swapTransitionType||"dissolve";function Oe(t,e,n){"scanline"===Ve?function(t,e,n=!0){ze?(Ue(t,-1,n,()=>{t.enabled=!1,console.log("[WipeTransition] Old splat hidden after wipe")}),Ue(e,1,n),console.log(`[WipeTransition] Dual wipe swap started (${n?"forward":"backward"})`)):t.enabled=!1}(t,e,n):function(t,e){ze?($e(t,-1,()=>{t.enabled=!1}),$e(e,1),console.log("[DissolveTransition] Cross-dissolve swap started")):t.enabled=!1}(t,e)}function We(t,e){"scanline"===Ve?Ue(t,1,e):$e(t,1)}function Ne(t){t.enabled=!1,console.log("[SplatSwap] Splat hidden")}function Ge(t){const e=gt.get(t);if(e){const n=e.script?.gsplatRelighting;n&&st.delete(n),e.destroy(),gt.delete(t),console.log("[SplatSwap] Splat disposed:",t)}}function He(t){const e=gt.get(t);return!!e&&(e.enabled=!0,ut=t,console.log("[SplatSwap] Splat shown:",t),!0)}function Xe(){if(ut){const t=gt.get(ut);if(t&&t.enabled)return t}return et&&et.enabled?et:null}async function qe(e){if(!gt.has(e)&&e!==ut&&!lt){console.log("[SplatSwap] Preloading splat:",e);try{const n=new t.Asset("splat-preload-"+Date.now(),"gsplat",{url:e});await new Promise((o,i)=>{n.ready(()=>{if(lt)return void i(new Error("Viewer destroyed"));const s=new t.Entity("splat-preload");s.addComponent("gsplat",{asset:n});const a=d.scale||{x:1,y:1,z:1},r=d.invertXScale||!1,l=d.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=d.position||[0,0,0];s.setPosition(p[0],p[1],-p[2]);const h=d.rotation||[0,0,0],u=[h[0]*(180/Math.PI),h[1]*(180/Math.PI),-h[2]*(180/Math.PI)];s.setEulerAngles(u[0],u[1],u[2]),s.enabled=!1,U.root.addChild(s),Ao(s),gt.set(e,s),console.log("[SplatSwap] Preload complete:",e),o()}),n.on("error",t=>{console.error("[SplatSwap] Preload error:",t),i(t)}),U.assets.add(n),U.assets.load(n)})}catch(t){console.error("[SplatSwap] Error preloading:",e,t)}}}function je(t,e=!1){if("explore"!==Lt)return;const n=e?ht:t||ht;if(Et){Et.mode!==n&&(Et.setMode(n),console.log(`[SplatSwap] Switching explore sub-mode to: ${n}`),Ee(n),N?x(F,"fly"===n):(S(F,"fly"===n),_(F,"orbit"===n)),E(F))}}async function Ye(e,n=!0){if(e!==ut&&!mt&&!lt){mt=!0,console.log(`[SplatSwap] Switching to splat (${n?"fwd":"bwd"}):`,e);try{const o=Xe();if(gt.has(e)){He(e),i.emit("splatChange",{url:e,isOriginal:!1});const t=gt.get(e);o&&o!==t?Oe(o,t,n):We(t,n)}else{const s=new t.Asset("splat-swap-"+Date.now(),"gsplat",{url:e});await new Promise((a,r)=>{s.ready(()=>{if(lt)return void r(new Error("Viewer destroyed"));const l=new t.Entity("splat-swap");l.addComponent("gsplat",{asset:s});const c=d.scale||{x:1,y:1,z:1},p=d.invertXScale||!1,h=d.invertYScale||!1,u={x:p?-c.x:c.x,y:h?c.y:-c.y,z:p!==h?c.z:-c.z};l.setLocalScale(u.x,u.y,u.z);const m=d.position||[0,0,0];l.setPosition(m[0],m[1],-m[2]);const g=d.rotation||[0,0,0],f=[g[0]*(180/Math.PI),g[1]*(180/Math.PI),-g[2]*(180/Math.PI)];l.setEulerAngles(f[0],f[1],f[2]),U.root.addChild(l),Ao(l),gt.set(e,l),ut=e,o?Oe(o,l,n):We(l,n),i.emit("splatChange",{url:e,isOriginal:!1}),console.log("[SplatSwap] New splat loaded and shown:",e),a()}),s.on("error",t=>{console.error("[SplatSwap] Load error:",t),r(t)}),U.assets.add(s),U.assets.load(s)})}const s=dt.findIndex(t=>t.url===e);-1!==s&&async function(t){if(!dt||0===dt.length)return;const e=(t+1)%dt.length,n=dt[e];n&&n.url&&await qe(n.url)}(s)}catch(t){console.error("[SplatSwap] Error switching splat:",t)}finally{mt=!1}}}function Ze(){return d.sogUrl?d.sogUrl:d.splatUrl?d.splatUrl:d.fallbackUrls&&d.fallbackUrls.length>0?d.fallbackUrls[0]:""}function Qe(){const t=Ze();if(ut===t)return;const e=Xe();et&&(et.enabled=!0,e&&e!==et?(Oe(e,et,!1),gt.forEach((n,o)=>{o!==t&&n!==e&&(pt?Ne(n):Ge(o))})):(We(et,!1),gt.forEach((e,n)=>{n!==t&&(pt?Ne(e):Ge(n))}))),ut=t,xt=0,i.emit("splatChange",{url:t,isOriginal:!0}),console.log("[SplatSwap] Manually returned to original splat"),je(void 0,!0)}function Ke(t,e=0){console.log("[SplatSwap] Applying skybox:",t,"rotation:",e),vo(t,e)}let Je=0,tn=0,en=!1;const nn=d.waypoints?.reduce((t,e)=>t+(e.duration||2e3),0)||1,on=d.waypoints?.length||1,sn=Math.max(1,20*(on-1));let an=void 0!==d.autoplaySpeed?60*d.autoplaySpeed/sn:1e3/nn;const rn=d.loopMode;let ln;ln=!0===rn?"loop":!1===rn?"none":"loop"===rn||"pingpong"===rn||"none"===rn?rn:"loop";let cn=1;console.log("[StorySplat Viewer] Playback config:",{loopMode:ln,playbackSpeed:an,totalDuration:nn,autoPlay:d.autoPlay,autoplaySpeed:d.autoplaySpeed,rawLoopMode:d.loopMode});let dn=null,pn=null;const hn=.01+.1*(d.transitionSpeed||1);let un=0,mn=0;let gn=!1,fn=!1,yn=0,vn=0;const xn=[],bn=[],wn=[],Sn=d.fov||60;let _n=Sn;function Mn(n){if(!Ot||xn.length<2)return;n=Math.max(0,Math.min(1,n));const o=xn.length,s=n*(o-1),a=Math.min(Math.floor(s),o-2),r=s-a,l=xn[Math.max(a-1,0)],c=xn[a],p=xn[a+1],h=xn[Math.min(a+2,o-1)],u=r*r,m=u*r;dn=new t.Vec3(.5*(2*c.x+(-l.x+p.x)*r+(2*l.x-5*c.x+4*p.x-h.x)*u+(-l.x+3*c.x-3*p.x+h.x)*m),.5*(2*c.y+(-l.y+p.y)*r+(2*l.y-5*c.y+4*p.y-h.y)*u+(-l.y+3*c.y-3*p.y+h.y)*m),.5*(2*c.z+(-l.z+p.z)*r+(2*l.z-5*c.z+4*p.z-h.z)*u+(-l.z+3*c.z-3*p.z+h.z)*m));const g=bn[a],f=bn[a+1];pn=new t.Quat,pn.slerp(g,f,r);const y=wn[a],v=wn[a+1];_n=Ut(y,v,r);const x=Math.round(n*(o-1));if(x!==K){const n=K;K=x;const o=d.waypoints[x],s=o.cameraMode||"first-person";"orbit"===s&&(o.orbitTarget?new t.Vec3(o.orbitTarget.x,o.orbitTarget.y,-(o.orbitTarget.z||0)):new t.Vec3(xn[x].x,xn[x].y,xn[x].z)),i.emit("waypointChange",{index:x,waypoint:o,prevIndex:n,cameraMode:s}),b=x,w=n,qn.forEach((t,e)=>{const{entity:n,waypointIndex:o,config:i,slotId:s,autoplayTriggered:a}=t,r=n.sound?.slot(s);if(!r)return;const l=w===o&&b!==o;b===o&&w!==o&&i.autoplay&&!a&&(console.log(`[Audio] Autoplay waypoint audio: ${e} at waypoint ${o}`),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))}),jn(),Yn(e);const a=e.querySelector(".storysplat-hotspot-popup");a&&a.classList.remove("visible")}var b,w;i.emit("progressUpdate",{progress:Math.max(0,Math.min(1,n)),index:K}),function(){if(!dt||0===dt.length)return;const t=d.waypoints?.length||1,e=100*Je,n=Math.round(Je*Math.max(1,t-1));if(Math.abs(e-ft)<.1&&n===yt)return;ft=e,yt=n;let o=null,s=null,a=-1/0,r=-1/0;for(const t of dt)-1!==t.waypointIndex?n>=t.waypointIndex&&t.waypointIndex>a&&(a=t.waypointIndex,o=t):-1!==t.percentage&&e>=t.percentage&&t.percentage>r&&(r=t.percentage,s=t);const l=s||o,c=l&&"__ORIGINAL__"===l.url,p=Ze(),h=l?c?p:l.url:p;if(h&&h!==ut){const t=e>=xt;if(xt=e,h===p&&et&&!ut)ut=p;else if(h===p&&et){const e=Xe();et.enabled=!0,ut=p,e&&e!==et?(Oe(e,et,t),gt.forEach((t,n)=>{n!==p&&t!==e&&(pt?Ne(t):Ge(n))})):(We(et,t),gt.forEach((t,e)=>{e!==p&&(pt?Ne(t):Ge(e))})),i.emit("splatChange",{url:p,isOriginal:!0}),console.log("[SplatSwap] Returned to primary splat"),je(void 0,!0)}else Ye(h,t),l&&je(l.defaultExploreMode,!1);l&&l.skyboxUrl&&!c?Ke(l.skyboxUrl,l.skyboxRotation||0):c&&mo&&Ke(mo,go)}}()}function En(t,e=!1){const n=Math.max(0,Math.min(1,t));tn=n,e?Tn(n):(Je=n,Mn(Je))}d.waypoints&&d.waypoints.length>0&&(d.waypoints.forEach(e=>{const n=e.position?new t.Vec3(e.position.x,e.position.y,-e.position.z):new t.Vec3(0,d.playerHeight||1.6,0);xn.push(n);const o=e.rotation?ke(e.rotation):new t.Quat;bn.push(o);let i=e.fov||Sn;i<3.5&&(i*=180/Math.PI),wn.push(i)}),xn.length>0&&(dn=xn[0].clone(),pn=bn[0].clone(),_n=wn[0])),U.on("update",function(){if(!Ot)return;if(!en){const t=tn-Je;Math.abs(t)>1e-4&&(Je+=t*hn,Mn(Je))}if(!dn||!pn)return;const e=bt.getPosition(),n=new t.Vec3;n.lerp(e,dn,hn),bt.setPosition(n.x,n.y,n.z);const o=bt.camera;if(o&&wn.length>0){const t=Ut(o.fov,_n,hn);o.fov=t}gn||(un*=.95,mn*=.95,Math.abs(un)<.01&&(un=0),Math.abs(mn)<.01&&(mn=0));const i=new t.Quat;i.setFromEulerAngles(mn,un,0);const s=new t.Quat;s.mul2(pn,i);const a=bt.getRotation(),r=new t.Quat;r.slerp(a,s,hn),bt.setRotation(r)});const Cn=500*(d.transitionSpeed||1);function Tn(t,e=Cn){const n=Je,o=performance.now();en=!0;const i=()=>{const s=performance.now()-o,a=Math.min(s/e,1);Je=n+(t-n)*(a<.5?2*a*a:(4-2*a)*a-1),Mn(Je),a<1?requestAnimationFrame(i):en=!1};requestAnimationFrame(i)}function An(t){if(!d.waypoints||t<0||t>=d.waypoints.length)return;if(!Ot)return;const e=t/Math.max(1,d.waypoints.length-1);tn=e,Tn(e)}function Pn(){if(!d.waypoints||0===d.waypoints.length)return;const t=d.scrollButtonMode||"waypoint";if("percentage"===t||"continuous"===t||"incremental"===t){const t=d.scrollAmount||10;let e=Je+t/100;e>1&&(e="loop"===ln?0:1),tn=e,Tn(e)}else{let t=K+1;t>=d.waypoints.length&&(t="loop"===ln?0:d.waypoints.length-1),An(t)}}function Ln(){if(!d.waypoints||0===d.waypoints.length)return;const t=d.scrollButtonMode||"waypoint";if("percentage"===t||"continuous"===t||"incremental"===t){const t=d.scrollAmount||10;let e=Je-t/100;e<0&&(e="loop"===ln?1:0),tn=e,Tn(e)}else{let t=K-1;t<0&&(t="loop"===ln?d.waypoints.length-1:0),An(t)}}let kn=0,zn=null;function Rn(t){if(!tt)return;0===kn&&(kn=t);const e=(t-kn)/1e3;kn=t;const n=an*e*cn;if(Je+=n,tn+=n,Je>=1)switch(console.log("[StorySplat Viewer] End of tour reached, loopMode:",ln),ln){case"loop":console.log("[StorySplat Viewer] Looping - restarting at beginning"),Je=0,tn=0;break;case"pingpong":console.log("[StorySplat Viewer] Pingpong - reversing direction"),Je=1,tn=1,cn=-1;break;case"none":return console.log("[StorySplat Viewer] No loop - stopping playback"),Je=1,tn=1,In(),void i.emit("playbackComplete");default:return console.log("[StorySplat Viewer] Unknown loopMode, stopping:",ln),Je=1,tn=1,In(),void i.emit("playbackComplete")}else if(Je<=0)if("pingpong"===ln)console.log("[StorySplat Viewer] Pingpong - reversing to forward"),Je=0,tn=0,cn=1;else Je=0,tn=0;Mn(Je),zn=requestAnimationFrame(Rn)}function Dn(){if(ct)return ct.play(),void i.emit("playbackStart");tt||!d.waypoints||d.waypoints.length<2||(tt=!0,kn=0,cn=1,i.emit("playbackStart"),zn=requestAnimationFrame(Rn))}function In(){if(ct)return ct.pause(),void i.emit("playbackStop");tt=!1,zn&&(cancelAnimationFrame(zn),zn=null),i.emit("playbackStop")}const Fn=U.graphicsDevice.canvas;Fn.addEventListener("wheel",t=>{if(!Ot)return;t.preventDefault();const e=d.scrollSpeed||.1,n=d.scrollAmount||100,o=d.waypoints?.length||2,i=Math.max(20,20*(o-1)),s=100*(Math.abs(t.deltaY)/100)*e*(n/100)/i,a=t.deltaY>0?s:-s;tn=Math.max(0,Math.min(1,tn+a))},{passive:!1}),Fn.addEventListener("pointerdown",t=>{Ot&&!fn&&(gn=!0,yn=t.clientX,vn=t.clientY)},{capture:!0}),Fn.addEventListener("pointermove",t=>{if(!Ot||!gn||fn)return;const e=t.clientX-yn,n=t.clientY-vn;yn=t.clientX,vn=t.clientY;un+=.3*-e,mn+=.3*-n,mn=Math.max(-60,Math.min(60,mn))},{capture:!0}),Fn.addEventListener("pointerup",()=>{gn=!1},{capture:!0}),Fn.addEventListener("pointerleave",()=>{gn=!1},{capture:!0});const Bn=[],Un=[],$n=F.portalPopup;let Vn=null;const On=()=>{$n&&($n.classList.remove("visible"),Vn=null)};if($n){const t=$n.querySelector(".storysplat-portal-popup-confirm"),e=$n.querySelector(".storysplat-portal-popup-cancel");t?.addEventListener("click",()=>{if(Vn){const t=Vn;On(),fi(t)}}),e?.addEventListener("click",()=>{On()})}function Wn(e){const n=parseInt(e.slice(1,3),16)/255,o=parseInt(e.slice(3,5),16)/255,i=parseInt(e.slice(5,7),16)/255;return new t.Color(n,o,i)}const Nn=[],Gn=[];let Hn=!1;const Xn=new Map,qn=new Map;function jn(){Bn.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 Yn(t){const e=t.querySelector(".storysplat-hotspot-popup");if(!e)return;e.querySelectorAll("video").forEach(t=>{t.pause(),t.currentTime=0}),e.querySelectorAll("iframe").forEach(t=>{t.src=""});const n=e.__modelViewerCleanup;n&&(n(),delete e.__modelViewerCleanup)}const Zn=new Map,Qn=new Map,Kn={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 Jn(e,n){return new Promise((o,i)=>{if(Qn.has(e))return void o(Qn.get(e));const s=new t.Asset(e,"texture",{url:n});s.on("load",()=>{if(lt)return console.log(`[Particle] Ignoring texture load - viewer was destroyed: ${e}`),void i(new Error("Viewer destroyed"));const t=s.resource;Qn.set(e,t),o(t)}),s.on("error",t=>{console.error(`[Particle] Failed to load texture: ${e}`,t),i(t)}),U.assets.add(s),U.assets.load(s)})}function to(e){const n=new t.Entity(e.name||"particle-system"),o=(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=e.emitterPosition?o(e.emitterPosition,0):{x:e.position?.x??0,y:e.position?.y??0,z:e.position?.z??0},a=o(e.gravity,0),r=i(e.color1),l=i(e.color2),c=i(e.colorDead),d=o(e.direction1,0),p=o(e.direction2,0),h=e.maxLifeTime??3;let u=t.EMITTERSHAPE_BOX,m=new t.Vec3(.1,.1,.1),g=new t.Vec3(0,0,0);if("sphere"===e.emitterType||"sphere"===e.emitterShape){u=t.EMITTERSHAPE_SPHERE;const n=e.emitterRadius||.1;m=new t.Vec3(n,n,n)}else if("box"===e.emitterShape&&e.emitBoxMin&&e.emitBoxMax){u=t.EMITTERSHAPE_BOX;const n=o(e.emitBoxMin,0),i=o(e.emitBoxMax,0);m=new t.Vec3(Math.abs(i.x-n.x),Math.abs(i.y-n.y),Math.abs(i.z-n.z)),g=new t.Vec3((n.x+i.x)/2,(n.y+i.y)/2,-(n.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||.01,e.emitterExtents?.y||e.emitterRadius||.01,e.emitterExtents?.z||e.emitterRadius||.01);let f=t.BLEND_ADDITIVE;const y=e.blendMode||"";"BLENDMODE_STANDARD"===y||"normal"===y||"alpha"===y?f=t.BLEND_NORMAL:"BLENDMODE_MULTIPLY"===y||"multiply"===y?f=t.BLEND_MULTIPLICATIVE:"BLENDMODE_ONEONE"===y||"BLENDMODE_ADD"===y?f=t.BLEND_ADDITIVE:"BLENDMODE_MULTIPLYADD"===y&&(f=t.BLEND_ADDITIVEALPHA);const v=a.x||0,x=a.y||0,b=-(a.z||0),w=v*h,S=x*h,_=b*h,M=180/Math.PI,E=(e.minAngularSpeed??0)*M,C=(e.maxAngularSpeed??e.angularSpeed??0)*M,T=(e.minInitialRotation??0)*M,A=(e.maxInitialRotation??0)*M,P=.1*(e.minSize??.1),L=.1*(e.maxSize??.5),k=e.minScaleX??1,z=e.maxScaleX??1,R=e.minScaleY??1,D=e.maxScaleY??1,I=e.minEmitPower??1,F=e.maxEmitPower??2,B=d.x,U=d.y,$=-d.z,V=p.x,O=p.y,W=-p.z,N=u===t.EMITTERSHAPE_SPHERE,G=e.emitRate||e.rate||50,H=1/G,X=Math.ceil(G*h*2);n.addComponent("particlesystem",{numParticles:e.numParticles||Math.max(X,100),lifetime:h,rate:H,emitterShape:u,emitterExtents:m,emitterRadius:e.emitterRadius||.1,startAngle:T,startAngle2:A,...N?{radialSpeedGraph:new t.Curve([0,I]),radialSpeedGraph2:new t.Curve([0,F])}:{localVelocityGraph:new t.CurveSet([[0,B*I],[0,U*I],[0,$*I]]),localVelocityGraph2:new t.CurveSet([[0,V*F],[0,O*F],[0,W*F]])},velocityGraph:new t.CurveSet([[0,0,1,w],[0,0,1,S],[0,0,1,_]]),scaleGraph:new t.Curve([0,P*Math.min(k,R)]),scaleGraph2:new t.Curve([0,L*Math.max(z,D)]),rotationSpeedGraph:new t.Curve([0,E]),...C!==E?{rotationSpeedGraph2:new t.Curve([0,C])}:{},colorGraph:new t.CurveSet([[0,r.r,.95,l.r,1,c.r],[0,r.g,.95,l.g,1,c.g],[0,r.b,.95,l.b,1,c.b]]),alphaGraph:new t.Curve([0,r.a??1,.95,l.a??1,1,c.a??0]),blendType:f,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}),n.particlesystem&&(n.particlesystem.localSpace=e.localSpace??!1),n.setPosition(s.x+g.x,s.y+g.y,-s.z+g.z);const q=e.renderingGroupId??3;return n.particlesystem&&void 0!==q&&(n.particlesystem.drawOrder=q),console.log(`[Particle] Entity configured at position: (${s.x+g.x}, ${s.y+g.y}, ${-s.z+g.z})`),console.log(`[Particle] Emitter shape: ${e.emitterShape||"box"}, extents: ${m.x}, ${m.y}, ${m.z}`),console.log(`[Particle] Gravity: (${v}, ${x}, ${b}), Lifetime: ${h}s`),console.log(`[Particle] Colors: start=${JSON.stringify(r)}, mid=${JSON.stringify(l)}, dead=${JSON.stringify(c)}`),console.log(`[Particle] Angular speed: ${E.toFixed(1)} - ${C.toFixed(1)} deg/s, Initial rotation: ${T.toFixed(1)} - ${A.toFixed(1)} deg`),console.log(`[Particle] Scale: ${P}*${k} - ${L}*${D}, EmitPower: ${I}-${F}, RenderingGroupId: ${q}`),console.log(`[Particle] Rate: ${G} particles/sec → PC rate=${H.toFixed(4)}s/particle, numParticles=${e.numParticles||Math.max(X,100)}`),n}const eo=new Map;function no(t){const{type:e,component:n,node:o,animations:i}=t;console.log("[CustomMesh] Playing animation, type:",e,"node:",o?.name,"animations:",i?.length);try{if("pc-anim"===e){if(console.log("[CustomMesh] Using simple pc-anim approach (like HTML export)"),!n)return;n.playing=!0,t.isPlaying=!0,console.log("[CustomMesh] Animation started - playing:",n.playing)}else if("animation"===e){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"===e){console.log("[CustomMesh] Starting GLB skeleton animation playback");const{modelEntity:e,animations:n}=t;if(!e)return;if(n&&n.length>0)try{if(e.anim||e.addComponent("anim",{activate:!0,speed:1}),e.anim){const o=e.anim;n.forEach((t,e)=>{const n=t.resource||t,i=n._name||n.name||t._name||t.name||`Anim_${e}`;console.log("[CustomMesh] Assigning animation track:",i,"type:",typeof n);try{o.assignAnimation?.(i,n)}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&&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 oo(t){const{type:e,component:n}=t;if(n||"glb-skeleton"===e)try{"pc-anim"===e&&n?(n.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&&(U.off("update",t.updateHandler),t.updateHandler=null,console.log("[CustomMesh] GLB manual animation paused"))):n&&(n.playing=!1,n.speed=0,"animation"===e&&"function"==typeof n.pause&&n.pause())}catch(t){console.error("[CustomMesh] Error pausing animation:",t)}}function io(e,n){if(console.log("[CustomMesh] Loading mesh",n,":",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 o=new t.Entity("custom-mesh-"+n),i=e.position||{x:0,y:0,z:0};if(o.setPosition(i._x??i.x??0,i._y??i.y??0,-(i._z??i.z??0)),e.rotation){const t=e.rotation;o.setRotation(ri(t._x??t.x??0,t._y??t.y??0,t._z??t.z??0))}if(e.scale){const t=e.scale;o.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-"+n,"container",{url:s});U.assets.add(a);const r=e.id||`mesh-${n}`,l={entity:o,config:e,modelAsset:a,isAnimPlaying:!1,audioPlaying:!1};return eo.set(r,l),a.ready(n=>{try{if(!eo.has(r))return void console.log("[CustomMesh] Entity destroyed before model loaded, skipping:",e.name);if(console.log("[CustomMesh] Model loaded:",e.name),!n||!n.resource)return void console.error("[CustomMesh] Invalid asset resource for mesh:",e.name);const i=n.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)})}o.addChild(s),o.modelEntity=s,a(s);let c=0;function d(e){const n=e.render;if(n&&n.meshInstances)for(const t of n.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&&d(e)})}if(s.forEach(()=>{c++}),console.log("[CustomMesh] Enabled all children for:",e.name,"- Total nodes:",c),d(s),"animated"===e.opacityMode&&e.opacityAnimation){const h=e.opacityAnimation.startOpacity??1;co(o,h),console.log("[CustomMesh] Applied initial animated opacity:",h,"to",e.name)}else void 0!==e.opacity&&e.opacity<1&&(co(o,e.opacity),console.log("[CustomMesh] Applied static opacity:",e.opacity,"to",e.name));if(e.billboard){const u=o.getEulerAngles().clone();o._billboardActive=!e.billboardRange,U.on("update",()=>{if(!o.enabled)return;if(!o._billboardActive)return void o.setEulerAngles(u.x,u.y,u.z);const t=bt.getPosition(),e=o.getPosition(),n=t.x-e.x,i=t.z-e.z,s=Math.atan2(n,i)*(180/Math.PI),a=o.getEulerAngles();o.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 m=[],g=s.anim;if(g&&(console.log("[CustomMesh] Found modelEntity.anim component - using simple approach"),m.push({type:"pc-anim",component:g,modelEntity:s})),0===m.length){function f(e,n=0){e.anim&&!m.find(t=>t.component===e.anim)&&(m.push({type:"anim",component:e.anim,node:e}),console.log("[CustomMesh] Found existing ANIM component on:",e.name)),e.animation&&(m.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&&f(e,n+1)})}f(s)}if(0===m.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{m.push({type:"glb-skeleton",modelEntity:s,asset:n,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(v){console.error("[CustomMesh] Error setting up GLB animations:",v)}}if(m.length>0){l.allAnimComponents=m,l.animComponent=m[0].component,console.log("[CustomMesh] Total animation components for",e.name,":",m.length,"- Types:",m.map(t=>t.type).join(", "));const x=e.interaction?.animationAutoPlay;x&&(m.forEach(t=>{no(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,n,o){const i=n.interaction;if(!i)return;const s=n.id||n.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}}}),o.audioSlotId=a;const r=new t.Asset(`mesh-audio-asset-${s}`,"audio",{url:i.audioUrl});U.assets.add(r),r.ready(()=>{const t=e.sound?.slot(a);t&&(t.asset=r.id),console.log("[CustomMesh] Audio loaded for mesh:",n.name,"Spatial:",i.audioSpatial)}),r.on("error",t=>{console.error("[CustomMesh] Failed to load mesh audio:",n.name,t)}),U.assets.load(r)}(o,e,l),e.interaction&&function(e,n,o){if(!function(t){const e=t.interaction;return!!e&&!!(e.triggerUIPopup||e.playAudio||e.playModelAnimation||e.triggerDirectLink)}(n))return void console.log("[CustomMesh] Skipping interaction setup - no active interactions for:",n.name);const i=n.interaction?.activationMode||"click",s=U.graphicsDevice.canvas,a=(n,o)=>{const i=s.getBoundingClientRect(),a=n-i.left,r=o-i.top,l=bt.camera.screenToWorld(a,r,bt.camera.nearClip),c=bt.camera.screenToWorld(a,r,bt.camera.farClip),d=(new t.Vec3).sub2(c,l).normalize(),p=e.modelEntity;if(p&&so(p,l,d))return!0;if(so(e,l,d))return!0;const h=e.getPosition(),u=(new t.Vec3).sub2(h,l).dot(d);if(u>0){const n=(new t.Vec3).add2(l,d.clone().mulScalar(u)).distance(h),o=e.getLocalScale();if(n<1.5*Math.max(o.x,o.y,o.z))return!0}return!1},r=n.interaction?.popupTriggerMode||i,l=n.interaction?.audioTriggerMode||i,c=n.interaction?.animationTriggerMode||i,d=n.interaction?.directLinkTriggerMode||i;let p=!1;const h=()=>{if(s.style.cursor="pointer",Ai=!0,ue=!0,"hover"===r&&n.interaction?.triggerUIPopup&&ro(n),"hover"===l&&n.interaction?.playAudio&&o.audioSlotId){const t=e.sound?.slot(o.audioSlotId);t&&!t.isPlaying&&(t.play(),o.audioPlaying=!0,console.log("[CustomMesh] Playing audio on hover for:",n.name))}if("hover"===c&&n.interaction?.playModelAnimation){const t=o.allAnimComponents;t&&t.length>0&&!o.isAnimPlaying&&(t.forEach(t=>no(t)),o.isAnimPlaying=!0,console.log("[CustomMesh] Playing animation on hover for:",n.name))}"hover"===d&&n.interaction?.triggerDirectLink&&lo(n)},u=()=>{if(s.style.cursor="",Ai=!1,ue=!1,"hover"===r&&n.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&&n.interaction?.playAudio&&o.audioSlotId){const t=e.sound?.slot(o.audioSlotId);t&&t.isPlaying&&(t.stop(),o.audioPlaying=!1,console.log("[CustomMesh] Stopped audio on hover out for:",n.name))}if("hover"===c&&n.interaction?.playModelAnimation){const t=o.allAnimComponents;t&&t.length>0&&o.isAnimPlaying&&(t.forEach(t=>oo(t)),o.isAnimPlaying=!1,console.log("[CustomMesh] Paused animation on hover out for:",n.name))}},m=()=>{if(console.log("[CustomMesh] Clicked mesh:",n.name),"click"===r&&n.interaction?.triggerUIPopup&&ro(n),"click"===c&&n.interaction?.playModelAnimation){const t=o.allAnimComponents;t&&t.length>0&&(o.isAnimPlaying?(t.forEach(t=>oo(t)),o.isAnimPlaying=!1,console.log("[CustomMesh] Paused",t.length,"animations on click for:",n.name)):(t.forEach(t=>no(t)),o.isAnimPlaying=!0,console.log("[CustomMesh] Playing",t.length,"animations on click for:",n.name)))}if("click"===l&&n.interaction?.playAudio&&o.audioSlotId){const t=e.sound?.slot(o.audioSlotId);t&&(t.isPlaying?(t.pause(),o.audioPlaying=!1,console.log("[CustomMesh] Paused audio on click for:",n.name)):(t.play(),o.audioPlaying=!0,console.log("[CustomMesh] Playing audio on click for:",n.name)))}"click"===d&&n.interaction?.triggerDirectLink&&lo(n)},g=t=>{a(t.clientX,t.clientY)&&m()},f=t=>{const e=a(t.clientX,t.clientY);e&&!p?(p=!0,h()):!e&&p&&(p=!1,u())},y=()=>{p&&(p=!1,u())};s.addEventListener("click",g),s.addEventListener("mousemove",f),s.addEventListener("mouseleave",y),e.meshClickHandler=g,e.meshHoverHandler=f,e.meshLeaveHandler=y}(o,e,l),console.log("[CustomMesh] Mesh fully initialized:",e.name)}catch(b){console.error("[CustomMesh] Error processing loaded mesh:",e.name,b)}}),a.on("error",t=>{console.error("[CustomMesh] Failed to load mesh:",e.name,t),U.assets.remove(a)}),U.assets.load(a),e.visibilityRange?(o.visibilityRange=e.visibilityRange,o.enabled=!1!==e.enabled):o.enabled=!1!==e.enabled,o.opacityConfig={mode:e.opacityMode||"static",value:void 0!==e.opacity?e.opacity:1},U.root.addChild(o),o}function so(e,n,o){const i=e.render;if(i&&i.meshInstances)for(const t of i.meshInstances)if(t.aabb){if(ao(n,o,t.aabb))return!0}const s=e.model;if(s&&s.meshInstances)for(const t of s.meshInstances)if(t.aabb){if(ao(n,o,t.aabb))return!0}const a=e.children;if(a)for(const e of a)if(e instanceof t.Entity&&so(e,n,o))return!0;return!1}function ao(t,e,n){const o=n.getMin?n.getMin():n.min,i=n.getMax?n.getMax():n.max;if(!o||!i)return!1;let s=-1/0,a=1/0;const r=["x","y","z"],l=(t,e,n)=>{const o=t[e];if("number"==typeof o)return o;const i=t[`_${e}`];return"number"==typeof i?i:t.data?.[n]??0};for(let n=0;n<3;n++){const c=r[n],d=t[c],p=e[c],h=l(o,c,n),u=l(i,c,n);if(Math.abs(p)<1e-8){if(d<h||d>u)return!1}else{let t=(h-d)/p,e=(u-d)/p;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 ro(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"};F.showHotspotPopup?F.showHotspotPopup(e):function(t){const e=document.querySelector(".storysplat-mesh-popup");e&&e.remove();const n=document.createElement("div");n.className="storysplat-mesh-popup",n.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 o=document.createElement("h2");if(o.textContent=t.name||"Custom Mesh",o.style.cssText="margin-top: 0; margin-bottom: 16px;",n.appendChild(o),t.interaction?.popupContent){const e=document.createElement("p");e.textContent=t.interaction.popupContent,e.style.cssText="margin: 0; line-height: 1.6;",n.appendChild(e)}const i=document.createElement("button");i.textContent=`× ${l(P,"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=()=>n.remove(),n.appendChild(i),document.body.appendChild(n)}(t)}function lo(t){t.interaction?.triggerDirectLink&&t.interaction.directLinkUrl&&window.open(t.interaction.directLinkUrl,"_blank")}function co(e,n){!function e(o){o.render&&o.render.meshInstances&&o.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=n,e.material.blendType=t.BLEND_PREMULTIPLIED,e.material.depthTest=!0,e.material.depthWrite=!0,e.material.alphaTest=.01,e.material.update()}}),o.children.forEach(n=>{n instanceof t.Entity&&e(n)})}(e)}function po(){const t=U.graphicsDevice.canvas;eo.forEach(e=>{const n=e.entity,o=n.meshClickHandler;o&&t.removeEventListener("click",o),n.destroy()}),eo.clear()}i.on("progressUpdate",()=>{!function(){const t=100*Je,e=d.waypoints?.length||1,n=Math.round(Je*Math.max(1,e-1));eo.forEach(e=>{const{entity:o,config:i}=e,s=o.visibilityRange;if(s){let e=!0;"waypoint"===s.type?e=n>=s.start&&n<=s.end:"percentage"===s.type&&(e=t>=s.start&&t<=s.end),o.enabled=e}if("animated"===i.opacityMode&&i.opacityAnimation){const e=i.opacityAnimation;if(t>=e.startPercent&&t<=e.endPercent){const n=(t-e.startPercent)/(e.endPercent-e.startPercent);co(o,e.startOpacity+(e.endOpacity-e.startOpacity)*n)}}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=n>=e.start&&n<=e.end),o._billboardActive=s}else i.billboard&&(o._billboardActive=!0)})}()});let ho=null,uo=null;const mo=d.skybox?.url||d.skyboxUrl||null,go=d.skybox?.rotation??d.skyboxRotation??0;function fo(){const e=d.skybox?.url||d.skyboxUrl;if(!e)return void console.log("[StorySplat Viewer] No skybox configured");const n=d.skybox?.rotation??d.skyboxRotation??0,o=d.skybox?.intensity??1,i=d.skybox?.enableIBL??!0;console.log("[StorySplat Viewer] Creating skybox:",e,"rotation:",n,"rad =",n*(180/Math.PI),"deg","IBL:",i),yo();const s=new Image;s.crossOrigin="anonymous",s.onload=()=>{if(!lt)try{const e=new t.Texture(U.graphicsDevice,{width:s.width,height:s.height,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,projection:t.TEXTUREPROJECTION_EQUIRECT});e.setSource(s);const a=Math.min(s.width/4,2048),r=new t.Texture(U.graphicsDevice,{width:a,height:a,cubemap:!0,mipmaps:!1,format:t.PIXELFORMAT_RGBA8,minFilter:t.FILTER_LINEAR,magFilter:t.FILTER_LINEAR});t.reprojectTexture(e,r,{numSamples:1024});const l=a,c=new t.Texture(U.graphicsDevice,{width:l,height:l,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});if(t.EnvLighting.generateAtlas(r,{target:c}),U.scene.envAtlas=c,U.scene.skyboxMip=0,U.scene.skyboxIntensity=o,0!==n){const e=new t.Quat;e.setFromEulerAngles(0,-n*(180/Math.PI),0),U.scene.skyboxRotation=e}i&&(U.scene.ambientLight=new t.Color(.3*o,.3*o,.35*o),console.log("[StorySplat Viewer] IBL ambient lighting applied:",o)),console.log("[StorySplat Viewer] Native skybox applied successfully")}catch(e){console.warn("[StorySplat Viewer] Native skybox pipeline failed, using sphere fallback:",e),function(e,n,o,i,s){const a=new t.Entity("skybox-fallback"),r=new t.StandardMaterial;r.diffuse=new t.Color(0,0,0),r.emissive=new t.Color(1,1,1),r.emissiveIntensity=1,r.specular=new t.Color(0,0,0),r.cull=t.CULLFACE_NONE,r.depthTest=!1,r.depthWrite=!1,r.update();const l=new t.Texture(U.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});l.setSource(n),r.emissiveMap=l,r.emissiveIntensity=i,r.update(),a.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1}),a.render.material=r,a.render.meshInstances.length>0&&(a.render.meshInstances[0].drawOrder=-1e4,a.render.meshInstances[0].pick=!1);a.setLocalScale(-500,500,500),0!==o&&a.setEulerAngles(0,-o*(180/Math.PI),0);uo=()=>{const t=bt.getPosition();a.setPosition(t.x,t.y,t.z)},U.on("update",uo),U.root.addChild(a),ho=a,s&&(U.scene.ambientLight=new t.Color(.3*i,.3*i,.35*i));console.log("[StorySplat Viewer] Skybox fallback sphere applied")}(0,s,n,o,i)}},s.onerror=t=>{console.error("[StorySplat Viewer] Failed to load skybox texture:",e,t)},s.src=e}function yo(){U.scene.envAtlas=null,ho&&(ho.destroy(),ho=null),uo&&(U.off("update",uo),uo=null)}function vo(t,e){yo(),t&&(d.skybox={url:t,rotation:e??0},d.skyboxUrl=t,d.skyboxRotation=e??0,fo())}const xo=[];function bo(e){const n=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return n?new t.Color(parseInt(n[1],16)/255,parseInt(n[2],16)/255,parseInt(n[3],16)/255):new t.Color(1,1,1)}function wo(e){const n=new t.Entity(e.name||"Point Light");n.addComponent("light",{type:"point",color:bo(e.color||"#ffffff"),intensity:e.intensity??1,range:e.range||10,castShadows:e.castShadows||!1});const o=e.position;return o&&n.setPosition(o._x??o.x??0,o._y??o.y??0,-(o._z??o.z??0)),n.enabled=!1!==e.enabled,n}function So(e){const n=new t.Entity(e.name||"Directional Light");n.addComponent("light",{type:"directional",color:bo(e.color||"#ffffff"),intensity:e.intensity??1,castShadows:e.castShadows||!1});const o=e.position;o&&n.setPosition(o._x??o.x??0,o._y??o.y??0,-(o._z??o.z??0));const i=e.rotation;if(i)n.setRotation(ri(i._x??i.x??0,i._y??i.y??0,i._z??i.z??0));else if(e.direction){const o=e.direction,i=new t.Vec3(o._x??o.x??0,o._y??o.y??-1,-(o._z??o.z??0)).normalize();n.lookAt(n.getPosition().x+i.x,n.getPosition().y+i.y,n.getPosition().z+i.z)}else n.setEulerAngles(45,0,0);return n.enabled=!1!==e.enabled,n}function _o(e){const n=new t.Entity(e.name||"Hemispheric Light");n.addComponent("light",{type:"directional",color:bo(e.color||"#ffffff"),intensity:e.intensity??1,castShadows:!1});const o=e.position;if(o&&n.setPosition(o._x??o.x??0,o._y??o.y??0,-(o._z??o.z??0)),n.setEulerAngles(-90,0,0),n.enabled=!1!==e.enabled,e.groundColor){const n=bo(e.groundColor),o=bo(e.color||"#ffffff");U.scene.ambientLight=new t.Color((o.r+.3*n.r)/1.3,(o.g+.3*n.g)/1.3,(o.b+.3*n.b)/1.3)}return n}function Mo(e){const n=bo(e.color||"#404040"),o=e.intensity??.4;return U.scene.ambientLight=new t.Color(n.r*o,n.g*o,n.b*o),console.log("[StorySplat Viewer] Set ambient light:",e.name||"Ambient Light"),null}function Eo(e){const n=new t.Entity(e.name||"Spot Light"),o=180/Math.PI,i=(e.angle??45*Math.PI/180)*o;e.exponent;const s=e.innerConeAngle??e.innerAngle??.8*i,a=e.outerConeAngle??e.outerAngle??i;n.addComponent("light",{type:"spot",color:bo(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&&n.setPosition(r._x??r.x??0,r._y??r.y??0,-(r._z??r.z??0));const l=e.rotation;if(l)n.setRotation(ri(l._x??l.x??0,l._y??l.y??0,l._z??l.z??0));else if(e.direction){const o=e.direction,i=new t.Vec3(o._x??o.x??0,o._y??o.y??-1,-(o._z??o.z??0)).normalize();n.lookAt(n.getPosition().x+i.x,n.getPosition().y+i.y,n.getPosition().z+i.z)}else n.setEulerAngles(90,0,0);return n.enabled=!1!==e.enabled,n}const Co=[];function To(){if(!et)return;const t=d.splatRelighting,e=xo.length>0;if(console.log("[StorySplat Viewer] initSplatRelighting:",{relightConfig:t,hasLights:e,lightCount:xo.length,splatEntity:!!et}),!e)return void console.log("[StorySplat Viewer] No lights, skipping splat relighting");et.script||et.addComponent("script");const n=X(),o=et.script;if(it=o?.create?.(n)??null,it){const e=0,n=t?.ambientColor||"#ffffff";it.setAmbientColor(n),it.ambientR*=e,it.ambientG*=e,it.ambientB*=e;let o=0;!0===t?.enabled&&(o=t?.allowViewerToggle?t?.viewerDefaultOn?1:0:1),it.setRelightFade(o),it.enabled=!0,at=!0,st.add(it),console.log("[StorySplat Viewer] Splat relighting initialized, startFade:",o)}}function Ao(t){if(!at||!it)return;if(t.script?.gsplatRelighting)return;t.script||t.addComponent("script");const e=X(),n=t.script,o=n?.create?.(e)??null;o&&(o.ambientR=it.ambientR,o.ambientG=it.ambientG,o.ambientB=it.ambientB,o.relightFade=it.relightFade,o.enabled=!0,st.add(o),console.log("[Relighting] Added relighting script to swap entity:",t.name))}let Po=!1;let Lo=null,ko=null;function zo(t){for(const e of xo){if(!e.light)continue;"directional"===e.light.type&&(e.light.castShadows=t,t&&(e.light.shadowResolution=2048,e.light.shadowBias=.05,e.light.normalOffsetBias=.05,e.light.shadowDistance=40))}eo.forEach(e=>{Ro(e.entity,t)})}function Ro(e,n){e.render&&(e.render.castShadows=n);for(const o of e.children)o instanceof t.Entity&&Ro(o,n)}function Do(){Lo&&(Lo.destroy(),Lo=null),ko=null}function Io(){const e=d.splatRelighting;if(e?.shadowsEnabled){!function(e,n,o){if(Lo)return Lo.setLocalPosition(0,e,0),Lo.setLocalScale(n,1,n),void(ko&&(ko.opacity=o,ko.update()));ko=new t.StandardMaterial,ko.shadowCatcher=!0,ko.blendType=t.BLEND_MULTIPLICATIVE,ko.depthWrite=!1,ko.useSkybox=!1,ko.diffuse.set(0,0,0),ko.specular.set(0,0,0),ko.opacity=o,ko.update(),Lo=new t.Entity("ShadowCatcher"),Lo.addComponent("render",{type:"plane",material:ko,castShadows:!1,receiveShadows:!0}),Lo.setLocalPosition(0,e,0),Lo.setLocalScale(n,1,n);const i=Lo.render?.meshInstances;if(i)for(const t of i)t.drawBucket=250;U.root.addChild(Lo),console.log("[StorySplat Viewer] Shadow catcher created at Y:",e,"scale:",n)}(e.shadowGroundY??0,e.shadowPlaneScale??50,e.shadowIntensity??.4),zo(!0)}else Do(),zo(!1)}const Fo=[[1,.2,.1],[.1,.6,1],[.2,1,.3],[1,.8,.1],[.8,.2,1],[1,.4,.7],[.1,1,.9]],Bo=[],Uo=[];let $o=!1;const Vo=9.8,Oo=.6,Wo=.15;function No(e){if(0===Uo.length)for(const[e,n,o]of Fo){const i=new t.StandardMaterial;i.diffuse.set(0,0,0),i.specular.set(0,0,0),i.emissive.set(e,n,o),i.emissiveIntensity=4,i.useLighting=!1,i.update(),Uo.push(i)}return Uo[e%Uo.length]}const Go=[[.85,.85,.88],[.83,.69,.22],[.72,.45,.2],[.56,.57,.58],[.66,.66,.68]],Ho=[];function Xo(e){if(0===Ho.length)for(const[e,n,o]of Go){const i=new t.StandardMaterial;i.diffuse.set(e,n,o),i.specular.set(1,1,1),i.metalness=.9,i.gloss=.8,i.useMetalness=!0,i.update(),Ho.push(i)}return Ho[e%Ho.length]}function qo(){return Ct?Ct.collisionMeshEntities:Et._collisionEntities??[]}function jo(e,n){let o=null;for(const t of qo()){const i=t.getPosition(),s=t.getLocalScale(),a=t._collisionBounds;let r,l,c,d,p,h;if(a?(r=a.center.x,l=a.center.y,c=a.center.z,d=a.halfExtents.x,p=a.halfExtents.y,h=a.halfExtents.z):(r=i.x,l=i.y,c=i.z,d=s.x/2,p=s.y/2,h=s.z/2),Math.abs(e.x-r)<d+n&&Math.abs(e.z-c)<h+n){const t=l+p;(null===o||t>o)&&(o=t)}}const i=Ct?.voxelCollisionInstance;if(i&&et){const s=new t.Mat4;s.copy(et.getWorldTransform()).invert();const a=new t.Vec3;s.transformPoint(e,a);const r=i.findGround(a.x,a.z,a.y+2*n);if(null!==r){const t=et.getWorldTransform().data,e=t[1]*a.x+t[5]*r+t[9]*a.z+t[13];(null===o||e>o)&&(o=e)}}else if(i){const t=i.findGroundWorld(e.x,e.z,e.y,2*n);null!==t&&(null===o||t>o)&&(o=t)}return o}function Yo(e,n,o){for(const t of qo()){const i=t.getPosition(),s=t.getLocalScale(),a=t._collisionMeshType;if("floor"===a||"plane"===a)continue;const r=t._collisionBounds;let l,c,d,p,h,u;r?(l=r.center.x,c=r.center.y,d=r.center.z,p=r.halfExtents.x,h=r.halfExtents.y,u=r.halfExtents.z):(l=i.x,c=i.y,d=i.z,p=s.x/2,h=s.y/2,u=s.z/2);const m=e.x-l,g=e.y-c,f=e.z-d,y=p+o-Math.abs(m),v=h+o-Math.abs(g),x=u+o-Math.abs(f);y>0&&v>0&&x>0&&(y<=v&&y<=x?(e.x+=Math.sign(m)*y,n.x=-n.x*Oo):x<=v?(e.z+=Math.sign(f)*x,n.z=-n.z*Oo):(e.y+=Math.sign(g)*v,n.y=g>0?Math.abs(n.y)*Oo:-Math.abs(n.y)*Oo))}const i=Ct?.voxelCollisionInstance;if(i){let s,a,r,l,c,d;if(et){const n=new t.Mat4;n.copy(et.getWorldTransform()).invert();const o=new t.Vec3;n.transformPoint(e,o),s=o.x,a=o.y,r=o.z}else s=e.x,a=-e.y,r=-e.z;const p=i.querySphere(s,a,r,o);if(p){if(et){const t=et.getWorldTransform().data;l=t[0]*p.x+t[4]*p.y+t[8]*p.z,c=t[1]*p.x+t[5]*p.y+t[9]*p.z,d=t[2]*p.x+t[6]*p.y+t[10]*p.z}else l=p.x,c=-p.y,d=-p.z;e.x+=l,e.y+=c,e.z+=d;const t=Math.sqrt(l*l+c*c+d*d);if(t>1e-6){const e=l/t,o=c/t,i=d/t,s=n.x*e+n.y*o+n.z*i;s<0&&(n.x-=(1+Oo)*s*e,n.y-=(1+Oo)*s*o,n.z-=(1+Oo)*s*i)}}}}function Zo(t){const e=xo.indexOf(t.entity);e>=0&&xo.splice(e,1),t.entity.destroy()}function Qo(){if(!$o){if($o=!0,!at&&et){d.splatRelighting={...d.splatRelighting,enabled:!0},To();for(const t of st)t.setRelightFade(1)}console.log("[Playground] Started — press F to spawn light balls, relightingEnabled:",at,"splatEntity:",!!et)}}function Ko(){if($o){$o=!1;for(const t of Bo)Zo(t);Bo.length=0,console.log("[Playground] Stopped")}}window.__storysplat_playground_handler&&window.removeEventListener("keydown",window.__storysplat_playground_handler);const Jo=e=>{e.shiftKey&&"KeyP"===e.code?(e.preventDefault(),$o?Ko():Qo()):"KeyF"===e.code&&$o?(console.log("[Playground] F pressed — spawning light ball"),function(){Bo.length>=16&&Zo(Bo.shift());const e=Math.floor(Math.random()*Fo.length),[n,o,i]=Fo[e],s=bt.getPosition(),a=bt.forward,r=new t.Vec3(s.x+2*a.x,s.y+2*a.y,s.z+2*a.z);console.log("[Playground] Spawning ball #"+(Bo.length+1),"cam:",s.x.toFixed(2),s.y.toFixed(2),s.z.toFixed(2),"fwd:",a.x.toFixed(2),a.y.toFixed(2),a.z.toFixed(2),"spawn:",r.x.toFixed(2),r.y.toFixed(2),r.z.toFixed(2),"color:",[n.toFixed(1),o.toFixed(1),i.toFixed(1)],"collisionEntities:",qo().length,"lightEntities:",xo.length,"relightingEnabled:",at);const l=new t.Entity("playground-ball");l.addComponent("render",{type:"sphere",material:No(e),castShadows:!!d.splatRelighting?.shadowsEnabled}),l.setLocalScale(.3,.3,.3),l.addComponent("light",{type:"point",color:new t.Color(n,o,i),intensity:2,range:8,castShadows:!1}),l.setPosition(r),U.root.addChild(l),xo.push(l);const c=new t.Vec3(8*a.x+2*(Math.random()-.5),8*a.y+3,8*a.z+2*(Math.random()-.5));Bo.push({entity:l,velocity:c,age:0,lifetime:12,baseIntensity:2,radius:Wo})}()):"KeyG"===e.code&&$o&&function(){d.splatRelighting?.shadowsEnabled||(console.log("[Playground] Auto-enabling shadows for shadow ball testing"),d.splatRelighting={...d.splatRelighting,shadowsEnabled:!0},Io()),Bo.length>=16&&Zo(Bo.shift());const e=Math.floor(Math.random()*Go.length),n=bt.getPosition(),o=bt.forward,i=new t.Vec3(n.x+2*o.x,n.y+2*o.y,n.z+2*o.z);console.log("[Playground] G pressed — spawning shadow ball at",i.x.toFixed(2),i.y.toFixed(2),i.z.toFixed(2));const s=new t.Entity("playground-shadow-ball");s.addComponent("render",{type:"sphere",material:Xo(e),castShadows:!0}),s.setLocalScale(.3,.3,.3),s.setPosition(i),U.root.addChild(s);const a=new t.Vec3(8*o.x+2*(Math.random()-.5),8*o.y+3,8*o.z+2*(Math.random()-.5));Bo.push({entity:s,velocity:a,age:0,lifetime:12,baseIntensity:0,radius:Wo})}()};function ti(t){switch(t){case"linear":return"linear";case"inverse":return"inverse";default:return"exponential"}}window.__storysplat_playground_handler=Jo,window.addEventListener("keydown",Jo),console.log("[Playground] Toggle registered — Shift+P to start/stop, F = light ball, G = shadow ball");const ei=new Map;const ni=new Set;function oi(){Hn||(Gn.forEach(t=>{Xn.set(t,t.volume),t.volume=0}),qn.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);e&&(e._storedVolume=e.volume,e.volume=0)}),ei.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}),Hn=!0,console.log("[Audio] All audio muted"))}function ii(){Hn&&(Gn.forEach(t=>{const e=Xn.get(t);t.volume=void 0!==e?e:1}),qn.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);if(e){const t=e._storedVolume;e.volume=void 0!==t?t:1}}),ei.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}),Hn=!1,console.log("[Audio] All audio unmuted"))}const si=[];function ai(e,n=!1,o=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=.005,n?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=o,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 o=new vt(U,e,{autoPlay:!0,onReady:()=>{if(lt)return console.log(`[Hotspot] Ignoring GIF load - viewer was destroyed: ${e}`),void o.destroy();o.texture&&(o.texture.premultiplyAlpha=!0,n?(s.diffuseMap=o.texture,s.opacityMap=o.texture):(s.emissiveMap=o.texture,s.opacityMap=o.texture),s.opacityMapChannel="a",s.update(),console.log(`[Hotspot] GIF texture loaded: ${e}, useLighting=${n}`),i&&i())},onError:n=>{console.error(`[Hotspot] Failed to load GIF: ${e}`,n),s.opacity=.3,s.emissive=new t.Color(1,0,0),s.update(),i&&i()}});si.push(o)}else{const o=new Image;o.crossOrigin="anonymous",o.onload=()=>{if(lt)return void console.log(`[Hotspot] Ignoring texture load - viewer was destroyed: ${e}`);const a=new t.Texture(U.graphicsDevice,{width:o.width,height:o.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.premultiplyAlpha=!0,a.setSource(o),n?(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=${n}`),i&&i()},o.onerror=n=>{console.error(`[Hotspot] Failed to load texture: ${e}`,n),s.opacity=.3,s.emissive=new t.Color(1,0,0),s.update(),i&&i()},o.src=e}return s}function ri(e,n,o){const i=n/2,s=e/2,a=o/2,r=Math.cos(i),l=Math.sin(i),c=Math.cos(s),d=Math.sin(s),p=Math.cos(a),h=Math.sin(a),u=r*c*p+l*d*h,m=r*d*p+l*c*h,g=l*c*p-r*d*h,f=r*c*h-l*d*p;return new t.Quat(-m,-g,f,u)}function li(e,n,o,i){const s=(new t.Quat).setFromEulerAngles(90,0,0),a=ri(n,o,i);e.setRotation((new t.Quat).mul2(a,s))}function ci(e,n){const o=new t.Entity(`hotspot-${e.id||n}`),i=e.position||{_x:0,_y:0,_z:0};o.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=a._x??a.x??1,l=a._y??a.y??1,c=a._z??a.z??1,d=e.rotation||{_x:0,_y:0,_z:0},p=d._x??d.x??0,h=d._y??d.y??0,u=d._z??d.z??0;if(o.setRotation(ri(p,h,u)),"sphere"===e.type){o.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1});const n=new t.StandardMaterial,i=Wn(e.color||"#ffffff");n.diffuse=i,n.emissive=i.clone(),n.emissive.mulScalar(.5);const s=e.opacity??1;s>=.95?(n.opacity=1,n.blendType=t.BLEND_NONE,n.depthTest=!0,n.depthWrite=!0):(n.opacity=s,n.blendType=t.BLEND_ADDITIVEALPHA,n.depthTest=!0,n.depthWrite=!0),n.update(),o.render.material=n,o.setLocalScale(.2*r,.2*l,.2*c)}else if("image"===e.type&&e.imageUrl){o.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),o.targetOpacity=t,o.textureLoaded=!1,o.hiddenUntilTextureLoaded=!0,o.enabled=!1;const n=!0===e.useLighting,s=ai(e.imageUrl,n,t,()=>{o.textureLoaded=!0,o.visibilityRange&&!o.shouldBeVisible||(o.enabled=!0),o.hiddenUntilTextureLoaded=!1});o.render.material=s,o.setLocalScale(r,c,l),li(o,p,h,u),o.hotspotMaterial=s,console.log(`[Hotspot] Created image hotspot: ${e.title}, pos=(${(i._x??i.x??0).toFixed(2)}, ${(i._y??i.y??0).toFixed(2)}, ${(i._z??i.z??0).toFixed(2)}), rot=(${p.toFixed(3)}, ${h.toFixed(3)}, ${u.toFixed(3)}), scale=(${r.toFixed(3)}, ${l.toFixed(3)}, ${c.toFixed(3)}), opacity=${t}`)}else if("video"===e.type&&e.videoUrl){o.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,d=(t=>{const e=t.toLowerCase();return e.endsWith(".webm")||e.includes("format=webm")||e.includes("video/webm")})(s)&&!1!==e.webmHasAlpha,m=(n,o,i=!1)=>{const s=document.createElement("video");s.src=n,s.loop=!1!==e.videoLoop,s.crossOrigin="anonymous",s.playsInline=!0,s.preload="metadata",s.muted=!!o||!1!==e.videoMuted,"autoplay"===e.mediaTriggerMode&&(s.autoplay=!0,s.muted=!0);const a=new t.Texture(U.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}},g=m(s,!1,d),f=g.video,y=g.texture;if(e.videoBackupUrl){const t=e.videoBackupUrl;f.onerror=()=>{console.log(`[Hotspot] Main video failed, trying backup: ${t}`),f.src=t,f.load()}}const v=new t.StandardMaterial;v.useLighting=!1,v.emissiveMap=y,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,d&&(v.opacityMap=y,v.blendType=t.BLEND_PREMULTIPLIED,v.alphaTest=.01,console.log(`[Hotspot] WebM video with alpha enabled: ${e.title}`));let x=null,b=null;if(a){const n=m(a,!0,!1);x=n.video,b=n.texture,v.opacityMap=b,v.opacityMapChannel="r",v.blendType=t.BLEND_PREMULTIPLIED,v.alphaTest=.01,f.addEventListener("play",()=>{x&&x.paused&&(x.currentTime=f.currentTime,x.play().catch(console.warn))}),f.addEventListener("pause",()=>{x&&!x.paused&&x.pause()}),f.addEventListener("seeked",()=>{x&&(x.currentTime=f.currentTime)}),console.log(`[Hotspot] iOS alpha mask video enabled: ${e.title}, alphaUrl=${a.substring(0,50)}...`)}v.update(),U.on("update",()=>{f.readyState===f.HAVE_ENOUGH_DATA&&y.upload(),x&&b&&x.readyState===x.HAVE_ENOUGH_DATA&&b.upload()});const w={x:r,y:l,z:c};if(f.addEventListener("loadedmetadata",()=>{const t=f.videoWidth,e=f.videoHeight;if(t>0&&e>0){const n=t/e;1===w.x&&1===w.y&&o.setLocalScale(n*w.y,w.z,w.y)}}),o.render.material=v,o.setLocalScale(r,c,l),li(o,p,h,u),o.videoElement=f,o.alphaVideoElement=x,o.hotspotMaterial=v,o.mediaTriggerMode=e.mediaTriggerMode||"click",o.proximityDistance=e.proximityDistance||5,o.isVideoPlaying=!1,"click"!==e.mediaTriggerMode&&e.mediaTriggerMode||(f.addEventListener("loadeddata",()=>{f.currentTime=0,y.upload(),x&&b&&(x.currentTime=0,b.upload()),console.log(`[Hotspot] First frame loaded for: ${e.title}`)},{once:!0}),f.load(),x&&x.load()),!0!==e.videoMuted){const t=function(t,e,n){if(!n.videoSpatialAudio&&!1!==n.videoMuted)return null;try{const o=new(window.AudioContext||window.webkitAudioContext);Nn.push(o);const i=o.createMediaElementSource(e),s=o.createPanner();s.panningModel="HRTF",s.distanceModel=n.videoDistanceModel||"linear",s.refDistance=void 0!==n.videoRefDistance?n.videoRefDistance:1,s.maxDistance=void 0!==n.videoMaxDistance?n.videoMaxDistance:100,s.rolloffFactor=void 0!==n.videoRolloffFactor?n.videoRolloffFactor:1;const a=t.getPosition();return s.setPosition(a.x,a.y,a.z),i.connect(s),s.connect(o.destination),U.on("update",()=>{if(!t||!t.getPosition)return;const e=t.getPosition();if(s.setPosition(e.x,e.y,e.z),bt&&bt.getPosition){const t=bt.getPosition(),e=bt.forward,n=bt.up;o.listener.positionX?(o.listener.positionX.value=t.x,o.listener.positionY.value=t.y,o.listener.positionZ.value=t.z,o.listener.forwardX.value=e.x,o.listener.forwardY.value=e.y,o.listener.forwardZ.value=e.z,o.listener.upX.value=n.x,o.listener.upY.value=n.y,o.listener.upZ.value=n.z):(o.listener.setPosition(t.x,t.y,t.z),o.listener.setOrientation(e.x,e.y,e.z,n.x,n.y,n.z))}}),console.log(`[Audio] Video spatial audio setup for hotspot: ${n.title}, refDist=${s.refDistance}, maxDist=${s.maxDistance}`),{audioCtx:o,source:i,panner:s}}catch(t){return console.warn("[Audio] Failed to setup video spatial audio:",t),null}}(o,f,e);t&&(o.videoSpatialAudio=t)}if("click"===e.mediaTriggerMode||!e.mediaTriggerMode||"autoplay"===e.mediaTriggerMode&&!1===e.videoMuted){const i=new t.Entity("video-overlay-"+(e.id||n));i.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1}),i.setLocalPosition(0,.02,0),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(U.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,o.addChild(i);const d=()=>{"autoplay"===e.mediaTriggerMode&&!1===e.videoMuted?i.enabled=f.muted:i.enabled=f.paused};f.addEventListener("play",d),f.addEventListener("pause",d),f.addEventListener("volumechange",d),i.enabled=!0,o.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=${!!o.videoSpatialAudio}`)}else if("gif"===e.type&&e.gifUrl){o.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1});let n=1;"animated"===e.opacityMode&&e.opacityAnimation?n=void 0!==e.opacityAnimation.startOpacity?e.opacityAnimation.startOpacity:1:void 0!==e.opacity&&(n=e.opacity),o.targetOpacity=n,o.textureLoaded=!1,o.hiddenUntilTextureLoaded=!0,o.enabled=!1;const i=!0===e.useLighting,s=new t.StandardMaterial;s.blendType=t.BLEND_PREMULTIPLIED,s.opacity=n,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=new vt(U,e.gifUrl,{autoPlay:!0,onReady:()=>{if(lt)a.destroy();else if(a.texture){a.texture.premultiplyAlpha=!0,i||(s.emissiveMap=a.texture),s.diffuseMap=a.texture,s.opacityMap=a.texture,s.opacityMapChannel="a",s.update(),o.render.material=s;const t=(a.texture.width||256)/(a.texture.height||256);1===r&&1===l?o.setLocalScale(t,c,1):o.setLocalScale(r,c,l),li(o,p,h,u),o.textureLoaded=!0,o.gifTexture=a.texture,o.hotspotMaterial=s,o.animatedGifTexture=a,o.visibilityRange&&!o.shouldBeVisible||(o.enabled=!0),o.hiddenUntilTextureLoaded=!1,console.log(`[Hotspot] Created GIF hotspot: ${e.title}, opacity=${n}, useLighting=${i}`)}},onError:n=>{console.error("[Hotspot] Failed to load GIF:",e.gifUrl,n),o.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1});const i=new t.StandardMaterial;i.diffuse=Wn(e.color||"#FF00FF"),i.opacity=.8,i.blendType=t.BLEND_NORMAL,i.update(),o.render.material=i,o.setLocalScale(.2*r,.2*l,.2*c),o.enabled=!0,o.hiddenUntilTextureLoaded=!1}});si.push(a)}else{o.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1});const n=new t.StandardMaterial,i=Wn(e.color||"#CC5833");n.diffuse=i,n.emissive=i.clone(),n.emissive.mulScalar(.5);const s=e.opacity??1;n.opacity=s,n.blendType=t.BLEND_ADDITIVEALPHA,n.depthTest=!0,n.depthWrite=!0,n.update(),o.render.material=n,o.setLocalScale(.2*r,.2*l,.2*c)}o.addComponent("collision",{type:"sphere"===e.type?"sphere":"box",radius:.1,halfExtents:new t.Vec3(.5,.5,.05)}),o.hotspotData=e;const m=function(t,e){if(!e.audioUrl)return null;const n=document.createElement("audio");if(n.src=e.audioUrl,n.loop=e.audioLoop||!1,n.volume=void 0!==e.audioVolume?e.audioVolume:1,n.crossOrigin="anonymous",Gn.push(n),e.audioSpatial){const o=new(window.AudioContext||window.webkitAudioContext);Nn.push(o);const i=o.createMediaElementSource(n),s=o.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(o.destination);const r=()=>{if(!t||!t.getPosition)return;const e=t.getPosition();if(s.setPosition(e.x,e.y,e.z),bt&&bt.getPosition){const t=bt.getPosition(),e=bt.forward,n=bt.up;o.listener.positionX?(o.listener.positionX.value=t.x,o.listener.positionY.value=t.y,o.listener.positionZ.value=t.z,o.listener.forwardX.value=e.x,o.listener.forwardY.value=e.y,o.listener.forwardZ.value=e.z,o.listener.upX.value=n.x,o.listener.upY.value=n.y,o.listener.upZ.value=n.z):(o.listener.setPosition(t.x,t.y,t.z),o.listener.setOrientation(e.x,e.y,e.z,n.x,n.y,n.z))}};return U.on("update",r),console.log(`[Audio] Spatial audio setup for hotspot: ${e.title}, refDist=${s.refDistance}, maxDist=${s.maxDistance}`),{audio:n,audioCtx:o,source:i,panner:s,updateAudioPosition:r}}return console.log(`[Audio] Non-spatial audio setup for hotspot: ${e.title}`),{audio:n}}(o,e);if(m&&(o.audioElements=m,console.log(`[StorySplat Viewer] Audio setup for hotspot: ${e.title||"Untitled"}`)),e.billboard){const n=o.render?.meshInstances?.[0]?.material;n&&(n.cull=t.CULLFACE_FRONT,n.update());const i=o.getEulerAngles().clone(),s=void 0!==e.billboardRangeStart||void 0!==e.billboardRangeEnd;o._billboardActive=!s,o._billboardOriginalRotation=i,U.on("update",()=>{o._billboardActive&&(o.lookAt(bt.getPosition()),o.rotateLocal(90,0,0))})}return e.visibilityRange&&(o.visibilityRange=e.visibilityRange,o.enabled=!1),U.root.addChild(o),Bn.push(o),console.log(`[StorySplat Viewer] Created hotspot: ${e.title||"Untitled"}`),o}function di(){const e=100*Je,n=d.waypoints?.length||1,o=Math.round(Je*Math.max(1,n-1)),i=bt.getPosition();Bn.forEach(n=>{const s=n.hotspotData;if(!s)return;let a=!0;if(s.alwaysVisible)a=!0;else{const t=n.visibilityRange;t&&("waypoint"===t.type?a=o>=t.start&&o<=t.end:"percentage"===t.type&&(a=e>=t.start&&e<=t.end))}if(n.shouldBeVisible=a,s.billboard&&(void 0!==s.billboardRangeStart||void 0!==s.billboardRangeEnd)){const o=s.billboardRangeStart??0,i=s.billboardRangeEnd??100,a=e>=o&&e<=i,r=n._billboardActive;if(n._billboardActive=a,a!==r){const e=n.render?.meshInstances?.[0]?.material;e&&(e.cull=a?t.CULLFACE_FRONT:t.CULLFACE_NONE,e.update())}if(!a&&n._billboardOriginalRotation){const t=n._billboardOriginalRotation;n.setEulerAngles(t.x,t.y,t.z)}}if(n.hiddenUntilTextureLoaded?n.enabled=!1:n.enabled=a,n.videoElement&&"video"===s.type){const t=n.mediaTriggerMode||"click";if("proximity"===t){const t=n.getPosition(),e=i.distance(t),o=n.proximityDistance||5;e<=o&&!n.isVideoPlaying?(pi(n,s),console.log(`[Hotspot] Proximity play: ${s.title}, distance=${e.toFixed(2)}`)):e>o&&n.isVideoPlaying&&(hi(n),console.log(`[Hotspot] Proximity pause: ${s.title}, distance=${e.toFixed(2)}`))}"scroll"===t&&(a&&!n.isVideoPlaying?(pi(n,s),console.log(`[Hotspot] Scroll play: ${s.title}, scroll=${e.toFixed(1)}%`)):!a&&n.isVideoPlaying&&(hi(n),console.log(`[Hotspot] Scroll pause: ${s.title}, scroll=${e.toFixed(1)}%`)))}if("animated"===s.opacityMode&&s.opacityAnimation){if("image"===s.type&&!n.textureLoaded)return;const t=s.opacityAnimation,o=t.startPercent??0,i=t.endPercent??100,a=void 0!==t.startOpacity?t.startOpacity:1,r=void 0!==t.endOpacity?t.endOpacity:1;let l;if(e<=o)l=a;else if(e>=i)l=r;else{l=a+(r-a)*((e-o)/(i-o))}if(l=Math.max(0,Math.min(1,l)),n.hotspotMaterial)n.hotspotMaterial.opacity=l,n.hotspotMaterial.update();else if(n.render&&n.render.material){const t=n.render.material;t.opacity=l,t.update?.()}}})}function pi(t,e){const n=t.videoElement,o=t.alphaVideoElement;if(n){if("autoplay"!==t.mediaTriggerMode&&(n.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))}n.play().catch(t=>console.warn("Video play failed:",t)),o&&o.play().catch(t=>console.warn("Alpha video play failed:",t)),t.isVideoPlaying=!0}}function hi(t){const e=t.videoElement,n=t.alphaVideoElement;e&&(e.pause(),n&&n.pause(),t.isVideoPlaying=!1)}function ui(e,n){const o=new t.Entity(`portal-${n}`),i=e.position||{_x:0,_y:0,_z:0};o.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,d=e.rotation||{_x:0,_y:0,_z:0},p=d._x??d.x??0,h=d._y??d.y??0,u=d._z??d.z??0;if(o.setRotation(ri(p,h,u)),"sphere"===e.type){o.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1});const n=new t.StandardMaterial,i=Wn(e.color||"#9C27B0");n.diffuse=i,n.emissive=i.clone(),n.emissive.mulScalar(.5);const s=e.opacity??.8;s>=.95?(n.opacity=1,n.blendType=t.BLEND_NONE,n.depthTest=!0,n.depthWrite=!0):(n.opacity=s,n.blendType=t.BLEND_ADDITIVEALPHA,n.depthTest=!0,n.depthWrite=!0),n.update(),o.render.material=n,o.setLocalScale(.2*r,.2*l,.2*c)}else if("image"===e.type&&e.imageUrl){o.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1});const t=!0===e.useLighting,n=e.opacity??1;o.textureLoaded=!1,o.hiddenUntilTextureLoaded=!0,o.enabled=!1;const i=ai(e.imageUrl,t,n,()=>{o.textureLoaded=!0,o.visibilityRange&&!o.shouldBeVisible||(o.enabled=!0),o.hiddenUntilTextureLoaded=!1});o.render.material=i,o.setLocalScale(r,c,l),li(o,p,h,u),o.portalMaterial=i,console.log(`[Portal] Created image portal: ${e.title||e.targetSceneName||"Untitled"}`)}else if("video"===e.type&&e.videoUrl){o.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1});const n=document.createElement("video");n.src=e.videoUrl,n.loop=!0,n.muted=!0,n.crossOrigin="anonymous",n.playsInline=!0,n.autoplay=!0;const i=new t.Texture(U.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(n);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(),o.render.material=s,o.setLocalScale(r,c,l),li(o,p,h,u),U.on("update",()=>{n.readyState>=n.HAVE_CURRENT_DATA&&i.setSource(n)}),n.play().catch(t=>console.log("[Portal] Video autoplay blocked:",t)),o.videoElement=n,o.portalMaterial=s,console.log(`[Portal] Created video portal: ${e.title||e.targetSceneName||"Untitled"}`)}else if("gif"===e.type&&e.gifUrl){o.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1});const t=!0===e.useLighting,n=e.opacity??1,i=ai(e.gifUrl,t,n,()=>{o.textureLoaded=!0,o.visibilityRange&&!o.shouldBeVisible||(o.enabled=!0),o.hiddenUntilTextureLoaded=!1});o.render.material=i,o.setLocalScale(r,c,l),li(o,p,h,u),o.textureLoaded=!1,o.hiddenUntilTextureLoaded=!0,o.enabled=!1,o.portalMaterial=i,console.log(`[Portal] Created GIF portal: ${e.title||e.targetSceneName||"Untitled"}`)}else{o.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1});const n=new t.StandardMaterial,i=Wn(e.color||"#9C27B0");n.diffuse=i,n.emissive=i.clone(),n.emissive.mulScalar(.5);const s=e.opacity??.8;n.opacity=s,n.blendType=t.BLEND_ADDITIVEALPHA,n.depthTest=!0,n.depthWrite=!0,n.update(),o.render.material=n,o.setLocalScale(.2*r,.2*l,.2*c)}if(o.addComponent("collision",{type:"sphere"===e.type?"sphere":"box",radius:.1,halfExtents:new t.Vec3(.5,.5,.05)}),o.portalData=e,e.billboard){const e=o.render?.meshInstances?.[0]?.material;e&&(e.cull=t.CULLFACE_FRONT,e.update()),U.on("update",()=>{o.enabled&&(o.lookAt(bt.getPosition()),o.rotateLocal(90,0,0))})}return e.visibilityRange&&(o.visibilityRange=e.visibilityRange,o.enabled=!1),U.root.addChild(o),Un.push(o),console.log(`[StorySplat Viewer] Created portal: ${e.title||e.targetSceneName||"Untitled"} -> ${e.targetSceneId}`),o}function mi(){const t=100*Je,e=d.waypoints?.length||1,n=Math.round(Je*Math.max(1,e-1)),o=bt.getPosition();Un.forEach(e=>{const i=e.portalData;if(!i)return;let s=!0;const a=e.visibilityRange;if(a&&("waypoint"===a.type?s=n>=a.start&&n<=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(),n=o.distance(t),s=i.proximityDistance||2;n<=s&&!e.proximityTriggered?(e.proximityTriggered=!0,console.log(`[Portal] Proximity triggered: ${i.title||i.targetSceneName}, navigating to scene ${i.targetSceneId}`),fi(i)):n>s&&(e.proximityTriggered=!1)}})}function gi(e,n){const o=bt.camera.screenToWorld(e,n,bt.camera.nearClip),i=bt.camera.screenToWorld(e,n,bt.camera.farClip);let s=null;Un.forEach(e=>{if(!e.enabled)return;const n=e.getPosition(),a=(new t.Vec3).sub2(i,o).normalize(),r=(new t.Vec3).sub2(n,o).dot(a);if(r<0)return;const l=(new t.Vec3).add2(o,a.clone().mulScalar(r)).distance(n),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 fi(t){if(Vt(),!t.targetSceneId)return void console.warn("[Portal] No target scene ID specified");console.log(`[StorySplat Viewer] Portal activated: navigating to scene ${t.targetSceneId}`);const n=i.listenerCount("portalActivated")>0;if(i.emit("portalActivated",{portalId:t.id,targetSceneId:t.targetSceneId,targetSceneName:t.targetSceneName}),n)console.log("[Portal] External portal handler detected, deferring navigation");else{!function(t,e){const n=t.querySelector(".storysplat-portal-loading");n&&n.remove();try{"static"===window.getComputedStyle(t).position&&(t.style.position="relative")}catch{}const o=document.createElement("div");o.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(P,"loadingScene").replace("{name}",e),o.appendChild(i),o.appendChild(s),Object.assign(o.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 ${m}`,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(o)}(e,t.targetSceneName||t.title||"scene");try{const n=`https://discover.storysplat.com/api/scene/${t.targetSceneId}`;console.log(`[Portal] Fetching scene from: ${n}`);const o=await fetch(n);if(!o.ok)throw new Error(`Failed to fetch scene: ${o.status} ${o.statusText}`);const i=await o.json(),s=i.data||i;s.name&&function(t,e){const n=t.querySelector(".storysplat-portal-loading-text");n&&(n.textContent=l(P,"loadingScene").replace("{name}",e))}(e,s.name);!function(){console.log("[Portal] Cleaning up current scene for navigation..."),lt=!0,In(),Gn.forEach(t=>{t.pause(),t.src=""}),Gn.length=0,Xn.clear(),Nn.forEach(t=>{t.close().catch(()=>{})}),Nn.length=0,qn.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);e&&e.stop(),t.entity.destroy()}),qn.clear(),ei.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);e&&e.stop(),t.entity.destroy()}),ei.clear(),Bn.forEach(t=>{t.videoElement&&(t.videoElement.pause(),t.videoElement.src=""),t.alphaVideoElement&&(t.alphaVideoElement.pause(),t.alphaVideoElement.src="")}),si.forEach(t=>t.destroy()),si.length=0,po(),Bn.forEach(t=>{t.destroy()}),Bn.length=0,Un.forEach(t=>{t.destroy()}),Un.length=0,et&&(et.destroy(),et=null);it=null,st.clear(),at=!1,Ko(),window.removeEventListener("keydown",Jo),qt.destroy(),Yt.destroy(),Xt.destroy(),vi.removeEventListener("mousemove",xi),Do(),xo.forEach(t=>{t.destroy()}),xo.length=0,Zn.forEach(t=>{t.destroy()}),Zn.clear(),ho&&(ho.destroy(),ho=null);ct&&(ct.destroy(),ct=null);Ct&&(Ct.destroy(),Ct=null);const t=U.__htmlMeshManager;t&&t.destroy();const n=U.__customScriptSystem;n&&n.dispose();window.removeEventListener("resize",Di),F.preloader&&F.preloader.remove();F.scrollControls&&F.scrollControls.remove();F.fullscreenButton&&F.fullscreenButton.remove();F.helpButton&&F.helpButton.remove();F.helpPanel&&F.helpPanel.remove();F.waypointInfo&&F.waypointInfo.remove();F.watermark&&F.watermark.remove();const o=document.getElementById("storysplat-viewer-styles");o&&o.remove();e.classList.remove("storysplat-viewer-container"),U.destroy(),console.log("[Portal] Cleanup complete")}(),await new Promise(t=>setTimeout(t,100)),B&&B.parentNode&&B.remove();await Ft(e,s,{lazyLoad:!1});return yi(e),void console.log(`[Portal] Successfully navigated to scene: ${t.targetSceneId}`)}catch(t){console.error("[Portal] Navigation failed:",t),yi(e);const n=document.createElement("div");n.className="storysplat-portal-error",n.textContent=`Failed to load scene: ${t.message}`,Object.assign(n.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(n),setTimeout(()=>n.remove(),3e3)}}}function yi(t){const e=t.querySelector(".storysplat-portal-loading");e&&e.remove()}i.on("progressUpdate",()=>{di()}),setTimeout(()=>{di()},100),i.on("progressUpdate",()=>{mi()}),setTimeout(()=>{mi()},100);const vi=U.graphicsDevice.canvas,xi=t=>{const e=vi.getBoundingClientRect();Qt=t.clientX-e.left,Kt=t.clientY-e.top};N||vi.addEventListener("mousemove",xi);let bi=!1,wi=!1,Si=0,_i=0;vi.addEventListener("pointerdown",t=>{0===t.button&&(wi=!0,bi=!1,Si=t.clientX,_i=t.clientY)}),vi.addEventListener("pointermove",t=>{if(!wi)return;const e=t.clientX-Si,n=t.clientY-_i;e*e+n*n>25&&(bi=!0)});const Mi=()=>{wi=!1};function Ei(e,n){const o=bt.camera.screenToWorld(e,n,bt.camera.nearClip),i=bt.camera.screenToWorld(e,n,bt.camera.farClip);let s=null;Bn.forEach(e=>{if(!e.enabled)return;const n=e.getPosition(),a=(new t.Vec3).sub2(i,o).normalize(),r=(new t.Vec3).sub2(n,o).dot(a);if(r<0)return;const l=(new t.Vec3).add2(o,a.clone().mulScalar(r)).distance(n),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}vi.addEventListener("pointerup",Mi),vi.addEventListener("pointercancel",Mi);let Ci=null,Ti=!1,Ai=!1;const Pi=e.querySelector(".storysplat-hotspot-popup"),Li=e.querySelector(".storysplat-hotspot-overlay");function ki(t,e){Me||(Me=!0,E(F)),S(F,!1),"walk"!==Lt?"explore"===Lt&&async function(t,e){if("explore"!==Lt)return;console.log("[StorySplat Viewer] Double-click focus at:",t,e);const n=Ei(t,e);if(n){const t=n.entity.getPosition();return console.log("[StorySplat Viewer] Focusing on hotspot at:",t.x,t.y,t.z),void("fly"===Et.mode?Et.flyTo(t):Et.focus(t,!1))}try{const n=.25;Wt.resize(Math.floor(vi.clientWidth*n),Math.floor(vi.clientHeight*n));const o=U.scene.layers.getLayerByName("World");if(!o)return void console.warn("[StorySplat Viewer] World layer not found");Wt.prepare(bt.camera,U.scene,[o]);const i=Math.floor(t*n),s=Math.floor(e*n),a=await Wt.getWorldPointAsync(i,s);if(a)if(isFinite(a.x)&&isFinite(a.y)&&isFinite(a.z)){const t=bt.getPosition().distance(a);if(t>.3&&t<500)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"===Et.mode?Et.flyTo(a):Et.focus(a,!1));console.log("[StorySplat Viewer] Discarding pick result — distance out of range:",t.toFixed(2))}else console.log("[StorySplat Viewer] Discarding non-finite pick result:",a.x,a.y,a.z);console.log("[StorySplat Viewer] No valid pick result at click point")}catch(t){console.warn("[StorySplat Viewer] Picking failed:",t)}}(t,e):$t(!Bt)}Pi&&(Pi.addEventListener("mouseenter",()=>{Ti=!0}),Pi.addEventListener("mouseleave",()=>{Ti=!1,Ci&&"hover"===Ci.activationMode&&(Pi.classList.remove("visible"),Li&&Li.classList.remove("visible"),Ci=null)})),vi.addEventListener("mousemove",t=>{const n=vi.getBoundingClientRect(),o=t.clientX-n.left,i=t.clientY-n.top,s=gi(o,i);if(s&&s.portal){const t=s.portal.activationMode||"click";return vi.style.cursor="click"===t?"pointer":"default",void(ue=!0)}const a=Ei(o,i);if(a&&a.hotspot){const t=a.hotspot,n=t.activationMode||"click";if("click"===n||"hover"===n||"video"===t.type?(vi.style.cursor="pointer",ue=!0):(vi.style.cursor="default",ue=!1),"hover"===t.activationMode&&Ci!==t){Ci=t;!(c&&p===t.id)&&(t.information||t.photoUrl||t.iframeUrl||t.externalLinkUrl||t.modelUrl)&&v(e,t,P)}}else if(Ai||(vi.style.cursor="default",ue=!1),Ci&&"hover"===Ci.activationMode&&!Ti){const t=e.querySelector(".storysplat-hotspot-popup"),n=e.querySelector(".storysplat-hotspot-overlay");t&&t.classList.remove("visible"),n&&n.classList.remove("visible"),Ci=null}}),vi.addEventListener("click",n=>{if(bi)return void(bi=!1);const o=vi.getBoundingClientRect(),s=n.clientX-o.left,a=n.clientY-o.top,r=gi(s,a);if(null!==r&&r.portal){const t=r.portal;if(console.log("[StorySplat Viewer] Portal clicked:",t.title||t.targetSceneName),c)return void i.emit("portalClick",{portal:t});if("click"===(t.activationMode||"click")){!1!==t.confirmNavigation&&(t.title||t.targetSceneName||t.targetSceneId)&&$n?(t=>{if(!$n)return;const e=$n.querySelector(".storysplat-portal-popup-title");e&&(t.title?e.textContent=t.title:t.targetSceneName?e.textContent=`${l(P,"switchScenes").replace("?","")} ${t.targetSceneName}?`:e.textContent=l(P,"switchScenes")),Vn=t,$n.classList.add("visible")})(t):fi(t)}return}const h=Ei(s,a);if(null!==h){const n=h.entity,o=h.hotspot;if(console.log("[StorySplat Viewer] Hotspot clicked:",o.title),i.emit("hotspotClick",{hotspot:o}),n.audioElements&&n.audioElements.audio){const t=n.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:",o.title)):(e.pause(),console.log("[Audio] Hotspot audio paused:",o.title))}if(n.videoElement&&("click"===n.mediaTriggerMode||"autoplay"===n.mediaTriggerMode)){const t=n.videoElement;n.alphaVideoElement,"autoplay"===n.mediaTriggerMode&&!t.paused&&t.muted?(t.muted=(o.videoMuted,!1),console.log("[Hotspot] Video unmuted by click")):t.paused?(pi(n,o),console.log("[Hotspot] Video started")):(hi(n),console.log("[Hotspot] Video paused"))}const s=o.activationMode||"click",a=o.title||o.information||o.photoUrl||o.iframeUrl||o.externalLinkUrl||o.modelUrl;"click"===s&&a&&(ge?xe?be():function(e){if(!ge)return;ye||(ye=new t.Entity("arContentPlane"),ye.addComponent("render",{type:"plane"}),ye.setLocalScale(.8,1,.45),U.root.addChild(ye));const n=512,o=288,i=document.createElement("canvas");i.width=n,i.height=o;const s=i.getContext("2d"),a=e.backgroundColor||"rgba(20, 20, 20, 0.95)";s.fillStyle=a,s.fillRect(0,0,n,o),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,n,o,i,s){const a=e.split(" ");let r="",l=o;for(const e of a){const o=r+e+" ";t.measureText(o).width>i&&r?(t.fillText(r.trim(),n,l),r=e+" ",l+=s):r=o}return t.fillText(r.trim(),n,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",ve||(ve=new t.Texture(U.graphicsDevice,{width:n,height:o,format:t.PIXELFORMAT_RGBA8,mipmaps:!1,minFilter:t.FILTER_LINEAR,magFilter:t.FILTER_LINEAR})),ve.setSource(i);const c=new t.StandardMaterial;c.diffuse=new t.Color(0,0,0),c.diffuseMap=ve,c.emissive=new t.Color(1,1,1),c.emissiveMap=ve,c.useLighting=!1,c.blendType=t.BLEND_NORMAL,c.cull=t.CULLFACE_NONE,c.depthWrite=!0,c.update(),ye.render&&ye.render.meshInstances[0]&&(ye.render.meshInstances[0].material=c),ye.enabled=!0,xe=!0,console.log("[StorySplat Viewer] AR content displayed for hotspot:",e.title)}(o):c&&p===o.id||v(e,o,P));const r=o.teleportWaypoint??o.teleportToWaypoint,l=o.teleportPercent??o.teleportToPercent,u=o.teleportMode||"animate";let m=null;if(void 0!==r&&-1!==r){const t=d.waypoints?.length||1,e=Math.max(0,Math.min(r,t-1));m=t>1?e/(t-1):0,console.log("[Hotspot] Teleporting to waypoint:",e,"(progress:",m,", mode:",u,")")}else void 0!==l&&-1!==l&&(m=Math.max(0,Math.min(l/100,1)),console.log("[Hotspot] Teleporting to percent:",l,"(progress:",m,", mode:",u,")"));null!==m&&("instant"===u?(Je=m,tn=m,Mn(Je)):(tn=m,Tn(m,800)))}}),vi.addEventListener("dblclick",t=>{const e=vi.getBoundingClientRect();ki(t.clientX-e.left,t.clientY-e.top)});let zi=0;vi.addEventListener("touchend",t=>{if(1!==t.changedTouches.length)return;const e=Date.now();if(e-zi<300){const e=t.changedTouches[0],n=vi.getBoundingClientRect();ki(e.clientX-n.left,e.clientY-n.top),zi=0}else zi=e}),document.addEventListener("keydown",t=>{if(Bt&&["KeyW","KeyA","KeyS","KeyD","ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Space"].includes(t.code)&&Vt(),t.shiftKey&&t.ctrlKey&&"KeyD"===t.code&&Ct){t.preventDefault();const e=!Ct.collisionDebugVisible;Ct.setCollisionDebug(e),console.log(`[StorySplat Viewer] Collision debug: ${e?"ON":"OFF"} (${Ct.collisionMeshEntities.length} meshes)`)}}),Te(.2,"Initializing...");const Ri=n.frameSequence&&n.frameSequence.frameUrls&&n.frameSequence.frameUrls.length>0?async function(){if(!n.frameSequence||!n.frameSequence.frameUrls||0===n.frameSequence.frameUrls.length)throw new Error("No frame sequence URLs provided");console.log("[StorySplat Viewer] Loading 4DGS frame sequence:",n.frameSequence.frameUrls.length,"frames"),Te(.3,"Loading 4DGS frames..."),ct=new Pt(U,{frameUrls:n.frameSequence.frameUrls,fps:n.frameSequence.fps||24,loop:!1!==n.frameSequence.loop,preloadCount:n.frameSequence.preloadCount||5,autoplay:n.frameSequence.autoplay||!1},{onFrameChange:(t,e)=>{i.emit("frameChange",t,e)},onLoadProgress:(t,e)=>{Te(.3+t/e*.6,`Loading frames... ${t}/${e}`)},onError:t=>{console.error("[StorySplat Viewer] Frame sequence error:",t),i.emit("error",new Error(t))}}),ct.on("complete",()=>{i.emit("frameComplete")}),U.on("update",t=>{ct&&!lt&&ct.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:d.lodMetaUrl||"(none)",sogUrl:d.sogUrl||"(none)",splatUrl:d.splatUrl||"(none)",fallbackUrls:d.fallbackUrls?.length||0}),d.lodMetaUrl&&(a.push(d.lodMetaUrl),console.log("[SPLAT] ✓ LOD streaming URL added (highest priority):",d.lodMetaUrl)),d.sogUrl&&(a.push(d.sogUrl),console.log("[SPLAT] ✓ SOG URL added (second priority):",d.sogUrl)),d.splatUrl&&(a.push(d.splatUrl),console.log("[SPLAT] ✓ Original splat URL added (third priority):",d.splatUrl)),d.fallbackUrls&&(a.push(...d.fallbackUrls),console.log("[SPLAT] ✓ Fallback URLs added:",d.fallbackUrls)),console.log("[SPLAT] Final URL priority order:",a),console.log("[SPLAT] Will try URLs in order: LOD streaming > SOG > PLY/Other"),Te(.3,l(P,"loading"));for(const r of a)if(r)try{const a=r.split(".").pop()?.toLowerCase()||"splat",c="gsplat",p=r.includes("lod-meta.json"),h=r.includes(".sog")||p;console.log("[SPLAT] Attempting to load URL:",r),console.log("[SPLAT] Format detection:",{extension:a,isLodStreaming:p,isSogFormat:h,assetType:c});const u=new t.Asset("splat-"+Date.now(),c,{url:r});u.on("progress",(t,n)=>{if(n>0){Ae(r)&&(e=t);const o=.3+t/n*.6,i=Math.round(t/n*100);i%25!=0&&100!==i||console.log(`[SPLAT] Loading progress: ${i}% (${(t/1024/1024).toFixed(2)}MB / ${(n/1024/1024).toFixed(2)}MB)`),Te(o,`${l(P,"loading")} ${i}%`)}}),s=r,await new Promise((t,e)=>{let s=!1;const a=h?t=>{if(s)return;const n=t.reason?.message||String(t.reason);(n.includes("shape")||n.includes("upgradeMeta"))&&(console.warn("[SPLAT] ⚠️ Parse error detected (likely deprecated v1 format):",n),s=!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..."),e(new Error(`SOG parse error: ${n}`)))}:null;h&&a&&(console.log("[SPLAT] Registered error handler for SOG format parsing"),window.addEventListener("unhandledrejection",a));const l=()=>{h&&a&&window.removeEventListener("unhandledrejection",a)};u.ready(()=>{if(lt)return console.log("[SPLAT] Ignoring load - viewer was destroyed during loading"),l(),void e(new Error("Viewer destroyed"));console.log("[SPLAT] ✓ Asset loaded and resources ready");try{if(et=Pe(u,r),Ct){Ct.setSplatEntity(et);const t=Ct.voxelCollisionInstance;t&&Et.setVoxelCollision(t,et)}It(r)?(console.log("[SPLAT] ✓ LOD streaming enabled for this splat"),console.log("[SPLAT] LOD distances configured:",{distances:j,description:"Quality levels switch at these camera distances",preset:H})):h?console.log("[SPLAT] ✓ Single SOG file loaded (no LOD streaming)"):console.log("[SPLAT] Standard format loaded (PLY/SPLAT)");const e=o.revealEffect||n.revealEffect||d.revealEffect||"none",i=o.revealStyle||n.revealStyle||d.revealStyle||"bloom",a=nt(e,i);if(a){et.addComponent("script");const t="radial"===i?Y():J();ot=et.script?.create?.(t)??null,ot&&(ot.enabled=!1,ot.center.set(0,0,0),ot.speed=a.speed,ot.acceleration=a.acceleration,ot.delay=a.delay,ot.oscillationIntensity=a.oscillationIntensity,ot.dotTint.set(a.dotTint.r,a.dotTint.g,a.dotTint.b),ot.waveTint.set(a.waveTint.r,a.waveTint.g,a.waveTint.b),ot.endRadius=a.endRadius,console.log("[StorySplat Viewer] Reveal effect configured (waiting to start):",i,e))}else console.log("[StorySplat Viewer] Reveal effect disabled");p?(l(),t()):setTimeout(()=>{s||(l(),t())},100)}catch(t){console.error("[StorySplat Viewer] Error during gsplat setup:",t),l(),e(t)}}),u.on("error",t=>{const n=t;console.error("[SPLAT] ✗ Asset load error:",{url:r,assetType:c,isSogFormat:h,isLodFormat:p,error:t,message:n?.message||"Unknown error",status:n?.status||n?.statusCode||"N/A"}),l(),e(t)}),Le(u,r)});const m=Ae(s);return i.emit("loaded",{bandwidthUsed:m?e:0,isStorySplatHosted:m}),console.log("[SPLAT] ✓✓✓ SPLAT LOADED SUCCESSFULLY ✓✓✓"),void console.log("[SPLAT] Load summary:",{url:s,format:p?"LOD streaming (lod-meta.json)":h?"SOG (compressed)":"Standard (PLY/SPLAT)",lodEnabled:p,lodPreset:p?H:"N/A",bandwidthUsed:`${(e/1024/1024).toFixed(2)}MB`,isStorySplatHosted:m,bandwidthCounted:m?"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"))};Ri().then(()=>{if(Te(1,"Ready!"),c||(d.hotspots&&0!==d.hotspots.length?(console.log(`[StorySplat Viewer] Creating ${d.hotspots.length} hotspots...`),d.hotspots.forEach((t,e)=>{ci(t,e)})):console.log("[StorySplat Viewer] No hotspots to create"),function(){if(!d.portals||0===d.portals.length)return void console.log("[StorySplat Viewer] No portals to create");const t=d.portals.filter(t=>!t.menuOnly);console.log(`[StorySplat Viewer] Creating ${t.length} portals (${d.portals.length-t.length} menu-only)...`),t.forEach((t,e)=>{ui(t,e)})}(),d.waypoints&&0!==d.waypoints.length&&(d.waypoints.forEach((e,n)=>{e.interactions&&Array.isArray(e.interactions)&&e.interactions.forEach(o=>{if("audio"===o.type&&o.data){const i=o.data;if(!i.url)return void console.warn(`[Audio] Waypoint ${n} audio interaction has no URL, skipping`);const s=String(o.id||`audio-${n}`),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:ti(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});U.assets.add(c),c.ready(()=>{const t=a.sound?.slot(s);t&&(t.asset=c.id);const e=qn.get(s);e&&(e.assetReady=!0),console.log(`[Audio] Waypoint audio loaded: ${s}, spatialSound=${i.spatialSound}, maxDistance=${i.maxDistance}`)}),U.assets.load(c),U.root.addChild(a),qn.set(s,{entity:a,waypointIndex:n,config:i,slotId:s,playing:!1,autoplayTriggered:!1,assetReady:!1}),console.log(`[Audio] Waypoint audio created: ${s} at waypoint ${n}, spatial=${i.spatialSound}`)}})}),qn.size>0&&console.log(`[StorySplat Viewer] Setup ${qn.size} waypoint audio sources`)),function(){const e=d.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 n=e.id||`emitter-${Date.now()}-${Math.random().toString(36).substr(2,9)}`,o=e.position||{x:0,y:0,z:0},i=new t.Entity(`audio-emitter-${n}`);i.setPosition(o.x,o.y,-o.z);const s=ti(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(n,{volume:e.volume??.5,loop:!1!==e.loop,autoPlay:!1,overlap:!1});const a=new t.Asset(`audio-emitter-${n}`,"audio",{url:e.url});a.on("load",()=>{if(lt)return;const t=i.sound?.slot(n);t&&(t.asset=a.id);const o=ei.get(n);o&&(o.assetReady=!0,!1!==e.autoplay&&t&&(console.log(`[Audio] Autoplay emitter: ${e.name||n}`),t.play(),o.playing=!0)),console.log(`[Audio] Emitter loaded: ${e.name||n}, spatial=${!1!==e.spatialSound}, maxDistance=${e.maxDistance||100}`)}),U.assets.add(a),U.assets.load(a),U.root.addChild(i),ei.set(n,{entity:i,config:e,slotId:n,playing:!1,assetReady:!1}),console.log(`[Audio] Emitter created: ${e.name||n} at (${o.x}, ${o.y}, ${o.z})`)}),ei.size>0&&console.log(`[StorySplat Viewer] Setup ${ei.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:",d.particles),console.log("[Particle] Type:",typeof d.particles),console.log("[Particle] Is Array:",Array.isArray(d.particles)),!d.particles||0===d.particles.length)return console.log("[Particle] ⚠️ No particle systems to create (particles array is empty or undefined)"),void console.log("═══════════════════════════════════════");console.log(`[Particle] 📋 Found ${d.particles.length} particle system(s) to create`);for(let t=0;t<d.particles.length;t++){const e=d.particles[t];console.log(`[Particle] --- Particle System ${t+1}/${d.particles.length} ---`),console.log("[Particle] Raw config:",JSON.stringify(e,null,2));try{const n=e.particleTexture||"flare";let o,i;"custom"===n&&e.customTextureUrl?(o=e.customTextureUrl,i=`custom_${e.id||e.name||t}`,console.log(`[Particle] Custom texture: ${o.substring(0,60)}...`)):(o=Kn[n]||Kn.flare,i=n,console.log(`[Particle] Texture: ${n} -> ${o.substring(0,60)}...`)),console.log("[Particle] Loading texture...");const s=await Jn(i,o);console.log("[Particle] ✅ Texture loaded:",s.name),console.log("[Particle] Creating entity...");const a=to(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!"),U.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-${Zn.size}`).replace(/[^a-zA-Z0-9]/g,"_");Zn.set(l,a),console.log(`[Particle] ✅ SUCCESS: Created "${e.name||l}"`)}catch(t){const n=t instanceof Error?t:{message:String(t),stack:""};console.error(`[Particle] ❌ FAILED to create particle system: ${e.name}`),console.error(`[Particle] Error message: ${n.message||"No message"}`),console.error(`[Particle] Error stack: ${n.stack||"No stack"}`),console.error("[Particle] Error object:",t)}}console.log("═══════════════════════════════════════"),console.log(`[Particle] 🎉 COMPLETE: ${Zn.size}/${d.particles.length} particle systems created`),console.log("[Particle] Active particle systems:",Array.from(Zn.keys())),console.log("═══════════════════════════════════════")}(),async function(){d.customMeshes&&0!==d.customMeshes.length?(console.log(`[StorySplat Viewer] Creating ${d.customMeshes.length} custom meshes...`),console.log("[StorySplat Viewer] All custom mesh configs:",d.customMeshes),setTimeout(()=>{let t=0,e=0;d.customMeshes.forEach((n,o)=>{if(console.log(`[CustomMesh] Processing mesh ${o}:`,{name:n.name,enabled:n.enabled,hasModelUrl:!!n.modelUrl,modelUrl:n.modelUrl?.substring(0,100)+"..."}),!1!==n.enabled)try{io(n,o)?t++:(e++,console.warn(`[CustomMesh] Mesh ${n.name} returned null (likely missing modelUrl)`))}catch(t){console.error("[CustomMesh] Error loading mesh",n.name,":",t),e++}else console.log("[CustomMesh] Skipping disabled mesh:",n.name,"(enabled =",n.enabled,")"),e++}),console.log(`[CustomMesh] Summary: ${t} loaded, ${e} skipped`)},100),console.log(`[StorySplat Viewer] ${d.customMeshes.length} custom meshes queued for loading`)):console.log("[StorySplat Viewer] No custom meshes to create")}(),fo(),d.lights&&0!==d.lights.length?(console.log(`[StorySplat Viewer] Creating ${d.lights.length} custom lights...`),d.lights.forEach((t,e)=>{let n=null;switch(t.type){case"point":n=wo(t);break;case"directional":n=So(t);break;case"hemispheric":n=_o(t);break;case"ambient":Mo(t);break;case"spot":n=Eo(t);break;default:return void console.warn("[StorySplat Viewer] Unknown light type:",t.type)}n&&(U.root.addChild(n),xo.push(n),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")),To(),Io(),dt.length>0&&dt[0].url&&qe(dt[0].url),ot&&(ot.enabled=!0,console.log("[StorySplat Viewer] Reveal effect started immediately")),!ot&&d.lodMetaUrl){const t=5e3,e=performance.now();let n=!1;const o=()=>{if(n)return;const i=U.scene.gsplat;null!=i?.material||performance.now()-e>t?(n=!0,requestAnimationFrame(()=>{requestAnimationFrame(()=>{F.preloader&&g(F.preloader)})})):requestAnimationFrame(o)};requestAnimationFrame(o)}else setTimeout(()=>{F.preloader&&g(F.preloader)},200);if(function(){if(!d.includeXR)return;if(!U.xr)return void console.warn("[StorySplat Viewer] WebXR not supported in this browser");const e=U.xr,n=d.xrMode||"both";"vr"!==n&&"both"!==n||(e.isAvailable(t.XRTYPE_VR)&&(F.vrButton?.classList.add("available"),console.log("[StorySplat Viewer] VR is available")),e.on("available:"+t.XRTYPE_VR,t=>{t?F.vrButton?.classList.add("available"):F.vrButton?.classList.remove("available")})),"ar"!==n&&"both"!==n||(e.isAvailable(t.XRTYPE_AR)&&(F.arButton?.classList.add("available"),console.log("[StorySplat Viewer] AR is available")),e.on("available:"+t.XRTYPE_AR,t=>{t?F.arButton?.classList.add("available"):F.arButton?.classList.remove("available")})),e.on("start",()=>{ge=!0,console.log("[StorySplat Viewer] XR session started"),"vr"===fe?(F.vrButton?.classList.add("active"),F.vrButton.textContent=l(P,"exitVr")):"ar"===fe&&(F.arButton?.classList.add("active"),F.arButton.textContent=l(P,"exitAr")),Et.disable(),Ct&&Ct.disable(),i.emit("xrStart",{type:fe})}),e.on("end",()=>{ge=!1,console.log("[StorySplat Viewer] XR session ended"),be(),F.vrButton?.classList.remove("active"),F.arButton?.classList.remove("active"),F.vrButton&&(F.vrButton.textContent=l(P,"vr")),F.arButton&&(F.arButton.textContent=l(P,"ar")),fe=null,"explore"===Lt?Et.enable():"walk"===Lt&&Ct&&Ct.enable(),i.emit("xrEnd",{})}),F.vrButton&&F.vrButton.addEventListener("click",()=>{ge&&"vr"===fe?e.end():!ge&&e.isAvailable(t.XRTYPE_VR)&&(fe="vr",bt.camera.startXr(t.XRTYPE_VR,t.XRSPACE_LOCALFLOOR,{callback:t=>{t&&(console.error("[StorySplat Viewer] Failed to start VR:",t),fe=null)}}))}),F.arButton&&F.arButton.addEventListener("click",()=>{ge&&"ar"===fe?e.end():!ge&&e.isAvailable(t.XRTYPE_AR)&&(fe="ar",bt.camera.startXr(t.XRTYPE_AR,t.XRSPACE_LOCALFLOOR,{callback:t=>{t&&(console.error("[StorySplat Viewer] Failed to start AR:",t),fe=null)}}))})}(),d.htmlMeshes&&d.htmlMeshes.length>0){console.log("[StorySplat Viewer] Setting up HTML meshes:",d.htmlMeshes.length);const t=St(U,d.htmlMeshes);U.__htmlMeshManager=t,i.on("progressUpdate",()=>{const e=100*Je,n=d.waypoints?.length||1,o=Math.round(Je*Math.max(1,n-1));t.updateVisibility(e,o)})}try{const t=new URLSearchParams(window.location.search),e=t.get("waypoint"),n=t.get("autoplay");if(null!==e&&d.waypoints&&d.waypoints.length>0){const t=parseInt(e,10);!isNaN(t)&&t>=0&&t<d.waypoints.length&&(console.log("[StorySplat Viewer] URL param: navigating to waypoint",t),An(t))}"true"!==n||o.autoPlay||d.autoPlay||(console.log("[StorySplat Viewer] URL param: starting autoplay"),Dn())}catch(t){console.warn("[StorySplat Viewer] Could not parse URL parameters:",t)}if(i.emit("ready"),console.log("[StorySplat Viewer] Ready"),c||Ce(D),h){if(y(F,{nextWaypoint:Pn,prevWaypoint:Ln,play:Dn,pause:In,isPlaying:()=>tt,getCurrentWaypointIndex:()=>K,getWaypointCount:()=>d.waypoints?.length||0,getWaypoints:()=>d.waypoints||[],setCameraMode:Ce,on:(t,e)=>i.on(t,e)},D,P,{container:e,hideProgressText:T.hideProgressText}),function(t,e){if(!t.sceneMenuContainer)return;const n=t.sceneMenuContainer.querySelectorAll(".storysplat-scene-menu-item"),o=t.sceneMenuContainer.querySelector(".storysplat-scene-menu-toggle"),i=t.sceneMenuContainer.querySelector(".storysplat-scene-menu-dropdown");n.forEach(t=>{t.addEventListener("click",()=>{const n=t.getAttribute("data-portal-id")||"";e(n),o?.classList.remove("open"),i?.classList.remove("open")})})}(F,t=>{const e=d.portals?.find(e=>e.id===t);e&&fi(e)}),F.muteButton&&F.muteButton.addEventListener("click",()=>{const t=(Hn?ii():oi(),Hn),e=F.muteButton.querySelector(".storysplat-unmuted-icon"),n=F.muteButton.querySelector(".storysplat-muted-icon");e&&(e.style.display=t?"none":""),n&&(n.style.display=t?"":"none"),F.muteButton.setAttribute("aria-label",l(P,t?"unmute":"mute"))}),F.relightingButton&&it){const t=d.splatRelighting?.viewerDefaultOn??!1;console.log("[StorySplat Viewer] Relighting toggle button wired up, startOn:",t);let e=t,n=t?1:0;F.relightingButton.classList.toggle("active",t);let o=null;F.relightingButton.addEventListener("click",()=>{if(ot&&ot.enabled)return void console.log("[StorySplat Viewer] Relighting toggle blocked — reveal still active");e=!e,n=e?1:0,console.log("[StorySplat Viewer] Relighting toggle:",e,"scripts:",st.size),F.relightingButton.classList.toggle("active",e),null!==o&&cancelAnimationFrame(o);const t=()=>{if(0===st.size)return;const e=it?it.relightFade:n,i=n-e;if(Math.abs(i)<.01){for(const t of st)t.setRelightFade(n);return void(o=null)}const s=e+.15*i;for(const t of st)t.setRelightFade(s);o=requestAnimationFrame(t)};o=requestAnimationFrame(t)})}const t=e.querySelector(".storysplat-hotspot-popup-close");t&&t.addEventListener("click",()=>{jn(),Yn(e)}),F.waypointListContainer&&d.waypoints&&d.waypoints.length>0&&(!function(t,e){if(!t.waypointListContainer)return;const n=t.waypointListContainer.querySelectorAll(".storysplat-waypoint-item"),o=t.waypointListContainer.querySelector(".storysplat-waypoint-list-toggle"),i=t.waypointListContainer.querySelector(".storysplat-waypoint-list-dropdown");n.forEach(t=>{t.addEventListener("click",()=>{const n=parseInt(t.getAttribute("data-waypoint-index")||"0",10);e(n),o?.classList.remove("open"),i?.classList.remove("open")})})}(F,t=>{console.log("[StorySplat Viewer] Waypoint list: jumping to waypoint",t),"tour"!==Lt&&Ce("tour"),An(t)}),i.on("waypointChange",({index:t})=>{C(F,t)}),C(F,0)),i.emit("progressUpdate",{progress:Je,index:K}),d.waypoints&&d.waypoints.length>0&&i.emit("waypointChange",{index:0,waypoint:d.waypoints[0],prevIndex:-1})}(o.autoPlay||d.autoPlay)&&Dn()}).catch(t=>{console.error("[StorySplat Viewer] Failed to initialize:",t),F.preloader&&g(F.preloader),i.emit("error",t)});const Di=()=>{U.resizeCanvas()};window.addEventListener("resize",Di);const Ii=new Map,Fi=new Map,Bi=new Map,Ui=new Map,$i=new Map,Vi=new Map;c&&(Bn.forEach(t=>{const e=t.hotspotData?.id;e&&Ii.set(e,t)}),xo.forEach((t,e)=>{const n=d.lights?.[e],o=n?.id||n?.name||`light-${e}`;Fi.set(o,t)}),Zn.forEach((t,e)=>{Bi.set(e,t)}),Un.forEach(t=>{const e=t.portalData?.id;e&&$i.set(e,t)}));const Oi={app:U,canvas:B,goToWaypoint:t=>{if("explore"===Lt)throw new Error('[StorySplat Viewer] goToWaypoint() requires tour mode. Call setCameraMode("tour") first.');An(t)},nextWaypoint:()=>{if("explore"===Lt)throw new Error('[StorySplat Viewer] nextWaypoint() requires tour mode. Call setCameraMode("tour") first.');Pn()},prevWaypoint:()=>{if("explore"===Lt)throw new Error('[StorySplat Viewer] prevWaypoint() requires tour mode. Call setCameraMode("tour") first.');Ln()},getCurrentWaypointIndex:()=>K,getWaypointCount:()=>d.waypoints?.length||0,setPosition:(t,e,n)=>{if("explore"!==Lt)throw new Error('[StorySplat Viewer] setPosition() requires explore mode. Call setCameraMode("explore") first.');if(tt)throw new Error("[StorySplat Viewer] setPosition() cannot be used during autoplay. Call pause() or stop() first.");Et.disable(),bt.setPosition(t,e,n),Et.syncFromCamera(),Et.enable(),requestAnimationFrame(()=>{bt.setPosition(t,e,n),Et.syncFromCamera()})},setRotation:(t,e,n)=>{if("explore"!==Lt)throw new Error('[StorySplat Viewer] setRotation() requires explore mode. Call setCameraMode("explore") first.');if(tt)throw new Error("[StorySplat Viewer] setRotation() cannot be used during autoplay. Call pause() or stop() first.");Et.disable(),bt.setEulerAngles(t,e,n),Et.syncFromCamera(),Et.enable(),requestAnimationFrame(()=>{bt.setEulerAngles(t,e,n),Et.syncFromCamera()})},getPosition:()=>{const t=bt.getPosition();return{x:t.x,y:t.y,z:t.z}},getRotation:()=>{const t=bt.getEulerAngles();return{x:t.x,y:t.y,z:t.z}},play:()=>{if("explore"===Lt)throw new Error('[StorySplat Viewer] play() requires tour mode. Call setCameraMode("tour") first.');Dn()},pause:()=>{if("explore"===Lt)throw new Error('[StorySplat Viewer] pause() requires tour mode. Call setCameraMode("tour") first.');In()},stop:()=>{if("explore"===Lt)throw new Error('[StorySplat Viewer] stop() requires tour mode. Call setCameraMode("tour") first.');!function(){if(ct)return ct.stop(),void i.emit("playbackStop");In(),En(0)}()},isPlaying:()=>tt,setFrame:t=>ct?.setFrame(t),getCurrentFrame:()=>ct?.getCurrentFrame()??0,getTotalFrames:()=>ct?.getTotalFrames()??0,setFps:t=>ct?.setFps(t),getFps:()=>ct?.getFps()??24,getFrameProgress:()=>ct?.getProgress()??0,setFrameProgress:t=>ct?.setProgress(t),goToOriginalSplat:Qe,goToSplat:async t=>{const e=Ze();if(t===e)return void Qe();const n=dt.find(e=>e.url===t);n||gt.has(t)||await qe(t);const o=ut||e;if(o===e&&et)Ne(et);else if(o){const t=gt.get(o);t&&(t.enabled=!1)}if(!He(t))throw new Error(`[StorySplat Viewer] Failed to switch to splat: ${t}`);n&&je(n.defaultExploreMode,!1),i.emit("splatChange",{url:t,isOriginal:!1})},getCurrentSplatUrl:function(){return ut||Ze()},isShowingOriginalSplat:function(){return ut===Ze()||null===ut},getAdditionalSplats:()=>dt.map(t=>({url:t.url,name:t.name,waypointIndex:t.waypointIndex,percentage:t.percentage})),destroy:()=>{lt=!0,In(),ct&&(ct.destroy(),ct=null),window.removeEventListener("resize",Di),e.removeAttribute("data-storysplat-editor"),e.querySelector(".storysplat-error-popup")?.remove(),F.preloader&&F.preloader.remove(),F.scrollControls&&F.scrollControls.remove(),F.fullscreenButton&&F.fullscreenButton.remove(),F.helpButton&&F.helpButton.remove(),F.helpPanel&&F.helpPanel.remove(),F.waypointInfo&&F.waypointInfo.remove(),F.watermark&&F.watermark.remove(),F.wasdHint&&F.wasdHint.remove(),F.orbitHint&&F.orbitHint.remove(),F.doubleTapHint&&F.doubleTapHint.remove(),F.lookZone&&F.lookZone.remove(),F.joystick&&F.joystick.remove(),F.muteButton&&F.muteButton.remove(),F.relightingButton&&F.relightingButton.remove(),F.waypointListContainer&&F.waypointListContainer.remove(),F.sceneMenuContainer&&F.sceneMenuContainer.remove(),F.fpsCounter&&F.fpsCounter.remove(),F.hotspotPopup&&F.hotspotPopup.remove(),F.portalPopup&&F.portalPopup.remove(),F.vrButton&&F.vrButton.remove(),F.arButton&&F.arButton.remove(),F.modeContainer&&F.modeContainer.remove(),F.exploreControls&&F.exploreControls.remove();const t=document.getElementById("storysplat-viewer-styles");t&&t.remove(),e.classList.remove("storysplat-viewer-container"),si.forEach(t=>t.destroy()),si.length=0,po(),Et.setCollisionEntities([]),Ct&&Ct.destroy();const n=U.__customScriptSystem;n&&n.dispose();const o=U.__htmlMeshManager;o&&o.destroy(),qt.destroy(),Yt.destroy(),Xt.destroy(),vi.removeEventListener("mousemove",xi),ye&&(ye.destroy(),ye=null),ve&&(ve.destroy(),ve=null),Nt.destroy(),U.destroy(),B.remove()},resize:Di,navigateToScene:async t=>{const e={targetSceneId:t,activationMode:"click"};await fi(e)},setCameraMode:t=>Ce(t),getCameraMode:()=>Lt,setExploreMode:t=>{if("explore"!==Lt&&"walk"!==Lt)throw new Error('[StorySplat Viewer] setExploreMode() requires explore mode. Call setCameraMode("explore") first.');if("walk"===t)"walk"!==Lt&&Ce("walk"),Ee("walk");else{"walk"===Lt&&Ce("explore"),Et.setMode(t),Ee(t);Rt()?x(F,"fly"===t):(S(F,"fly"===t),_(F,"orbit"===t)),E(F)}},setProgress:t=>{if("explore"===Lt)throw new Error('[StorySplat Viewer] setProgress() requires tour mode. Call setCameraMode("tour") first.');En(t)},getProgress:()=>Je,muteAll:()=>oi(),unmuteAll:()=>ii(),isMuted:()=>Hn,getHotspots:()=>Bn.map(t=>{const e=t.hotspotData,n=t.getPosition();return{id:e?.id||"",title:e?.title||e?.information?.substring(0,30)||"",type:e?.type||"sphere",position:{x:n.x,y:n.y,z:n.z}}}),triggerHotspot:t=>{const n=Bn.find(e=>e.hotspotData?.id===t);if(!n)throw new Error(`[StorySplat Viewer] Hotspot not found: ${t}`);const o=n.hotspotData;o&&v(e,o,P)},closeHotspot:()=>{const t=e.querySelector(".storysplat-hotspot-popup"),n=e.querySelector(".storysplat-hotspot-overlay");t&&t.classList.remove("visible"),n&&n.classList.remove("visible"),jn(),Yn(e)},setButtonLabels:t=>{P={...P,...t};const n=t=>l(P,t),o=(t,o)=>{const i=e.querySelector(`.storysplat-mode-btn[data-mode="${t}"]`);i&&(i.textContent=n(o))};o("tour","tour"),o("explore","explore");const i=e.querySelector('.storysplat-explore-btn[data-explore-mode="orbit"]');i&&(i.textContent=n("orbit"));const s=e.querySelector('.storysplat-explore-btn[data-explore-mode="fly"]');s&&(s.textContent=n("fly"));const a=e.querySelector('.storysplat-explore-btn[data-explore-mode="walk"]');a&&(a.textContent=n("walk"));const r=e.querySelector(".storysplat-btn-prev");r&&(r.textContent=n("previous"));const c=e.querySelector(".storysplat-btn-next");c&&(c.textContent=n("next"));const d=e.querySelector(".storysplat-waypoint-list-toggle");if(d){const t=d.querySelector("svg");d.textContent="",d.append(n("waypoints")),t&&d.appendChild(t),d.setAttribute("aria-label",n("waypoints"))}const p=e.querySelector(".storysplat-vr-btn");p&&!p.classList.contains("active")&&(p.textContent=n("vr"),p.setAttribute("aria-label",n("vr")));const h=e.querySelector(".storysplat-ar-btn");h&&!h.classList.contains("active")&&(h.textContent=n("ar"),h.setAttribute("aria-label",n("ar")));const u=e.querySelector(".storysplat-fullscreen-btn");u&&u.setAttribute("aria-label",n("fullscreen"));const m=e.querySelector(".storysplat-hotspot-popup-close");m&&(m.textContent=n("close"));const g=e.querySelector(".storysplat-portal-popup-confirm");g&&(g.textContent=n("yes"));const f=e.querySelector(".storysplat-portal-popup-cancel");f&&(f.textContent=n("cancel"));const y=e.querySelector(".storysplat-help-panel");if(y){y.textContent="";const t=(t,e,n)=>{const o=document.createElement(t);if(n){const t=document.createElement("strong");t.textContent=e,o.appendChild(t)}else o.textContent=e;return y.appendChild(o),o},e=()=>y.appendChild(document.createElement("br"));t("h3",n("helpTitle")),t("p",n("helpCameraModes"),!0),t("p",`• ${n("tour")} - ${n("helpTourDesc")}`),t("p",`• ${n("explore")} - ${n("helpExploreDesc")}`),t("p",`• ${n("walk")} - ${n("helpWalkDesc")}`),e(),t("p",`${n("tour")} Mode:`,!0),t("p","• Scroll - Move along path"),t("p","• Drag - Look around"),e(),t("p",`${n("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",`${n("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 v=e.querySelector(".storysplat-help-btn");v&&v.setAttribute("title",n("helpTitle"))},on:(t,e)=>i.on(t,e),off:(t,e)=>i.off(t,e)};if(c){const e=Oi;return e.setCameraMode=t=>Ce(t),e.getCameraMode=()=>Lt,e.getCameraControls=()=>Et,e.suppressInput=()=>{fn=!0,gn=!1,Et.disable()},e.resumeInput=()=>{fn=!1,"explore"===Lt&&Et.enable()},e.setReticleEnabled=t=>{he=t,t||(Xt.enabled=!1,ee=!1,me=0)},e.setEditingHotspotId=t=>{p=t},e.getApp=()=>U,e.getSplatEntity=()=>et,e.getAllHotspotEntities=()=>Ii,e.getAllLightEntities=()=>Fi,e.getAllPortalEntities=()=>$i,e.getAllCustomMeshEntities=()=>Ui,e.getAllParticleEntities=()=>Bi,e.getAllCollisionMeshEntities=()=>Vi,e.getAllHtmlMeshEntities=()=>{const t=U.__htmlMeshManager,e=new Map;return t&&t.getAllMeshes().forEach((t,n)=>{e.set(n,t.entity)}),e},e.getCamera=()=>bt,e.setProgress=t=>En(t),e.getProgress=()=>Je,e.addHotspot=t=>{const e=t.id||`hotspot-${Date.now()}`,n=ci(t,Bn.length);return Ii.set(e,n),e},e.removeHotspot=t=>{const e=Ii.get(t);if(e){e.destroy(),Ii.delete(t);const n=Bn.indexOf(e);n>=0&&Bn.splice(n,1)}},e.updateHotspot=(t,e)=>{const n=Ii.get(t);if(!n)return;e.position&&n.setPosition(e.position.x,e.position.y,-e.position.z);const o=n.hotspotData||{};n.hotspotData={...o,...e}},e.addLight=e=>{const n=e.id||e.name||`light-${Date.now()}`;let o=null;switch(e.type){case"point":default:o=wo(e);break;case"directional":o=So(e);break;case"hemispheric":o=_o(e);break;case"ambient":Mo(e);break;case"spot":o=Eo(e)}if(o){o.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1}),o.setLocalScale(.25,.25,.25);const e=new t.StandardMaterial;e.diffuse=new t.Color(1,.85,0,1),e.emissive=new t.Color(.5,.42,0,1),e.update(),o.render.meshInstances[0].material=e,U.root.addChild(o),xo.push(o),Fi.set(n,o)}return n},e.removeLight=t=>{const e=Fi.get(t);if(e){e.destroy(),Fi.delete(t);const n=xo.indexOf(e);n>=0&&xo.splice(n,1)}},e.updateLight=(t,e)=>{const n=Fi.get(t);n&&(e.position&&n.setPosition(e.position.x??0,e.position.y??0,-(e.position.z??0)),n.light&&(void 0!==e.intensity&&(n.light.intensity=e.intensity),void 0!==e.range&&(n.light.range=e.range),void 0!==e.castShadows&&(n.light.castShadows=e.castShadows),e.color&&(n.light.color=bo(e.color))))},e.addCustomMesh=t=>{const e=t.id||t.name||`mesh-${Date.now()}`,n=io(t,eo.size);return n&&Ui.set(e,n),e},e.removeCustomMesh=t=>{const e=Ui.get(t);e&&(eo.forEach((t,n)=>{t.entity===e&&(t.modelAsset&&(U.assets.remove(t.modelAsset),t.modelAsset.unload()),eo.delete(n))}),e.destroy(),Ui.delete(t))},e.addParticleSystem=e=>{const n=e.id||e.name||`particle-${Date.now()}`,o=to(e),i=e.particleTexture||"flare";let s,a;"custom"===i&&e.customTextureUrl?(s=e.customTextureUrl,a=`custom_${n}`):(s=Kn[i]||Kn.flare,a=i),Jn(a,s).then(t=>{o.particlesystem&&(o.particlesystem.colorMap=t)}).catch(t=>{console.warn("[Editor] Failed to load particle texture:",t)});const r=new t.Entity("particle-picker");r.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1}),r.setLocalScale(.15,.15,.15);const l=new t.StandardMaterial;l.diffuse=new t.Color(1,.5,0,1),l.opacity=.3,l.blendType=t.BLEND_NORMAL,l.depthWrite=!1,l.update(),r.render.meshInstances[0].material=l,o.addChild(r),U.root.addChild(o);const c=n.replace(/[^a-zA-Z0-9]/g,"_");return Zn.set(c,o),Bi.set(n,o),n},e.removeParticleSystem=t=>{const e=t.replace(/[^a-zA-Z0-9]/g,"_"),n=Bi.get(t)||Zn.get(e);n&&(n.destroy(),Bi.delete(t),Zn.delete(e))},e.addPortal=t=>{const e=t.id||`portal-${Date.now()}`,n=ui(t,Un.length);return $i.set(e,n),e},e.removePortal=t=>{const e=$i.get(t);if(e){const n=e.videoElement;n&&(n.pause(),n.src=""),e.destroy(),$i.delete(t);const o=Un.indexOf(e);o>=0&&Un.splice(o,1)}},e.updatePortal=(t,e)=>{const n=$i.get(t);if(!n)return;if(e.position&&n.setPosition(e.position.x??0,e.position.y??0,-(e.position.z??0)),e.rotation){const t=(e.rotation.x??0)*(180/Math.PI),o=(e.rotation.y??0)*(180/Math.PI),i=-(e.rotation.z??0)*(180/Math.PI);n.setEulerAngles(t,o,i)}e.scale&&("number"==typeof e.scale?n.setLocalScale(e.scale,e.scale,e.scale):n.setLocalScale(e.scale.x??1,e.scale.y??1,e.scale.z??1));const o=n.portalData||{};n.portalData={...o,...e}},e.addHtmlMesh=t=>{const e=t.id||`html-mesh-${Date.now()}`;let n=U.__htmlMeshManager;return n||(n=St(U,[]),U.__htmlMeshManager=n),n.createMesh({...t,id:e,html:t.html||t.htmlContent||""}),e},e.removeHtmlMesh=t=>{const e=U.__htmlMeshManager;e&&e.destroyMesh(t)},e.addCollisionMesh=e=>{const n=e.id||`collision-${Date.now()}`,o=new t.Entity(`collision-mesh-${n}`),i=e.meshType||"cube",s=e.position,a=Array.isArray(s)?s[0]:s.x,r=Array.isArray(s)?s[1]:s.y,l=Array.isArray(s)?s[2]:s.z;if(o.setPosition(a??0,r??0,-(l??0)),e.rotation){const n=e.rotation,s=(Array.isArray(n)?n[0]:n.x)??0,a=((Array.isArray(n)?n[1]:n.y)??0)/2,r=s/2,l=((Array.isArray(n)?n[2]:n.z)??0)/2,c=Math.cos(a),d=Math.sin(a),p=Math.cos(r),h=Math.sin(r),u=Math.cos(l),m=Math.sin(l),g=c*p*u+d*h*m,f=c*h*u+d*p*m,y=d*p*u-c*h*m,v=c*p*m-d*h*u,x=new t.Quat(-f,-y,v,g);if("plane"===i){const e=(new t.Quat).setFromEulerAngles(90,0,0);o.setRotation((new t.Quat).mul2(x,e))}else o.setRotation(x)}const c=e.scaling,d=(c?Array.isArray(c)?c[0]:c.x:1)??1,p=(c?Array.isArray(c)?c[1]:c.y:1)??1,h=(c?Array.isArray(c)?c[2]:c.z:1)??1;if("plane"===i){const t=.01;o.setLocalScale(3*d,h*t,3*p)}else"cube"===i||"sphere"===i?o.setLocalScale(3*d,3*p,3*h):"floor"===i?o.setLocalScale(100*d,1*p,100*h):o.setLocalScale(d,p,h);if("custom"!==i){const e="cube"===i?"box":"floor"===i?"plane":i;o.addComponent("render",{type:e,castShadows:!1,receiveShadows:!1});const n=new t.StandardMaterial;n.diffuse=new t.Color(0,.8,0),n.opacity=1,n.blendType=t.BLEND_NONE,n.depthWrite=!0,n.cull=t.CULLFACE_NONE,n.useLighting=!1,n.update(),o.render&&o.render.meshInstances.forEach(t=>{t.material=n})}return o.addComponent("collision",{type:"sphere"===i?"sphere":"box"}),o.enabled=!1!==e.visible,o._collisionMeshId=n,U.root.addChild(o),Vi.set(n,o),n},e.removeCollisionMesh=t=>{const e=U.root.children;for(let n=e.length-1;n>=0;n--)if(e[n]._collisionMeshId===t){e[n].destroy();break}Vi.delete(t)},e.addAudioEmitter=e=>{const n=e.id||`emitter-${Date.now()}`,o=e.position||{x:0,y:0,z:0},i=new t.Entity(`audio-emitter-${n}`);if(i.setPosition(o.x,o.y,-o.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(n,{volume:e.volume??.5,loop:!1!==e.loop,autoPlay:!1,overlap:!1}),e.url){const o=new t.Asset(`audio-emitter-${n}`,"audio",{url:e.url});o.on("load",()=>{if(lt)return;const t=i.sound?.slot(n);t&&(t.asset=o.id,!1!==e.autoplay&&t.play())}),U.assets.add(o),U.assets.load(o)}i.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1}),i.setLocalScale(.3,.3,.3);const s=new t.StandardMaterial;return s.diffuse=new t.Color(.2,.6,1,1),s.emissive=new t.Color(.1,.3,.5,1),s.update(),i.render.meshInstances[0].material=s,U.root.addChild(i),ei.set(n,{entity:i,config:e,slotId:n,playing:!1,assetReady:!1}),n},e.removeAudioEmitter=t=>{const e=ei.get(t);if(e){const n=e.entity.sound?.slot(e.slotId);n&&n.stop(),e.entity.destroy(),ei.delete(t)}},e.updateAudioEmitter=(t,e)=>{const n=ei.get(t);n&&(e.position&&n.entity.setPosition(e.position.x,e.position.y,-e.position.z),void 0!==e.volume&&n.entity.sound&&(n.entity.sound.volume=e.volume),n.config={...n.config,...e})},e.setSplatRelighting=t=>{if(d.splatRelighting={...d.splatRelighting,...t},!1!==t.enabled){if(!it&&xo.length>0&&et&&To(),it){if(void 0!==t.ambientColor||void 0!==t.ambientIntensity){const e=t.ambientColor??d.splatRelighting?.ambientColor??"#ffffff",n=t.ambientIntensity??d.splatRelighting?.ambientIntensity??0;for(const t of st)t.setAmbientColor(e),t.ambientR*=n,t.ambientG*=n,t.ambientB*=n}if(!0===t.enabled&&!at){for(const t of st)t.enabled=!0,t.setRelightFade(1);at=!0}}void 0===t.shadowsEnabled&&void 0===t.shadowIntensity&&void 0===t.shadowGroundY&&void 0===t.shadowPlaneScale||Io()}else{for(const t of st)t.enabled=!1;at=!1}},e.setSkybox=(t,e)=>{vo(t,e)},e.setSkyboxRotation=e=>{const n=new t.Quat;n.setFromEulerAngles(0,-e*(180/Math.PI),0),U.scene.skyboxRotation=n,ho&&ho.setEulerAngles(0,-e*(180/Math.PI),0),d.skybox&&(d.skybox.rotation=e),d.skyboxRotation=e},e.setBackgroundColor=e=>{const n=new t.Color(e.r/255,e.g/255,e.b/255);bt.camera&&(bt.camera.clearColor=n)},e.setFOV=t=>{bt.camera&&(bt.camera.fov=t)},e.loadSplatUrl=(e,n)=>async function(e,n){const o=[];if(n&&o.push(n),e&&e!==n&&o.push(e),0!==o.length){console.log("[SPLAT] loadSplatByUrl: URLs to try:",o),et&&(console.log("[SPLAT] loadSplatByUrl: Destroying existing splat entity"),et.destroy(),et=null);for(const e of o)try{console.log("[SPLAT] loadSplatByUrl: Trying URL:",e);const n=new t.Asset("splat-"+Date.now(),"gsplat",{url:e});return await new Promise((t,o)=>{n.ready(()=>{if(lt)t();else try{if(et=Pe(n,e),Ct){Ct.setSplatEntity(et);const t=Ct.voxelCollisionInstance;t&&Et.setVoxelCollision(t,et)}console.log("[SPLAT] loadSplatByUrl: New splat entity added to scene"),t()}catch(t){console.error("[SPLAT] loadSplatByUrl: Error during gsplat setup:",t),o(t)}}),n.on("error",t=>{console.error("[SPLAT] loadSplatByUrl: Asset load error:",{url:e,error:t}),o(t)}),Le(n,e)}),void i.emit("loaded",{bandwidthUsed:0,isStorySplatHosted:Ae(e)})}catch(t){console.warn("[SPLAT] loadSplatByUrl: Failed, trying next fallback:",e,t)}throw new Error("[SPLAT] loadSplatByUrl: Failed to load from any URL")}console.warn("[SPLAT] loadSplatByUrl called with no URL")}(e,n),e.addWaypoint=t=>{d.waypoints||(d.waypoints=[]),d.waypoints.push(t)},e.removeWaypoint=t=>{d.waypoints&&t>=0&&t<d.waypoints.length&&d.waypoints.splice(t,1)},e.updateWaypoint=(t,e)=>{d.waypoints&&t>=0&&t<d.waypoints.length&&Object.assign(d.waypoints[t],e)},e.rebuildTourPath=e=>{if(e&&(d.waypoints=e),xn.length=0,bn.length=0,wn.length=0,d.waypoints&&d.waypoints.length>0&&d.waypoints.forEach(e=>{const n=e.position?new t.Vec3(e.position.x,e.position.y,-e.position.z):new t.Vec3(0,d.playerHeight||1.6,0);xn.push(n);const o=e.rotation?ke(e.rotation):new t.Quat;bn.push(o);let i=e.fov||Sn;i<3.5&&(i*=180/Math.PI),wn.push(i)}),xn.length>=2){const t=Ot;Ot=!0,Mn(Je),Ot=t}else xn.length>0&&(dn=xn[0].clone(),pn=bn[0].clone(),_n=wn[0]);console.log("[Editor] Tour path rebuilt with",xn.length,"waypoints")},e.setSplatScale=(t,e,n)=>{const o=e??d.invertXScale??!1,i=n??d.invertYScale??!1;if(d.invertXScale=o,d.invertYScale=i,d.scale={x:t,y:t,z:t},!et)return;const s=o?-t:t,a=i?t:-t,r=o!==i?t:-t;et.setLocalScale(s,a,r)},e.setSplatPosition=(t,e,n)=>{et&&et.setPosition(t,e,-n)},e.setSplatRotation=(t,e,n)=>{et&&et.setEulerAngles(t*(180/Math.PI),e*(180/Math.PI),-n*(180/Math.PI))},e.setAutoplaySpeed=t=>{const e=d.waypoints?.length||1,n=Math.max(1,20*(e-1));an=60*t/n},e.setLoopMode=t=>{ln=t},e.setScrollSpeed=t=>{d.scrollSpeed=t},e.startPlayground=()=>{Qo()},e.stopPlayground=()=>{Ko()},e.isPlaygroundActive=()=>$o,e.initVoxelCollision=async t=>{if(!Ct){const t=null!=d.walkSpeed?d.walkSpeed:Mt;Ct=new $(bt,U,{moveSpeed:t,sprintMultiplier:2,lookSensitivity:1/(d.cameraRotationSensitivity||4e3)*10,playerHeight:d.playerHeight||1.6,gravity:20,jumpVelocity:8,collisionRadius:.3,stepHeight:.3}),z.includes("walk")||z.push("walk"),console.log("[StorySplat Viewer] CharacterController created lazily for voxel collision")}et&&Ct.setSplatEntity(et),await Ct.initVoxelCollision(t);const e=Ct.voxelCollisionInstance;e&&Et.setVoxelCollision(e,et),console.log("[StorySplat Viewer] Voxel collision loaded via editor API")},e.setVoxelDebug=t=>{Ct&&Ct.setVoxelDebug(t)},e.getVoxelDebugVisible=()=>Ct?.voxelDebugVisible??!1,e.clearVoxelDebug=()=>{Ct?.clearVoxelDebug()},e}if(d.customScript&&""!==d.customScript.trim()){console.log("[StorySplat Viewer] Initializing custom script system...");const t=At(Oi,U,B,d.customScript,()=>Je,()=>K);t&&(U.__customScriptSystem=t,console.log("[StorySplat Viewer] Custom script system initialized"))}if(o.analytics){const t=o.analytics.baseUrl??"https://discover.storysplat.com",e=kt(Oi,t,o.analytics.sceneId,o.analytics.ownerId),n=Oi.destroy;Oi.destroy=()=>{e.destroy(),n()}}return Oi}async function Bt(t,e,n){console.log("[StorySplat Viewer] Fetching scene from:",e);const o=await fetch(e);if(!o.ok)throw new Error(`Failed to fetch scene: ${o.statusText}`);const i=await o.json();return console.log("[StorySplat Viewer] Scene data loaded:",i),Ft(t,i,n)}function Ut(t,e,n){return t+(e-t)*n}async function $t(t,e,n,o,i){try{const s=await fetch(`${t}/api/track-embed`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sceneId:e,ownerId:n,type:o,..."bandwidth"===o&&i?{bytes:i}:{}})});s.ok?console.log(`[StorySplat] Tracked ${o}${"bandwidth"===o?` (${(i/1024/1024).toFixed(2)}MB)`:""}`):console.warn(`[StorySplat] Failed to track ${o}:`,s.status)}catch(t){console.warn(`[StorySplat] Error tracking ${o}:`,t)}}class Vt extends Error{constructor(t){super(`Scene not found: ${t}`),this.name="SceneNotFoundError"}}class Ot extends Error{constructor(t,e){super(t),this.name="SceneApiError",this.statusCode=e}}const Wt=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 Nt(t,e,n={}){const o=n.baseUrl||Wt;console.log(`[StorySplat Viewer] Fetching scene: ${e}`);const i=`${o}/api/scene/${encodeURIComponent(e)}`,s={"Content-Type":"application/json"};n.apiKey&&(s.Authorization=`Bearer ${n.apiKey}`);const a=await fetch(i,{method:"GET",headers:s});if(!a.ok){if(404===a.status)throw new Vt(e);const t=await a.text();let n;try{n=JSON.parse(t).error||`API error: ${a.status}`}catch{n=`API error: ${a.status}`}throw new Ot(n,a.status)}const r=await a.json();if(!r.success||!r.data)throw new Ot("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:d,apiKey:p,...h}=n,u=Ft(t,c,{...h,analytics:{sceneId:e,ownerId:l,baseUrl:o}});$t(o,e,l,"view");let m=!1;return u.on("loaded",t=>{m||(m=!0,t&&t.bandwidthUsed>0&&t.isStorySplatHosted?$t(o,e,l,"bandwidth",t.bandwidthUsed):console.log("[StorySplat] Bandwidth not tracked (self-hosted or no data)"))}),u}async function Gt(t,e={}){const n=`${e.baseUrl||Wt}/api/scene/${encodeURIComponent(t)}/meta`,o={"Content-Type":"application/json"};e.apiKey&&(o.Authorization=`Bearer ${e.apiKey}`);const i=await fetch(n,{method:"GET",headers:o});if(!i.ok){if(404===i.status)throw new Vt(t);throw new Ot(`API error: ${i.status}`,i.status)}const s=await i.json();return{...s,userName:s.userName||"Unknown",userSlug:s.userSlug||"unknown"}}class Ht{constructor(e,n,o={}){this.translateGizmo=null,this.rotateGizmo=null,this.scaleGizmo=null,this.activeGizmo=null,this.attachedEntity=null,this._mode="none",this._interacting=!1,this.app=e,this.camera=n,this.gizmoLayer=t.Gizmo.createLayer(e),this.translateGizmo=new t.TranslateGizmo(n,this.gizmoLayer),this.rotateGizmo=new t.RotateGizmo(n,this.gizmoLayer),this.scaleGizmo=new t.ScaleGizmo(n,this.gizmoLayer),this.setSnap(o.snap??!1,o.snapIncrement??1),this.setCoordSpace(o.coordSpace??"world"),this.setupEvents(this.translateGizmo),this.setupEvents(this.rotateGizmo),this.setupEvents(this.scaleGizmo)}get isInteracting(){return this._interacting}setupEvents(t){t.on("pointer:down",(t,e,n)=>{n&&(this._interacting=!0,this.onPointerDown?.())}),t.on("pointer:up",()=>{this._interacting&&(this._interacting=!1,this.onPointerUp?.())}),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()})})}getLayer(){return this.gizmoLayer}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])}get hasAttachment(){return null!==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,this.onPointerDown=t.pointerDown,this.onPointerUp=t.pointerUp}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 Xt{constructor(e,n,o={}){this.selectedEntities=new Set,this.highlightedEntity=null,this.originalMaterials=new Map,this.app=e,this.camera=n,this.config={highlightColor:o.highlightColor??new t.Color(.2,.5,1,1),multiSelect:o.multiSelect??!1},this.picker=new t.Picker(e,1,1,!0)}async pickAtScreenPosition(t,e){const n=this.app.graphicsDevice.canvas,o=this.camera.camera;if(!o||!n)return null;const i=.25,s=Math.max(1,Math.floor(n.clientWidth*i)),a=Math.max(1,Math.floor(n.clientHeight*i));this.picker.resize(s,a);const r=this.app.scene.layers.getLayerByName("World");if(!r)return null;this.picker.prepare(o,this.app.scene,[r]);const l=Math.floor(t*i),c=Math.floor(e*i),d=this.picker.getSelection(l,c);if(d&&d.length>0){const t=d[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 n=this.app.graphicsDevice.canvas,o=this.camera.camera;if(!o||!n)return null;const i=.25,s=Math.max(1,Math.floor(n.clientWidth*i)),a=Math.max(1,Math.floor(n.clientHeight*i));this.picker.resize(s,a);const r=this.app.scene.layers.getLayerByName("World");if(!r)return null;this.picker.prepare(o,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 n=this.getSelectedEntity();e||(this.selectedEntities.forEach(t=>this.removeHighlight(t)),this.selectedEntities.clear()),t&&(this.selectedEntities.add(t),this.applyHighlight(t)),this.onSelectionChange?.({entity:t,previousEntity:n})}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 n=new Map;e.render.meshInstances.forEach((e,o)=>{if(e.material){n.set(o,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,n)}removeHighlight(t){const e=this.originalMaterials.get(t);e&&t.render&&(t.render.meshInstances.forEach((t,n)=>{const o=e.get(n);o&&(t.material=o)}),this.originalMaterials.delete(t))}onSelect(t){this.onSelectionChange=t}async handlePointerDown(t,e=!1){let n,o;if(t instanceof MouseEvent)n=t.clientX,o=t.clientY;else{const e=t.touches[0];n=e.clientX,o=e.clientY}const i=await this.pickAtScreenPosition(n,o);return i?this.select(i,e):e||this.deselectAll(),i}destroy(){this.deselectAll(),this.selectedEntities.clear(),this.originalMaterials.clear()}}class qt{constructor(e,n,o,i={}){this.fpvEntity=null,this._enabled=!1,this.orbitTarget=new t.Vec3,this.orbitDistance=10,this.orbitYaw=0,this.orbitPitch=-30,this.isDragging=!1,this.isPanning=!1,this.lastMouseX=0,this.lastMouseY=0,this._onMouseDown=null,this._onMouseMove=null,this._onMouseUp=null,this._onWheel=null,this._onContextMenu=null,this._onFPVUpdate=null,this.app=e,this.tourCamera=n,this.cameraControls=o,this.config=i,this.editCamera=new t.Entity("editCamera");const s=n.camera;if(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}),i.gizmoLayer&&this.editCamera.camera){const t=this.editCamera.camera.layers;t.includes(i.gizmoLayer.id)||(this.editCamera.camera.layers=[...t,i.gizmoLayer.id])}const a=n.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._onFPVUpdate=()=>{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)}},this.app.on("update",this._onFPVUpdate)}updateOrbitCamera(){const e=this.orbitYaw*t.math.DEG_TO_RAD,n=this.orbitPitch*t.math.DEG_TO_RAD,o=this.orbitTarget.x+this.orbitDistance*Math.cos(n)*Math.sin(e),i=this.orbitTarget.y+this.orbitDistance*Math.sin(n),s=this.orbitTarget.z+this.orbitDistance*Math.cos(n)*Math.cos(e);this.editCamera.setPosition(o,i,s),this.editCamera.lookAt(this.orbitTarget)}attachInputHandlers(){const e=this.app.graphicsDevice.canvas;this._onMouseDown=t=>{0!==t.button||t.shiftKey?1===t.button||0===t.button&&t.shiftKey?(this.isDragging=!0,this.isPanning=!0,t.preventDefault()):2===t.button&&(this.isDragging=!0,this.isPanning=!0):(this.isDragging=!0,this.isPanning=!1),this.lastMouseX=t.clientX,this.lastMouseY=t.clientY},this._onMouseMove=e=>{if(!this.isDragging)return;const n=e.clientX-this.lastMouseX,o=e.clientY-this.lastMouseY;if(this.lastMouseX=e.clientX,this.lastMouseY=e.clientY,this.isPanning){const t=.002*this.orbitDistance,e=this.editCamera.right,i=this.editCamera.up;this.orbitTarget.x-=e.x*n*t-i.x*o*t,this.orbitTarget.y-=e.y*n*t-i.y*o*t,this.orbitTarget.z-=e.z*n*t-i.z*o*t}else{const e=this.config.rotateSensitivity||.3;this.orbitYaw-=n*e,this.orbitPitch+=o*e,this.orbitPitch=t.math.clamp(this.orbitPitch,-89,89)}this.updateOrbitCamera()},this._onMouseUp=()=>{this.isDragging=!1,this.isPanning=!1},this._onWheel=t=>{t.preventDefault();const e=.1*this.orbitDistance;this.orbitDistance+=t.deltaY>0?e:-e,this.orbitDistance=Math.max(.5,this.orbitDistance),this.updateOrbitCamera()},this._onContextMenu=t=>{t.preventDefault()},e.addEventListener("mousedown",this._onMouseDown),e.addEventListener("mousemove",this._onMouseMove),e.addEventListener("mouseup",this._onMouseUp),e.addEventListener("wheel",this._onWheel,{passive:!1}),e.addEventListener("contextmenu",this._onContextMenu)}detachInputHandlers(){const t=this.app.graphicsDevice.canvas;this._onMouseDown&&t.removeEventListener("mousedown",this._onMouseDown),this._onMouseMove&&t.removeEventListener("mousemove",this._onMouseMove),this._onMouseUp&&t.removeEventListener("mouseup",this._onMouseUp),this._onWheel&&t.removeEventListener("wheel",this._onWheel),this._onContextMenu&&t.removeEventListener("contextmenu",this._onContextMenu)}enable(){this._enabled=!0,this.tourCamera.enabled=!1,this.editCamera.enabled=!0,this.cameraControls.disable();const e=this.tourCamera.getPosition(),n=this.tourCamera.forward;this.orbitTarget.set(e.x+5*n.x,e.y+5*n.y,e.z+5*n.z),this.orbitDistance=5;const o=e.x-this.orbitTarget.x,i=e.y-this.orbitTarget.y,s=e.z-this.orbitTarget.z;this.orbitYaw=Math.atan2(o,s)*t.math.RAD_TO_DEG,this.orbitPitch=Math.atan2(i,Math.sqrt(o*o+s*s))*t.math.RAD_TO_DEG,this.updateOrbitCamera(),this.attachInputHandlers(),this.fpvEntity&&(this.fpvEntity.enabled=!0)}disable(){this._enabled=!1,this.editCamera.enabled=!1,this.tourCamera.enabled=!0,this.detachInputHandlers(),this.fpvEntity&&(this.fpvEntity.enabled=!1)}get enabled(){return this._enabled}getCamera(){return this.editCamera}focusOn(t){this.orbitTarget.copy(t),this.orbitDistance=this.config.orbitDistance||10,this.updateOrbitCamera()}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),this.tourCamera.camera&&(this.tourCamera.camera.fov=t)}destroy(){this.detachInputHandlers(),this.disable(),this._onFPVUpdate&&(this.app.off("update",this._onFPVUpdate),this._onFPVUpdate=null),this.editCamera.destroy(),this.fpvEntity&&(this.fpvEntity.destroy(),this.fpvEntity=null)}}var jt;!function(t){t[t.selected=1]="selected",t[t.hidden=2]="hidden",t[t.deleted=4]="deleted"}(jt||(jt={}));const Yt=512,Zt=1536,Qt=[0,4,8,12,1,5,9,13,2,6,10,14];class Kt{constructor(e){this.device=e,this.count=1,this.version=0,this.texture=new t.Texture(e,{name:"splatTransformPalette",width:Zt,height:1,format:t.PIXELFORMAT_RGBA32F,mipmaps:!1,minFilter:t.FILTER_NEAREST,magFilter:t.FILTER_NEAREST,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE}),this.data=new Float32Array(6144),this._writeIdentity(0),this._upload()}alloc(t){const e=this.count;this.count+=t;const n=Math.ceil(this.count/Yt);n>this.texture.height&&this._resize(n);for(let n=0;n<t;n++)this._writeIdentity(e+n);return e}free(t){this.count=Math.max(1,this.count-t)}setTransform(t,e){const n=t%Yt*3,o=Math.floor(t/Yt)*Zt*4,i=e.data;for(let t=0;t<12;t++){const e=Math.floor(t/4),s=t%4;this.data[o+4*(n+e)+s]=i[Qt[t]]}}getTransform(e,n){const o=n??new t.Mat4,i=e%Yt*3,s=Math.floor(e/Yt)*Zt*4,a=o.data;a[3]=0,a[7]=0,a[11]=0,a[15]=1;for(let t=0;t<12;t++){const e=Math.floor(t/4),n=t%4;a[Qt[t]]=this.data[s+4*(i+e)+n]}return o}upload(){this._upload()}destroy(){this.texture.destroy()}_writeIdentity(t){const e=t%Yt*3,n=Math.floor(t/Yt)*Zt*4,o=n+4*e,i=n+4*(e+1),s=n+4*(e+2);for(let t=0;t<4;t++)this.data[o+t]=0,this.data[i+t]=0,this.data[s+t]=0;this.data[o+0]=1,this.data[i+1]=1,this.data[s+2]=1}_upload(){const t=this.texture.lock();t.set(this.data.subarray(0,t.length)),this.texture.unlock()}_resize(e){const n=this.data,o=this.texture.height,i=Math.max(e,2*o);this.texture.destroy(),this.texture=new t.Texture(this.device,{name:"splatTransformPalette",width:Zt,height:i,format:t.PIXELFORMAT_RGBA32F,mipmaps:!1,minFilter:t.FILTER_NEAREST,magFilter:t.FILTER_NEAREST,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE}),this.data=new Float32Array(Zt*i*4),this.data.set(n),this.version++}}const Jt=[{name:"x",type:"float",storage:"x"},{name:"y",type:"float",storage:"y"},{name:"z",type:"float",storage:"z"},{name:"nx",type:"float",storage:"nx"},{name:"ny",type:"float",storage:"ny"},{name:"nz",type:"float",storage:"nz"},{name:"f_dc_0",type:"float",storage:"f_dc_0"},{name:"f_dc_1",type:"float",storage:"f_dc_1"},{name:"f_dc_2",type:"float",storage:"f_dc_2"},{name:"opacity",type:"float",storage:"opacity"},{name:"scale_0",type:"float",storage:"scale_0"},{name:"scale_1",type:"float",storage:"scale_1"},{name:"scale_2",type:"float",storage:"scale_2"},{name:"rot_0",type:"float",storage:"rot_0"},{name:"rot_1",type:"float",storage:"rot_1"},{name:"rot_2",type:"float",storage:"rot_2"},{name:"rot_3",type:"float",storage:"rot_3"}],te=[];for(let t=0;t<45;t++)te.push(`f_rest_${t}`);const ee=[0,9,24,45];class ne{static async serialize(e,n){const o=e.entity.gsplat;if(!o)throw new Error("No gsplat component");let i=this._getGsplatData(o);if(!i)throw new Error("No gsplat data available");if("function"==typeof i.decompress){console.log("[SplatSerializer] Decompressing gsplat data...");const t=i.decompress();i=t instanceof Promise?await t:t,console.log("[SplatSerializer] Decompression complete")}let s=0;for(let t=0;t<e.numSplats;t++)e.state[t]&jt.deleted||s++;const a=Jt.filter(t=>{try{return null!==i.getProp(t.storage)}catch{return!1}});let r=[];for(const t of te)try{null!==i.getProp(t)&&r.push(t)}catch{}if(void 0!==n&&n>=0&&n<=3){const t=ee[n];r=r.filter((e,n)=>n<t)}if(0===a.length)throw new Error(`SplatSerializer: no readable properties found on gsplatData. Data type: ${i?.constructor?.name??typeof i}`);console.log(`[SplatSerializer] Writing ${s} splats (${a.length} props, ${r.length} SH)`);const l=["ply","format binary_little_endian 1.0",`element vertex ${s}`,...[...a.map(t=>`property ${t.type} ${t.name}`),...r.map(t=>`property float ${t}`)],"end_header",""].join("\n"),c=(new TextEncoder).encode(l),d=4*(a.length+r.length),p=c.length+s*d,h=new ArrayBuffer(p),u=new Uint8Array(h);u.set(c,0);const m=new ArrayBuffer(s*d),g=new Float32Array(m),f=a.map(t=>i.getProp(t.storage)),y=r.map(t=>i.getProp(t)),v=a.findIndex(t=>"x"===t.name),x=a.findIndex(t=>"y"===t.name),b=a.findIndex(t=>"z"===t.name),w=a.findIndex(t=>"rot_0"===t.name),S=a.findIndex(t=>"rot_1"===t.name),_=a.findIndex(t=>"rot_2"===t.name),M=a.findIndex(t=>"rot_3"===t.name),E=a.findIndex(t=>"scale_0"===t.name),C=a.findIndex(t=>"scale_1"===t.name),T=a.findIndex(t=>"scale_2"===t.name),A=new Map,P=n=>{if(0===n)return null;let o=A.get(n);if(!o){const i=e.transformPalette.getTransform(n),s=new t.Vec3,a=new t.Quat;i.getScale(s),a.setFromMat4(i),o={mat:i,rot:a,scale:s},A.set(n,o)}return o},L=new t.Vec3,k=new t.Quat;let z=0;for(let t=0;t<e.numSplats;t++){if(e.state[t]&jt.deleted)continue;const n=P(e.transform[t]);if(n){const e=f[v]?.[t]??0,o=f[x]?.[t]??0,i=f[b]?.[t]??0;L.set(e,o,i),n.mat.transformPoint(L,L),w>=0&&(k.set(f[S]?.[t]??0,f[_]?.[t]??0,f[M]?.[t]??0,f[w]?.[t]??1),k.mul2(n.rot,k));for(let e=0;e<f.length;e++){let o=f[e]?.[t]??0;e===v?o=L.x:e===x?o=L.y:e===b?o=L.z:e===w?o=k.w:e===S?o=k.x:e===_?o=k.y:e===M?o=k.z:e===E?o+=Math.log(Math.abs(n.scale.x)):e===C?o+=Math.log(Math.abs(n.scale.y)):e===T&&(o+=Math.log(Math.abs(n.scale.z))),g[z++]=o}}else for(let e=0;e<f.length;e++)g[z++]=f[e]?.[t]??0;for(let e=0;e<y.length;e++)g[z++]=y[e]?.[t]??0}return u.set(new Uint8Array(m),c.length),console.log(`[SplatSerializer] Done — ${(p/1024/1024).toFixed(1)}MB`),u}static _getGsplatData(t){const e=t.instance??t._instance,n=t._placement,o=e??n;if(o?.resource?.gsplatData)return o.resource.gsplatData;if(e?.splat?.gsplatData)return e.splat.gsplatData;const i=t.asset?.resource??t._resource??o?.resource;return i?.gsplatData?i.gsplatData:null}}class oe{constructor(e,n){this._stats={total:0,selected:0,hidden:0,deleted:0,shBands:0},this._material=null,this.tintClr=new t.Color(1,1,1),this.temperature=0,this.brightness=0,this.blackPoint=0,this.whitePoint=1,this.saturation=1,this.transparency=1,this._originalModifyVS=null,this.entity=e,this._app=n,this.device=n.graphicsDevice,this.transformPalette=new Kt(this.device);const o=e.gsplat;if(!o)throw new Error("SplatEditAttachment: entity has no gsplat component");const i=o.instance,s=o._placement;if(!i&&!s)throw new Error("SplatEditAttachment: gsplat instance/placement not ready");const a=o.asset?.resource??i?.resource??s?.resource,r=a?.textureDimensions??a?.streams?.textureDimensions??this._inferTextureDimensions(i??s);this.texWidth=r.x,this.texHeight=r.y,this.numSplats=this._getNumSplats(i??s,a),this.state=new Uint8Array(this.texWidth*this.texHeight),this.transform=new Uint16Array(this.texWidth*this.texHeight),this.stateTexture=new t.Texture(this.device,{name:"splatEditState",width:this.texWidth,height:this.texHeight,format:t.PIXELFORMAT_R8U,mipmaps:!1,minFilter:t.FILTER_NEAREST,magFilter:t.FILTER_NEAREST,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE}),this.transformTexture=new t.Texture(this.device,{name:"splatEditTransform",width:this.texWidth,height:this.texHeight,format:t.PIXELFORMAT_R16U,mipmaps:!1,minFilter:t.FILTER_NEAREST,magFilter:t.FILTER_NEAREST,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE}),this._uploadState(),this._uploadTransform(),this._applyShaders(),this._stats.total=this.numSplats,this._stats.shBands=this._detectSHBands()}get stats(){return{...this._stats}}updateState(){this._uploadState(),this._recountStats()}updateTransform(){this._uploadTransform()}getMaterial(){return this._material}updateMaterialParams(){if(!this._material)return;const t=-this.blackPoint+this.brightness,e=1/Math.max(.001,this.whitePoint-this.blackPoint);this._material.setParameter("clrOffset",[t,t,t]),this._material.setParameter("clrScale",[e*this.tintClr.r*(1+this.temperature),e*this.tintClr.g,e*this.tintClr.b*(1-this.temperature),this.transparency]),this._material.setParameter("saturation",this.saturation),this._material.setParameter("selectedClr",[.2,.4,1,.35]),this._material.setParameter("lockedClr",[.5,.5,.5,.5]),this._material.setParameter("transformPalette",this.transformPalette.texture),this._material.update()}destroy(){this._removeShaders(),this.stateTexture.destroy(),this.transformTexture.destroy(),this.transformPalette.destroy()}_applyShaders(){const t=this.entity.gsplat;if(!t)return;if(t._unified||t.unified?this._material=this._app.scene.gsplat?.material??null:this._material=t.material??null,!this._material)return void console.warn("SplatEditAttachment: could not find gsplat material");const e=this.device?.isWebGPU?"wgsl":"glsl",n=this._material.getShaderChunks(e);this._originalModifyVS=n.get("gsplatModifyVS")??null,n.set("gsplatModifyVS","\n\nuniform highp usampler2D splatState;\nuniform highp usampler2D splatTransform;\nuniform highp sampler2D transformPalette;\nuniform vec4 selectedClr;\nuniform vec4 lockedClr;\nuniform vec3 clrOffset;\nuniform vec4 clrScale;\nuniform float saturation;\n\nvoid modifySplatCenter(inout vec3 center) {\n // Read per-splat state\n uint vertexState = texelFetch(splatState, splat.uv, 0).r & 7u;\n\n // Deleted splats: push to clip space discard position\n if ((vertexState & 4u) != 0u) {\n center = vec3(0.0, 0.0, 1e10);\n return;\n }\n\n // Hidden splats: push to clip space discard position\n if ((vertexState & 2u) != 0u) {\n center = vec3(0.0, 0.0, 1e10);\n return;\n }\n\n // Apply per-splat transform from palette\n uint transformIndex = texelFetch(splatTransform, splat.uv, 0).r;\n if (transformIndex != 0u) {\n int u = int(transformIndex % 512u) * 3;\n int v = int(transformIndex / 512u);\n mat3x4 t;\n t[0] = texelFetch(transformPalette, ivec2(u, v), 0);\n t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);\n t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);\n\n mat4 paletteMat = transpose(mat4(t[0], t[1], t[2], vec4(0.0, 0.0, 0.0, 1.0)));\n center = (paletteMat * vec4(center, 1.0)).xyz;\n }\n}\n\nvoid modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) {\n // Could apply palette rotation/scale here if needed\n}\n\nvoid modifySplatColor(vec3 center, inout vec4 color) {\n // Read per-splat state\n uint vertexState = texelFetch(splatState, splat.uv, 0).r & 7u;\n\n // Apply color adjustments\n color.rgb = (color.rgb + clrOffset) * clrScale.rgb;\n\n // Saturation\n float grey = dot(color.rgb, vec3(0.299, 0.587, 0.114));\n color.rgb = mix(vec3(grey), color.rgb, saturation);\n\n // Transparency\n color.a *= clrScale.a;\n\n // Selected: blend with selection color\n if ((vertexState & 1u) != 0u) {\n color.rgb = mix(color.rgb, selectedClr.rgb, selectedClr.a);\n }\n}\n"),this._material.setParameter("splatState",this.stateTexture),this._material.setParameter("splatTransform",this.transformTexture),this._material.setParameter("transformPalette",this.transformPalette.texture),this._material.setParameter("clrOffset",[0,0,0]),this._material.setParameter("clrScale",[1,1,1,1]),this._material.setParameter("saturation",1),this._material.setParameter("selectedClr",[.2,.4,1,.35]),this._material.setParameter("lockedClr",[.5,.5,.5,.5]),this._material.update()}_removeShaders(){if(!this._material)return;const t=this.device?.isWebGPU?"wgsl":"glsl",e=this._material.getShaderChunks(t);null!==this._originalModifyVS?e.set("gsplatModifyVS",this._originalModifyVS):e.delete("gsplatModifyVS"),this._material.update(),this._material=null}_uploadState(){const t=this.stateTexture.lock();t.set(this.state.subarray(0,t.length)),this.stateTexture.unlock()}_uploadTransform(){const t=this.transformTexture.lock();t.set(this.transform.subarray(0,t.length)),this.transformTexture.unlock()}_recountStats(){let t=0,e=0,n=0;for(let o=0;o<this.numSplats;o++){const i=this.state[o];i&jt.selected&&t++,i&jt.hidden&&e++,i&jt.deleted&&n++}this._stats={total:this.numSplats,selected:t,hidden:e,deleted:n,shBands:this._stats.shBands}}_detectSHBands(){const t=this.entity.gsplat;if(!t)return 0;const e=t.instance??t._instance,n=t._placement,o=e??n,i=t.asset?.resource??o?.resource,s=i?.gsplatData??e?.splat?.gsplatData;if(!s)return 0;let a=0;for(let t=0;t<45;t++)try{if(null==s.getProp(`f_rest_${t}`))break;a++}catch{break}return function(t){return t>=25?3:t>=10?2:t>=1?1:0}(a)}_inferTextureDimensions(t){const e=t?.sorter;if(e?.centers?.width)return{x:e.centers.width,y:e.centers.height};const n=t?.splat?.numSplats??t?.numSplats??0,o=Math.ceil(Math.sqrt(n));return{x:o,y:Math.ceil(n/o)}}_getNumSplats(t,e){return e?.gsplatData?.numSplats?e.gsplatData.numSplats:t?.splat?.numSplats?t.splat.numSplats:t?.numSplats?t.numSplats:this.texWidth*this.texHeight}}const ie={mask:0,rect:1,sphere:2,box:3};class se{constructor(t){this.glProgram=null,this.glFramebuffer=null,this.glResultTexture=null,this.glQuadVAO=null,this.glQuadVBO=null,this.resultWidth=0,this.resultHeight=0,this.device=t}async run(t,e,n,o,i,s,a,r){const l=this.device.gl;if(!l)return console.warn("[SplatEdit] Intersect: no WebGL2 context"),new Uint8Array(t);this._ensureProgram(l),this._ensureResultTexture(l,t);const c=this.glProgram,d=l.getParameter(l.CURRENT_PROGRAM),p=l.getParameter(l.FRAMEBUFFER_BINDING),h=l.getParameter(l.VERTEX_ARRAY_BINDING),u=l.getParameter(l.VIEWPORT),m=l.getParameter(l.ACTIVE_TEXTURE);l.useProgram(c),l.bindFramebuffer(l.FRAMEBUFFER,this.glFramebuffer),l.viewport(0,0,this.resultWidth,this.resultHeight),l.disable(l.DEPTH_TEST),l.disable(l.BLEND),l.disable(l.SCISSOR_TEST);let g=8;const f=[],y=(t,e)=>{const n=l.getUniformLocation(c,t);if(null===n)return;const o=g++;f.push(o),l.activeTexture(l.TEXTURE0+o);const i=e.impl;let s=i?._glTexture??i?.glTexture??null;s||(s=this._uploadPcTexture(l,e)),s?l.bindTexture(l.TEXTURE_2D,s):console.warn(`[SplatEdit] Intersect: no GL handle for '${t}'`),l.uniform1i(n,o)};y("transformA",e),y("splatState",n),y("splatTransform",o),y("transformPalette",i);const v=(t,e)=>{const n=l.getUniformLocation(c,t);n&&l.uniformMatrix4fv(n,!1,e)},x=(t,e,n)=>{const o=l.getUniformLocation(c,t);o&&l.uniform2f(o,e,n)},b=(t,e,n,o)=>{const i=l.getUniformLocation(c,t);i&&l.uniform3f(i,e,n,o)};v("matrix_model",s.data),x("splat_params",a.x,a.y),((t,e)=>{const n=l.getUniformLocation(c,t);null!==n&&l.uniform1i(n,e)})("mode",ie[r.mode]),x("resultParams",this.resultWidth,t),"mask"===r.mode?(y("maskTexture",r.maskTexture),v("matrix_viewProjection",r.viewProjection.data)):"rect"===r.mode?(((t,e,n,o,i)=>{const s=l.getUniformLocation(c,t);s&&l.uniform4f(s,e,n,o,i)})("rectBounds",r.rect.minX,r.rect.minY,r.rect.maxX,r.rect.maxY),v("matrix_viewProjection",r.viewProjection.data)):"sphere"===r.mode?(b("sphereCenter",r.center.x,r.center.y,r.center.z),((t,e)=>{const n=l.getUniformLocation(c,t);null!==n&&l.uniform1f(n,e)})("sphereRadius",r.radius)):"box"===r.mode&&(b("boxMin",r.min.x,r.min.y,r.min.z),b("boxMax",r.max.x,r.max.y,r.max.z)),l.bindVertexArray(this.glQuadVAO),l.drawArrays(l.TRIANGLE_STRIP,0,4);const w=new Uint8Array(this.resultWidth*this.resultHeight*4);l.readPixels(0,0,this.resultWidth,this.resultHeight,l.RGBA,l.UNSIGNED_BYTE,w);for(const t of f){l.activeTexture(l.TEXTURE0+t),l.bindTexture(l.TEXTURE_2D,null);const e=this.device.textureUnits;e?.[t]&&(e[t][0]=null)}l.bindVertexArray(h),l.bindFramebuffer(l.FRAMEBUFFER,p),l.useProgram(d),l.viewport(u[0],u[1],u[2],u[3]),l.activeTexture(m);const S=new Uint8Array(t);let _=0;for(let e=0;e<t;e++){const t=Math.floor(e/4),n=e%4;S[e]=w[4*t+n]>127?1:0,S[e]&&_++}return console.log(`[SplatEdit] Intersect: ${_}/${t} selected`),S}destroy(){const t=this.device.gl;t&&(this.glProgram&&t.deleteProgram(this.glProgram),this.glFramebuffer&&t.deleteFramebuffer(this.glFramebuffer),this.glResultTexture&&t.deleteTexture(this.glResultTexture),this.glQuadVAO&&t.deleteVertexArray(this.glQuadVAO),this.glQuadVBO&&t.deleteBuffer(this.glQuadVBO)),this.glProgram=null,this.glFramebuffer=null,this.glResultTexture=null,this.glQuadVAO=null,this.glQuadVBO=null}_uploadPcTexture(e,n){const o=n._levels?.[0];if(!o)return console.warn("[SplatEdit] _uploadPcTexture: no pixel data in _levels[0]"),null;const i=e.createTexture();if(!i)return null;e.bindTexture(e.TEXTURE_2D,i);const s=n.format;let a,r,l;if(s===t.PIXELFORMAT_RGBA8)a=e.RGBA8,r=e.RGBA,l=e.UNSIGNED_BYTE;else if(s===t.PIXELFORMAT_RGBA32F)a=e.RGBA32F,r=e.RGBA,l=e.FLOAT;else if(s===t.PIXELFORMAT_R8)a=e.R8,r=e.RED,l=e.UNSIGNED_BYTE;else if(s===t.PIXELFORMAT_R8U||33===s)a=e.R8UI,r=e.RED_INTEGER,l=e.UNSIGNED_BYTE;else{if(s!==t.PIXELFORMAT_R16U&&35!==s)return console.warn("[SplatEdit] _uploadPcTexture: unsupported format",s),e.deleteTexture(i),null;a=e.R16UI,r=e.RED_INTEGER,l=e.UNSIGNED_SHORT}e.texImage2D(e.TEXTURE_2D,0,a,n.width,n.height,0,r,l,o);const c=n.minFilter===t.FILTER_LINEAR?e.LINEAR:e.NEAREST,d=n.magFilter===t.FILTER_LINEAR?e.LINEAR:e.NEAREST;e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,c),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,d),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE);const p=n.impl;return p&&(p._glTexture=i),i}_ensureProgram(t){if(this.glProgram)return;const e=t.createShader(t.VERTEX_SHADER);t.shaderSource(e,"#version 300 es\nin vec2 vertex_position;\nout vec2 vUv;\n\nvoid main() {\n vUv = vertex_position * 0.5 + 0.5;\n gl_Position = vec4(vertex_position, 0.0, 1.0);\n}\n"),t.compileShader(e),t.getShaderParameter(e,t.COMPILE_STATUS)||console.error("[SplatEdit] VS compile error:",t.getShaderInfoLog(e));const n=t.createShader(t.FRAGMENT_SHADER);t.shaderSource(n,"#version 300 es\nprecision highp float;\nprecision highp int;\nprecision highp usampler2D;\n\nin vec2 vUv;\nout vec4 fragColor;\n\n// Splat data\nuniform highp sampler2D transformA; // Splat centers (RGBA32F)\nuniform highp usampler2D splatState; // Per-splat state (selected/hidden/deleted)\nuniform highp usampler2D splatTransform; // Per-splat palette index\nuniform highp sampler2D transformPalette; // Transform palette\n\n// Camera\nuniform mat4 matrix_model;\nuniform mat4 matrix_viewProjection;\n\n// Splat data dimensions\nuniform vec2 splat_params; // x = 1/width, y = 1/height of transformA\n\n// Selection params\nuniform int mode; // 0=mask, 1=rect, 2=sphere, 3=box\nuniform sampler2D maskTexture; // For mode 0\nuniform vec4 rectBounds; // For mode 1: (minX, minY, maxX, maxY) in NDC\nuniform vec3 sphereCenter; // For mode 2\nuniform float sphereRadius; // For mode 2\nuniform vec3 boxMin; // For mode 3\nuniform vec3 boxMax; // For mode 3\n\n// Result texture dimensions\nuniform vec2 resultParams; // x = resultWidth, y = total splats\n\n// Read a splat center from the data texture\nvec3 readCenter(int splatIndex) {\n int texWidth = int(1.0 / splat_params.x);\n int u = splatIndex % texWidth;\n int v = splatIndex / texWidth;\n return texelFetch(transformA, ivec2(u, v), 0).xyz;\n}\n\n// Apply per-splat transform from palette\nvec3 applyTransform(vec3 pos, int splatIndex) {\n int texWidth = int(1.0 / splat_params.x);\n int su = splatIndex % texWidth;\n int sv = splatIndex / texWidth;\n uint transformIndex = texelFetch(splatTransform, ivec2(su, sv), 0).r;\n\n if (transformIndex == 0u) {\n return (matrix_model * vec4(pos, 1.0)).xyz;\n }\n\n int pu = int(transformIndex % 512u) * 3;\n int pv = int(transformIndex / 512u);\n mat3x4 t;\n t[0] = texelFetch(transformPalette, ivec2(pu, pv), 0);\n t[1] = texelFetch(transformPalette, ivec2(pu + 1, pv), 0);\n t[2] = texelFetch(transformPalette, ivec2(pu + 2, pv), 0);\n\n mat4 paletteTransform = transpose(mat4(t[0], t[1], t[2], vec4(0.0, 0.0, 0.0, 1.0)));\n return (matrix_model * paletteTransform * vec4(pos, 1.0)).xyz;\n}\n\n// Test a single splat against the selection criteria\nfloat testSplat(int splatIndex) {\n int totalSplats = int(resultParams.y);\n if (splatIndex >= totalSplats) return 0.0;\n\n // Read splat state — skip deleted (bit 2) and hidden (bit 1)\n int texWidth = int(1.0 / splat_params.x);\n int su = splatIndex % texWidth;\n int sv = splatIndex / texWidth;\n uint state = texelFetch(splatState, ivec2(su, sv), 0).r & 7u;\n if ((state & 6u) != 0u) return 0.0;\n\n vec3 center = readCenter(splatIndex);\n vec3 worldPos = applyTransform(center, splatIndex);\n\n if (mode == 0) {\n // Mask: project to NDC, sample mask texture\n vec4 clip = matrix_viewProjection * vec4(worldPos, 1.0);\n vec2 ndc = clip.xy / clip.w;\n vec2 maskUv = vec2(ndc.x * 0.5 + 0.5, -ndc.y * 0.5 + 0.5);\n if (maskUv.x < 0.0 || maskUv.x > 1.0 || maskUv.y < 0.0 || maskUv.y > 1.0) return 0.0;\n if (clip.w <= 0.0) return 0.0;\n return texture(maskTexture, maskUv).a > 0.1 ? 1.0 : 0.0;\n } else if (mode == 1) {\n // Rectangle: test NDC bounds\n vec4 clip = matrix_viewProjection * vec4(worldPos, 1.0);\n if (clip.w <= 0.0) return 0.0;\n vec2 ndc = clip.xy / clip.w;\n return (ndc.x >= rectBounds.x && ndc.x <= rectBounds.z &&\n ndc.y >= rectBounds.y && ndc.y <= rectBounds.w) ? 1.0 : 0.0;\n } else if (mode == 2) {\n // Sphere: world-space distance test\n float dist = length(worldPos - sphereCenter);\n return dist <= sphereRadius ? 1.0 : 0.0;\n } else if (mode == 3) {\n // Box: world-space AABB containment test\n return (worldPos.x >= boxMin.x && worldPos.x <= boxMax.x &&\n worldPos.y >= boxMin.y && worldPos.y <= boxMax.y &&\n worldPos.z >= boxMin.z && worldPos.z <= boxMax.z) ? 1.0 : 0.0;\n }\n\n return 0.0;\n}\n\nvoid main() {\n int resultWidth = int(resultParams.x);\n int px = int(gl_FragCoord.x);\n int py = int(gl_FragCoord.y);\n\n // Each pixel processes 4 splats\n int baseIndex = (py * resultWidth + px) * 4;\n\n fragColor = vec4(\n testSplat(baseIndex),\n testSplat(baseIndex + 1),\n testSplat(baseIndex + 2),\n testSplat(baseIndex + 3)\n );\n}\n"),t.compileShader(n),t.getShaderParameter(n,t.COMPILE_STATUS)||console.error("[SplatEdit] FS compile error:",t.getShaderInfoLog(n));const o=t.createProgram();t.attachShader(o,e),t.attachShader(o,n),t.linkProgram(o),t.getProgramParameter(o,t.LINK_STATUS)||console.error("[SplatEdit] Program link error:",t.getProgramInfoLog(o)),t.deleteShader(e),t.deleteShader(n),this.glProgram=o,this.glQuadVBO=t.createBuffer(),t.bindBuffer(t.ARRAY_BUFFER,this.glQuadVBO),t.bufferData(t.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,1,1]),t.STATIC_DRAW),this.glQuadVAO=t.createVertexArray(),t.bindVertexArray(this.glQuadVAO);const i=t.getAttribLocation(o,"vertex_position");t.enableVertexAttribArray(i),t.vertexAttribPointer(i,2,t.FLOAT,!1,0,0),t.bindVertexArray(null)}_ensureResultTexture(t,e){const n=Math.ceil(e/4),o=Math.max(1,Math.min(2048,Math.ceil(Math.sqrt(n)))),i=Math.max(1,Math.ceil(n/o));if(this.resultWidth===o&&this.resultHeight===i)return;this.glFramebuffer&&t.deleteFramebuffer(this.glFramebuffer),this.glResultTexture&&t.deleteTexture(this.glResultTexture),this.resultWidth=o,this.resultHeight=i,this.glResultTexture=t.createTexture(),t.bindTexture(t.TEXTURE_2D,this.glResultTexture),t.texImage2D(t.TEXTURE_2D,0,t.RGBA8,o,i,0,t.RGBA,t.UNSIGNED_BYTE,null),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MIN_FILTER,t.NEAREST),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MAG_FILTER,t.NEAREST),t.bindTexture(t.TEXTURE_2D,null),this.glFramebuffer=t.createFramebuffer(),t.bindFramebuffer(t.FRAMEBUFFER,this.glFramebuffer),t.framebufferTexture2D(t.FRAMEBUFFER,t.COLOR_ATTACHMENT0,t.TEXTURE_2D,this.glResultTexture,0);const s=t.checkFramebufferStatus(t.FRAMEBUFFER);s!==t.FRAMEBUFFER_COMPLETE&&console.error("[SplatEdit] Framebuffer incomplete:",s),t.bindFramebuffer(t.FRAMEBUFFER,null)}}class ae{constructor(t){this.shader=null,this.device=t}async run(e,n,o,i,s,a,r){this._ensureShader();const l=this.device,c=n.width,d=e=>new t.Texture(l,{name:e,width:c,height:1,format:t.PIXELFORMAT_RGBA32F,mipmaps:!1,minFilter:t.FILTER_NEAREST,magFilter:t.FILTER_NEAREST}),p=d("selMin"),h=d("selMax"),u=d("visMin"),m=d("visMax"),g=new t.RenderTarget({colorBuffers:[p,h,u,m],depth:!1});l.scope.resolve("transformA").setValue(n),l.scope.resolve("splatState").setValue(o),l.scope.resolve("splatTransform").setValue(i),l.scope.resolve("transformPalette").setValue(s),l.scope.resolve("matrix_model").setValue(a.data),l.scope.resolve("splat_params").setValue([r.x,r.y]),l.scope.resolve("totalSplats").setValue(e),t.drawQuadWithShader(l,g,this.shader);const f=async e=>{const n=new t.RenderTarget({colorBuffer:e,depth:!1}),o=new Float32Array(4*c),i=l.gl;return i&&(i.bindFramebuffer(i.FRAMEBUFFER,n.impl?._glFrameBuffer),i.readPixels(0,0,c,1,i.RGBA,i.FLOAT,o)),n.destroy(),o},[y,v,x,b]=await Promise.all([f(p),f(h),f(u),f(m)]),w=(e,n)=>{const o=new t.Vec3(1/0,1/0,1/0),i=new t.Vec3(-1/0,-1/0,-1/0);for(let t=0;t<c;t++){const s=e[4*t],a=e[4*t+1],r=e[4*t+2],l=n[4*t],c=n[4*t+1],d=n[4*t+2];isFinite(s)&&(s<o.x&&(o.x=s),a<o.y&&(o.y=a),r<o.z&&(o.z=r)),isFinite(l)&&(l>i.x&&(i.x=l),c>i.y&&(i.y=c),d>i.z&&(i.z=d))}return isFinite(o.x)?{min:o,max:i}:null},S={selectionBound:w(y,v),visibleBound:w(x,b)};return g.destroy(),p.destroy(),h.destroy(),u.destroy(),m.destroy(),S}destroy(){this.shader=null}_ensureShader(){this.shader||(this.shader=new t.Shader(this.device,{name:"splatCalcBound",vshader:"#version 300 es\nin vec2 vertex_position;\nout vec2 vUv;\n\nvoid main() {\n vUv = vertex_position * 0.5 + 0.5;\n gl_Position = vec4(vertex_position, 0.0, 1.0);\n}\n",fshader:"#version 300 es\nprecision highp float;\nprecision highp int;\nprecision highp usampler2D;\n\nin vec2 vUv;\nlayout(location = 0) out vec4 outSelMin;\nlayout(location = 1) out vec4 outSelMax;\nlayout(location = 2) out vec4 outVisMin;\nlayout(location = 3) out vec4 outVisMax;\n\nuniform highp sampler2D transformA;\nuniform highp usampler2D splatState;\nuniform highp usampler2D splatTransform;\nuniform highp sampler2D transformPalette;\nuniform mat4 matrix_model;\nuniform vec2 splat_params; // x = 1/width, y = 1/height\nuniform float totalSplats;\n\nvec3 applyTransform(vec3 pos, ivec2 uv) {\n uint transformIndex = texelFetch(splatTransform, uv, 0).r;\n\n mat4 model = matrix_model;\n if (transformIndex != 0u) {\n int pu = int(transformIndex % 512u) * 3;\n int pv = int(transformIndex / 512u);\n mat3x4 t;\n t[0] = texelFetch(transformPalette, ivec2(pu, pv), 0);\n t[1] = texelFetch(transformPalette, ivec2(pu + 1, pv), 0);\n t[2] = texelFetch(transformPalette, ivec2(pu + 2, pv), 0);\n model = model * transpose(mat4(t[0], t[1], t[2], vec4(0.0, 0.0, 0.0, 1.0)));\n }\n\n return (model * vec4(pos, 1.0)).xyz;\n}\n\nvoid main() {\n int texWidth = int(1.0 / splat_params.x);\n int texHeight = int(1.0 / splat_params.y);\n int col = int(gl_FragCoord.x);\n\n // Initialize bounds to infinity\n vec3 selMin = vec3(1e30);\n vec3 selMax = vec3(-1e30);\n vec3 visMin = vec3(1e30);\n vec3 visMax = vec3(-1e30);\n\n for (int row = 0; row < texHeight; row++) {\n int splatIndex = row * texWidth + col;\n if (splatIndex >= int(totalSplats)) break;\n\n ivec2 uv = ivec2(col, row);\n uint state = texelFetch(splatState, uv, 0).r & 7u;\n\n // Skip deleted splats\n if ((state & 4u) != 0u) continue;\n\n vec3 center = texelFetch(transformA, uv, 0).xyz;\n vec3 worldPos = applyTransform(center, uv);\n\n // Check for infinity/NaN\n if (isinf(worldPos.x) || isnan(worldPos.x)) continue;\n\n // Visible bounds (everything not deleted)\n visMin = min(visMin, worldPos);\n visMax = max(visMax, worldPos);\n\n // Selected bounds\n if ((state & 1u) != 0u) {\n selMin = min(selMin, worldPos);\n selMax = max(selMax, worldPos);\n }\n }\n\n outSelMin = vec4(selMin, 1.0);\n outSelMax = vec4(selMax, 1.0);\n outVisMin = vec4(visMin, 1.0);\n outVisMax = vec4(visMax, 1.0);\n}\n"}))}}class re{constructor(t){this.shader=null,this.device=t}async run(e,n,o,i,s){this._ensureShader();const a=this.device,r=e.width,l=e.height,c=new t.Texture(a,{name:"splatPositions",width:r,height:l,format:t.PIXELFORMAT_RGBA32F,mipmaps:!1,minFilter:t.FILTER_NEAREST,magFilter:t.FILTER_NEAREST}),d=new t.RenderTarget({colorBuffer:c,depth:!1});a.scope.resolve("transformA").setValue(e),a.scope.resolve("splatTransform").setValue(n),a.scope.resolve("transformPalette").setValue(o),a.scope.resolve("matrix_model").setValue(i.data),a.scope.resolve("splat_params").setValue([s.x,s.y]),t.drawQuadWithShader(a,d,this.shader);const p=new Float32Array(r*l*4),h=a.gl;return h&&(h.bindFramebuffer(h.FRAMEBUFFER,d.impl?._glFrameBuffer),h.readPixels(0,0,r,l,h.RGBA,h.FLOAT,p)),d.destroy(),c.destroy(),p}destroy(){this.shader=null}_ensureShader(){this.shader||(this.shader=new t.Shader(this.device,{name:"splatCalcPositions",vshader:"#version 300 es\nin vec2 vertex_position;\nout vec2 vUv;\n\nvoid main() {\n vUv = vertex_position * 0.5 + 0.5;\n gl_Position = vec4(vertex_position, 0.0, 1.0);\n}\n",fshader:"#version 300 es\nprecision highp float;\nprecision highp int;\nprecision highp usampler2D;\n\nin vec2 vUv;\nout vec4 fragColor;\n\nuniform highp sampler2D transformA;\nuniform highp usampler2D splatTransform;\nuniform highp sampler2D transformPalette;\nuniform mat4 matrix_model;\nuniform vec2 splat_params; // x = 1/width, y = 1/height\n\nvoid main() {\n int texWidth = int(1.0 / splat_params.x);\n int col = int(gl_FragCoord.x);\n int row = int(gl_FragCoord.y);\n\n ivec2 uv = ivec2(col, row);\n vec3 center = texelFetch(transformA, uv, 0).xyz;\n\n // Apply per-splat transform from palette\n uint transformIndex = texelFetch(splatTransform, uv, 0).r;\n\n mat4 model = matrix_model;\n if (transformIndex != 0u) {\n int pu = int(transformIndex % 512u) * 3;\n int pv = int(transformIndex / 512u);\n mat3x4 t;\n t[0] = texelFetch(transformPalette, ivec2(pu, pv), 0);\n t[1] = texelFetch(transformPalette, ivec2(pu + 1, pv), 0);\n t[2] = texelFetch(transformPalette, ivec2(pu + 2, pv), 0);\n model = model * transpose(mat4(t[0], t[1], t[2], vec4(0.0, 0.0, 0.0, 1.0)));\n }\n\n vec4 worldPos = model * vec4(center, 1.0);\n fragColor = vec4(worldPos.xyz / worldPos.w, 0.0);\n}\n"}))}}class le{constructor(t){this._queue=Promise.resolve(),this._centersTexture=null,this.device=t,this._intersect=new se(t),this._calcBound=new ae(t),this._calcPositions=new re(t)}intersect(e,n){return this._enqueue(()=>{const o=new t.Vec2(1/e.texWidth,1/e.texHeight),i=e.entity.getWorldTransform(),s=this._getTransformATexture(e);return s?this._intersect.run(e.numSplats,s,e.stateTexture,e.transformTexture,e.transformPalette.texture,i,o,n):(console.warn("[SplatEdit] intersect: no transformA texture, returning empty result"),Promise.resolve(new Uint8Array(e.numSplats)))})}calcBound(e){return this._enqueue(()=>{const n=new t.Vec2(1/e.texWidth,1/e.texHeight),o=e.entity.getWorldTransform(),i=this._getTransformATexture(e);return i?this._calcBound.run(e.numSplats,i,e.stateTexture,e.transformTexture,e.transformPalette.texture,o,n):Promise.resolve({selectionBound:null,visibleBound:null})})}calcPositions(e){return this._enqueue(()=>{const n=new t.Vec2(1/e.texWidth,1/e.texHeight),o=e.entity.getWorldTransform(),i=this._getTransformATexture(e);return i?this._calcPositions.run(i,e.transformTexture,e.transformPalette.texture,o,n):Promise.resolve(new Float32Array(0))})}destroy(){this._intersect.destroy(),this._calcBound.destroy(),this._calcPositions.destroy(),this._centersTexture?.destroy(),this._centersTexture=null}_getTransformATexture(e){if(this._centersTexture)return this._centersTexture;const n=e.entity.gsplat;if(!n)return null;const o=n.instance??n._instance,i=n._placement,s=o??i,a=n.asset?.resource??s?.resource??n._resource,r=a?.streams??s?._streams;if(r){if("function"==typeof r.getTexture){const e=r.getTexture("dataCenter");if(e&&e.format===t.PIXELFORMAT_RGBA32F)return e}if(r.textures instanceof Map)for(const e of["dataCenter","means"]){const n=r.textures.get(e);if(n&&n.format===t.PIXELFORMAT_RGBA32F)return n}}if(s?.sorter?.centers?.format===t.PIXELFORMAT_RGBA32F)return s.sorter.centers;if(s?.splat?.transformA?.format===t.PIXELFORMAT_RGBA32F)return s.splat.transformA;const l=a?.centers??null;if(l&&l.length>0){const n=e.texWidth,o=e.texHeight,i=new t.Texture(this.device,{name:"splatEditCenters",width:n,height:o,format:t.PIXELFORMAT_RGBA32F,mipmaps:!1,minFilter:t.FILTER_NEAREST,magFilter:t.FILTER_NEAREST,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE}),s=i.lock(),a=Math.min(e.numSplats,Math.floor(l.length/3));for(let t=0;t<a;t++)s[4*t+0]=l[3*t+0],s[4*t+1]=l[3*t+1],s[4*t+2]=l[3*t+2],s[4*t+3]=1;return i.unlock(),this._centersTexture=i,i}return console.warn("[SplatEdit] _getTransformATexture: no centers data found.","streams keys:",r?.textures instanceof Map?[...r.textures.keys()]:"N/A","resource.centers:",!!l),null}_enqueue(t){const e=this._queue.then(t);return this._queue=e.then(()=>{},()=>{}),e}}class ce{constructor(){this._undoStack=[],this._redoStack=[],this._maxSize=100}get canUndo(){return this._undoStack.length>0}get canRedo(){return this._redoStack.length>0}execute(t){t.do();for(const t of this._redoStack)t.destroy?.();for(this._redoStack.length=0,this._undoStack.push(t);this._undoStack.length>this._maxSize;){const t=this._undoStack.shift();t?.destroy?.()}this.onChange?.()}undo(){const t=this._undoStack.pop();t&&(t.undo(),this._redoStack.push(t),this.onChange?.())}redo(){const t=this._redoStack.pop();t&&(t.do(),this._undoStack.push(t),this.onChange?.())}clear(){for(const t of this._undoStack)t.destroy?.();for(const t of this._redoStack)t.destroy?.();this._undoStack.length=0,this._redoStack.length=0,this.onChange?.()}}class de{constructor(t,e,n){this.name="Select",this.attachment=t,this.op=e,this.selectionResult=n,this.prevState=new Uint8Array(t.state)}do(){const{state:t}=this.attachment,e=this.attachment.numSplats;for(let n=0;n<e;n++)t[n]&jt.deleted||("set"===this.op?this.selectionResult[n]?t[n]|=jt.selected:t[n]&=~jt.selected:"add"===this.op?this.selectionResult[n]&&(t[n]|=jt.selected):"remove"===this.op&&this.selectionResult[n]&&(t[n]&=~jt.selected));this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class pe{constructor(t){this.name="Select All",this.attachment=t,this.prevState=new Uint8Array(t.state)}do(){const{state:t}=this.attachment,e=this.attachment.numSplats;for(let n=0;n<e;n++)t[n]&jt.deleted||(t[n]|=jt.selected);this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class he{constructor(t){this.name="Select None",this.attachment=t,this.prevState=new Uint8Array(t.state)}do(){const{state:t}=this.attachment,e=this.attachment.numSplats;for(let n=0;n<e;n++)t[n]&=~jt.selected;this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class ue{constructor(t){this.name="Invert Selection",this.attachment=t,this.prevState=new Uint8Array(t.state)}do(){const{state:t}=this.attachment,e=this.attachment.numSplats;for(let n=0;n<e;n++)t[n]&jt.deleted||(t[n]^=jt.selected);this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class me{constructor(t){this.name="Hide Selection",this.attachment=t,this.prevState=new Uint8Array(t.state)}do(){const{state:t}=this.attachment,e=this.attachment.numSplats;for(let n=0;n<e;n++)t[n]&jt.selected&&(t[n]=(t[n]|jt.hidden)&~jt.selected);this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class ge{constructor(t){this.name="Unhide All",this.attachment=t,this.prevState=new Uint8Array(t.state)}do(){const{state:t}=this.attachment,e=this.attachment.numSplats;for(let n=0;n<e;n++)t[n]&=~jt.hidden;this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class fe{constructor(t){this.name="Delete Selection",this.attachment=t,this.prevState=new Uint8Array(t.state)}do(){const{state:t}=this.attachment,e=this.attachment.numSplats;for(let n=0;n<e;n++)t[n]&jt.selected&&(t[n]=(t[n]|jt.deleted)&~jt.selected);this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class ye{constructor(t){this.name="Reset Deleted",this.attachment=t,this.prevState=new Uint8Array(t.state)}do(){const{state:t}=this.attachment,e=this.attachment.numSplats;for(let n=0;n<e;n++)t[n]&=~jt.deleted;this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class ve{constructor(t,e,n,o="Brush Select"){this.attachment=t,this.prevState=e,this.newState=n,this.name=o}do(){this.attachment.state.set(this.newState),this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class xe{constructor(t,e){this.name="Transform Splats",this.newTransformIndices=null,this.allocatedBase=0,this.allocatedCount=0,this.attachment=t,this.transform=e.clone(),this.prevTransformIndices=new Uint16Array(t.transform)}do(){const{state:e,transform:n}=this.attachment,o=this.attachment.transformPalette,i=this.attachment.numSplats,s=new Set;for(let t=0;t<i;t++)e[t]&jt.selected&&s.add(n[t]);this.allocatedCount=s.size,this.allocatedBase=o.alloc(this.allocatedCount);const a=new Map;let r=0;for(const e of s){const n=this.allocatedBase+r;a.set(e,n);const i=o.getTransform(e),s=new t.Mat4;s.mul2(this.transform,i),o.setTransform(n,s),r++}o.upload(),this.attachment.updateMaterialParams(),this.newTransformIndices=new Uint16Array(i);for(let t=0;t<i;t++)e[t]&jt.selected&&a.has(n[t])?(this.newTransformIndices[t]=a.get(n[t]),n[t]=this.newTransformIndices[t]):this.newTransformIndices[t]=n[t];this.attachment.updateTransform()}undo(){this.attachment.transform.set(this.prevTransformIndices),this.attachment.updateTransform(),this.allocatedCount>0&&(this.attachment.transformPalette.free(this.allocatedCount),this.attachment.transformPalette.upload())}destroy(){}}class be{constructor(t,e){this._attachment=null,this._active=!1,this._previewBaseState=null,this._app=t,this._camera=e,this._dataProcessor=new le(t.graphicsDevice),this._history=new ce,this._history.onChange=()=>{this.onUndoRedoChange?.(this._history.canUndo,this._history.canRedo),this._notifyStats()}}activate(t){this._active&&this.deactivate(),this._attachment=new oe(t,this._app),this._attachment.updateMaterialParams(),this._active=!0,this._notifyStats()}deactivate(){this._active&&(this._history.clear(),this._attachment?.destroy(),this._attachment=null,this._active=!1)}isActive(){return this._active}getAttachment(){return this._attachment}async selectByRect(t,e){if(!this._attachment)return;const n=this._getViewProjection(),o=await this._dataProcessor.intersect(this._attachment,{mode:"rect",rect:e,viewProjection:n});this._history.execute(new de(this._attachment,t,o))}async selectByMask(t,e){if(!this._attachment)return;const n=this._getViewProjection(),o=await this._dataProcessor.intersect(this._attachment,{mode:"mask",maskTexture:e,viewProjection:n});this._history.execute(new de(this._attachment,t,o))}async selectBySphere(t,e,n){if(!this._attachment)return;const o=await this._dataProcessor.intersect(this._attachment,{mode:"sphere",center:e,radius:n});this._history.execute(new de(this._attachment,t,o))}async selectByBox(t,e,n){if(!this._attachment)return;const o=await this._dataProcessor.intersect(this._attachment,{mode:"box",min:e,max:n});this._history.execute(new de(this._attachment,t,o))}selectAll(){this._attachment&&this._history.execute(new pe(this._attachment))}selectNone(){this._attachment&&this._history.execute(new he(this._attachment))}selectInvert(){this._attachment&&this._history.execute(new ue(this._attachment))}startSelectionPreview(){this._attachment&&(this._previewBaseState=new Uint8Array(this._attachment.state))}async previewSelectionByMask(t,e){if(!this._attachment)return;const n=this._getViewProjection(),o=await this._dataProcessor.intersect(this._attachment,{mode:"mask",maskTexture:e,viewProjection:n});this._applySelectionDirect(t,o)}commitSelectionPreview(){if(!this._attachment||!this._previewBaseState)return;const t=new Uint8Array(this._attachment.state);this._history.execute(new ve(this._attachment,this._previewBaseState,t,"Brush Select")),this._previewBaseState=null}cancelSelectionPreview(){this._attachment&&this._previewBaseState&&(this._attachment.state.set(this._previewBaseState),this._attachment.updateState(),this._previewBaseState=null,this._notifyStats())}hideSelection(){this._attachment&&this._history.execute(new me(this._attachment))}unhideAll(){this._attachment&&this._history.execute(new ge(this._attachment))}deleteSelection(){this._attachment&&this._history.execute(new fe(this._attachment))}resetDeleted(){this._attachment&&this._history.execute(new ye(this._attachment))}transformSelection(t){this._attachment&&this._history.execute(new xe(this._attachment,t))}undo(){this._history.undo()}redo(){this._history.redo()}get canUndo(){return this._history.canUndo}get canRedo(){return this._history.canRedo}async serializeToPly(t){if(!this._attachment)throw new Error("SplatEditSystem not active");return ne.serialize(this._attachment,t)}getStats(){return this._attachment?.stats??{total:0,selected:0,hidden:0,deleted:0,shBands:0}}async getSelectionBounds(){if(!this._attachment)return null;return(await this._dataProcessor.calcBound(this._attachment)).selectionBound}destroy(){this.deactivate(),this._dataProcessor.destroy()}_applySelectionDirect(t,e){if(!this._attachment)return;const{state:n}=this._attachment,o=this._attachment.numSplats;for(let i=0;i<o;i++)n[i]&jt.deleted||("set"===t?e[i]?n[i]|=jt.selected:n[i]&=~jt.selected:"add"===t?e[i]&&(n[i]|=jt.selected):"remove"===t&&e[i]&&(n[i]&=~jt.selected));this._attachment.updateState(),this._notifyStats()}_getViewProjection(){const e=this._camera.camera;if(!e)throw new Error("Camera entity has no camera component");const n=new t.Mat4;return n.mul2(e.projectionMatrix,e.viewMatrix),n}_notifyStats(){this._attachment&&this.onStatsChange?.(this._attachment.stats)}}const we="Mar 2, 15:07:58";export{we as BUILD_VERSION,R as CameraControls,Tt as CustomScriptSystem,r as DEFAULT_BUTTON_LABELS,qt as EditorCameraController,Pt as FrameSequencePlayer,Ht as GizmoManager,q as GsplatRelighting,tt as REVEAL_PRESETS,Ot as SceneApiError,Vt as SceneNotFoundError,Xt as SelectionManager,oe as SplatEditAttachment,be as SplatEditSystem,ne as SplatSerializer,jt as SplatState,Kt as TransformPalette,B as VoxelCollision,Ft as createViewer,Nt as createViewerFromSceneId,Bt as createViewerFromUrl,a as exportPropsToViewerConfig,Gt as fetchSceneMeta,o as generateHTML,i as generateHTMLFromUrl,c as generateViewerStyles,X as getGsplatRelightingClass,nt as getRevealPreset,kt as setupAnalyticsTracking,At as setupCustomScript,s as transformSceneToExportProps};
1
+ import*as t from"playcanvas";function e(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}function n(t){return t.replace(/<\/script/gi,"<\\/script")}function o(t,o={}){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:d}=o,p=c??t.uiOptions?.lazyLoad??!1,h=d??t.uiOptions?.lazyLoadButtonText,u=r?`<link rel="icon" href="${e(r)}" />`:"",m=l?`<style>${l}</style>`:"",g=n(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 = ${g};\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: ${p},\n lazyLoadButtonText: ${h?`'${n(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 n=await fetch(t);if(!n.ok)throw new Error(`Failed to fetch scene: ${n.statusText}`);return o(await n.json(),e)}function s(t){const e=t.loadedModelUrl||t.splatUrl||"",n=t.sogModelUrl||t.sogUrl,o=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;n?c=n:o?c=o: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:n,compressedPlyUrl:o,selected:c});const d=(t.waypoints||[]).map(t=>{let e=t.fov||60;e<3.5&&(e*=180/Math.PI);let n=t.info||t.description||"";if(!n&&t.interactions){const e=t.interactions.find(t=>"info"===t.type),o=function(t){if(!t||"object"!=typeof t)return;const e=t;return"string"==typeof e.text?e.text:void 0}(e?.data);o&&(n=o)}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:n,interactions:t.interactions||[],triggerDistance:t.triggerDistance??1}}),p=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:n,lodMetaUrl:i,fallbackUrls:[...t.fallbackUrls||[],n!==c?n:null,o!==c?o: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:d,hotspots:t.hotspots||[],portals:t.portals||[],skybox:a,skyboxUrl:a?.url,skyboxRotation:a?.rotation,customMeshes:t.customMeshes||[],htmlMeshes:t.htmlMeshes||[],lights:(t.lights||[]).map(t=>"hemispheric"===t.type?{...t,type:"directional",direction:t.direction??{x:0,y:-1,z:0}}:t),particles:p,collisionMeshesData:t.collisionMeshesData||[],voxelCollisionUrl:t.voxelCollisionUrl,playerHeight:t.playerHeight,walkSpeed:t.walkSpeed,uiColor:t.uiColor||"#ffffff",uiOptions:{showStartExperience:t.uiOptions?.showStartExperience??!1,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,hideProgressText:t.uiOptions?.hideProgressText,viewerTheme:t.uiOptions?.viewerTheme},defaultCameraMode:t.defaultCameraMode||"tour",allowedCameraModes:t.allowedCameraModes||["tour","explore"],cameraMovementSpeed:t.cameraMovementSpeed,cameraRotationSensitivity:t.cameraRotationSensitivity,cameraDamping:t.cameraDamping,invertCameraRotation:t.invertCameraRotation,fov:null!=t.fov?t.fov<3.5?t.fov*(180/Math.PI):t.fov:void 0,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??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||[],doubleTapMoveSpeed:t.doubleTapMoveSpeed,headBobEnabled:t.headBobEnabled,frameSequence:t.frameSequence,splatRelighting:t.splatRelighting,revealEffect:t.revealEffect??(!1===t.useNodeMaterial?"none":void 0),revealStyle:t.revealStyle,swapTransitionType:t.swapTransitionType,lodSettings:t.lodSettings}}function a(t){const e=t.fov||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,voxelCollisionUrl:t.voxelCollisionUrl,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.8,walkSpeed:t.walkSpeed,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||[],doubleTapMoveSpeed:t.doubleTapMoveSpeed,headBobEnabled:t.headBobEnabled,frameSequence:t.frameSequence,splatRelighting:t.splatRelighting,revealEffect:t.revealEffect,revealStyle:t.revealStyle,swapTransitionType:t.swapTransitionType,lodSettings:t.lodSettings}}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",helpTourControls:"Tour Mode:",helpTourScroll:"Scroll - Move along path",helpTourDrag:"Drag - Look around",helpExploreControls:"Explore Mode:",helpExploreLMB:"LMB Drag - Orbit camera",helpExploreRMB:"RMB Drag - Fly/look",helpExploreWASD:"WASD/QE - Move camera",helpExploreShift:"Shift - Move fast",helpExploreScroll:"Scroll/Pinch - Zoom",helpExploreDblClick:"Double-click - Focus",helpWalkControls:"Walk Mode:",helpWalkClick:"Click to lock mouse",helpWalkWASD:"WASD/Arrows - Move",helpWalkMouse:"Mouse - Look around",helpWalkShift:"Shift - Sprint",helpWalkSpace:"Space - Jump",errorWebGLTitle:"Unable to Initialize 3D Graphics",errorWebGLMessage:"Your browser or device may not support WebGL. Please try a different browser or device.",percentageFormat:"{n}%",scenes:"Scenes"};function l(t,e){return t?.[e]||r[e]}function c(t="#CC5833",e="minimal",n){const o=n||{},i=o.globalTextColor||"white";return`\n @keyframes storysplat-morph {\n 0% { border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%; }\n 25% { border-radius: 30% 60% 70% 40% / 50% 60% 30% 60%; }\n 50% { border-radius: 20% 70% 50% 50% / 30% 30% 70% 70%; }\n 75% { border-radius: 70% 30% 40% 60% / 60% 30% 50% 40%; }\n 100% { border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%; }\n }\n\n @keyframes storysplat-spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n }\n\n @keyframes storysplat-float {\n 0%, 100% { transform: translateY(0) scale(1); }\n 50% { transform: translateY(-5px) scale(0.98); }\n }\n\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: ${o.fontFamily||"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: ${o.preloaderBg||"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: 16px;\n }\n\n .storysplat-preloader-image {\n height: 64px;\n width: auto;\n object-fit: contain;\n }\n\n .storysplat-preloader-image-inverted {\n height: 64px;\n width: auto;\n object-fit: contain;\n filter: invert(1);\n margin-right: -32px;\n }\n\n .storysplat-preloader-logo-text {\n font-family: 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif;\n font-weight: 700;\n font-size: 28px;\n color: white;\n white-space: nowrap;\n letter-spacing: -0.02em;\n }\n\n .storysplat-loader-container {\n position: relative;\n width: 64px;\n height: 64px;\n display: flex;\n align-items: center;\n justify-content: center;\n animation: storysplat-float 3s ease-in-out infinite;\n }\n\n .storysplat-blob {\n position: absolute;\n inset: -15%;\n background: #CC5833;\n opacity: 0.85;\n animation: storysplat-morph 4s ease-in-out infinite, storysplat-spin 12s linear infinite;\n box-shadow: inset 10px 0 20px rgba(0,0,0,0.1), 0 5px 15px rgba(204, 88, 51, 0.4);\n filter: blur(1px);\n }\n\n .storysplat-core {\n width: 50%;\n height: 50%;\n background-color: #e5e7eb;\n border-radius: 40% 60% 70% 30% / 40% 50% 60% 50%;\n box-shadow: inset -5px -5px 10px rgba(0,0,0,0.1), 0 2px 8px rgba(0,0,0,0.2);\n position: relative;\n z-index: 2;\n animation: storysplat-morph 5s ease-in-out infinite reverse, storysplat-spin 18s linear infinite reverse;\n }\n\n @media (max-width: 768px) {\n .storysplat-preloader-image,\n .storysplat-preloader-image-inverted {\n height: 40px;\n }\n .storysplat-preloader-image-inverted {\n margin-right: -20px;\n }\n .storysplat-loader-container {\n height: 40px;\n width: 40px;\n }\n .storysplat-preloader-logo-text {\n font-size: 20px;\n }\n }\n\n .storysplat-preloader-progress {\n width: 200px;\n height: ${o.preloaderBarHeight||"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: ${o.preloaderTextColor||"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: ${o.progressFontSize||"14px"};\n color: ${i};\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: ${o.progressBarHeight||"3px"};\n background: ${"white"===i?"rgba(255, 255, 255, 0.2)":"rgba(0, 0, 0, 0.15)"};\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: ${o.buttonBg||"rgba(0, 0, 0, 0.3)"};\n border: none;\n color: ${o.buttonTextColor||"white"};\n padding: 4px 8px;\n font-size: ${o.buttonFontSize||"12px"};\n border-radius: ${o.buttonBorderRadius||"3px"};\n cursor: pointer;\n transition: background 0.2s;\n text-align: center;\n }\n\n .storysplat-btn:hover {\n background: ${o.buttonHoverBg||"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: ${i};\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: ${i};\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: ${i};\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: ${i};\n padding: 3px 6px;\n font-size: ${o.modeBtnFontSize||"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: ${i};\n }\n\n /* Mute Button - Minimal (left side, next to help) */\n .storysplat-mute-btn {\n position: absolute;\n top: 10px;\n left: 45px;\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: ${i};\n }\n\n /* Relighting Toggle Button */\n .storysplat-relight-btn {\n position: absolute;\n top: 10px;\n left: 80px;\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 transition: opacity 0.3s ease;\n }\n\n .storysplat-relight-btn svg {\n width: 20px;\n height: 20px;\n fill: ${i};\n transition: fill 0.3s ease;\n }\n\n .storysplat-relight-btn.active svg {\n fill: #FFD700;\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: ${i};\n border: 1px solid ${"white"===i?"rgba(255, 255, 255, 0.15)":"rgba(0, 0, 0, 0.12)"};\n cursor: pointer;\n font-size: 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1002;\n transition: all 0.2s ease;\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n }\n\n .storysplat-help-btn:hover {\n background: rgba(0, 0, 0, 0.5);\n transform: scale(1.05);\n }\n\n .storysplat-help-btn.active {\n background: ${t};\n border-color: ${t};\n }\n\n .storysplat-help-panel {\n position: absolute;\n top: 50px;\n left: 10px;\n background: ${o.helpPanelBg||"rgba(0, 0, 0, 0.85)"};\n color: ${i};\n padding: 0;\n border-radius: ${o.helpPanelBorderRadius||"8px"};\n max-width: 260px;\n z-index: 1001;\n font-size: 11px;\n border: 1px solid ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.1)"};\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);\n opacity: 0;\n transform: translateY(-8px);\n pointer-events: none;\n transition: opacity 0.2s ease, transform 0.2s ease;\n }\n\n .storysplat-help-panel.visible {\n opacity: 1;\n transform: translateY(0);\n pointer-events: auto;\n }\n\n .storysplat-help-tabs {\n display: flex;\n border-bottom: 1px solid ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.1)"};\n }\n\n .storysplat-help-tab {\n flex: 1;\n padding: 8px 4px;\n background: none;\n border: none;\n color: ${i};\n opacity: 0.5;\n font-size: 10px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.15s ease;\n text-transform: uppercase;\n text-align: center;\n letter-spacing: 0.5px;\n border-bottom: 2px solid transparent;\n }\n\n .storysplat-help-tab:hover {\n opacity: 0.8;\n }\n\n .storysplat-help-tab.active {\n color: ${t};\n opacity: 1;\n border-bottom-color: ${t};\n }\n\n .storysplat-help-tab-content {\n display: none;\n padding: 10px 12px;\n }\n\n .storysplat-help-tab-content.active {\n display: block;\n }\n\n .storysplat-help-row {\n display: flex;\n align-items: center;\n padding: 3px 0;\n gap: 8px;\n }\n\n .storysplat-help-key {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 22px;\n height: 20px;\n padding: 0 5px;\n background: ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.08)"};\n border: 1px solid ${"white"===i?"rgba(255, 255, 255, 0.15)":"rgba(0, 0, 0, 0.12)"};\n border-radius: 3px;\n font-size: 9px;\n font-weight: 600;\n font-family: system-ui, -apple-system, sans-serif;\n white-space: nowrap;\n flex-shrink: 0;\n }\n\n .storysplat-help-desc {\n color: ${i};\n opacity: 0.7;\n font-size: 11px;\n }\n\n /* XR (VR/AR) Buttons - Minimal, positioned to the right of the ? help button */\n .storysplat-xr-btn {\n position: absolute;\n bottom: 10px;\n left: 10px;\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: ${i};\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 left: 10px;\n }\n\n .storysplat-help-panel h3 {\n margin: 0;\n padding: 10px 12px;\n font-size: 12px;\n font-weight: 600;\n text-align: center;\n border-bottom: 1px solid ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.1)"};\n }\n\n .storysplat-help-panel p {\n margin: 3px 0;\n line-height: 1.3;\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: ${o.infoBannerBg||"rgba(0, 0, 0, 0.5)"};\n color: ${i};\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: ${o.infoBannerTitleFontSize||"18px"};\n font-weight: bold;\n }\n\n .storysplat-waypoint-info p {\n margin: 0;\n font-size: ${o.infoBannerContentFontSize||"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: ${o.popupBg||"rgba(0, 0, 0, 0.75)"};\n color: ${o.popupTextColor||"white"};\n padding: 20px;\n border-radius: ${o.popupBorderRadius||"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: ${o.fontFamily||"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 /* Fits content and stays centered rather than filling the whole screen */\n .storysplat-hotspot-popup.fullscreen {\n top: 0 !important;\n left: 0 !important;\n right: 0 !important;\n bottom: 0 !important;\n transform: none !important;\n width: min(calc(100% - 40px), 840px) !important;\n height: fit-content !important;\n max-height: calc(100% - 40px) !important;\n margin: auto !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: 20px !important;\n overflow: auto !important;\n box-sizing: border-box !important;\n }\n\n .storysplat-hotspot-popup h2,\n .storysplat-hotspot-popup-title {\n margin: 0 0 10px 0;\n font-size: ${o.popupTitleFontSize||"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: ${o.popupContentFontSize||"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: 0 0 10px 0;\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 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: 10px auto 0 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: 100% !important;\n max-height: 70vh !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: 100% !important;\n max-width: 100% !important;\n height: auto !important;\n aspect-ratio: 16 / 9 !important;\n max-height: 70vh !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: ${o.portalPopupBg||"rgba(0, 0, 0, 0.85)"};\n color: ${i};\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: ${i};\n }\n\n /* Model container for 3D model lightbox */\n .storysplat-hotspot-popup .model-container {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n aspect-ratio: 1 / 1;\n max-height: 70vh;\n margin: 10px 0;\n position: relative;\n border-radius: 8px;\n overflow: hidden;\n background: rgba(0, 0, 0, 0.3);\n }\n .storysplat-hotspot-popup .model-container canvas {\n width: 100% !important;\n height: 100% !important;\n display: block;\n border-radius: 8px;\n }\n .storysplat-hotspot-popup .model-container .model-loading {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: rgba(255, 255, 255, 0.7);\n font-size: 14px;\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: 100% !important;\n max-width: 100% !important;\n max-height: 70vh !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: 70vh !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: ${o.popupCloseBtnColor||t};\n border: none;\n color: white;\n cursor: pointer;\n border-radius: ${o.buttonBorderRadius||"5px"};\n margin: 5px 0 0 0;\n font-size: 14px;\n font-weight: 500;\n font-family: ${o.fontFamily||"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 opacity: 0.85;\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: ${o.errorPopupBg||"rgba(0, 0, 0, 0.95)"};\n border-radius: ${o.errorPopupBorderRadius||"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: ${o.errorTitleColor||"#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, #CC5833, #b54d2d);\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: ${o.popupLinkBtnColor||"#007bff"};\n color: white;\n text-decoration: none;\n border-radius: ${o.buttonBorderRadius||"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 {\n max-width: calc(100% - 40px);\n }\n\n .storysplat-hotspot-popup.fullscreen {\n width: min(calc(100% - 20px), 840px) !important;\n max-height: calc(100% - 20px) !important;\n padding: 15px !important;\n }\n\n .storysplat-hotspot-popup-title {\n font-size: 16px !important;\n }\n }\n\n @media (max-width: 540px) {\n .storysplat-scroll-controls {\n margin-bottom: 45px;\n }\n\n .storysplat-hotspot-popup {\n max-width: calc(100% - 20px);\n }\n\n .storysplat-hotspot-popup.fullscreen {\n width: min(calc(100% - 10px), 840px) !important;\n max-height: calc(100% - 10px) !important;\n padding: 10px !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: 130px;\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: ${o.joystickBaseColor||"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: ${o.joystickThumbColor||"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: 130px;\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 /* WASD Key Hint (desktop fly mode) */\n @keyframes storysplat-hint-fadein {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n }\n\n .storysplat-wasd-hint {\n position: absolute;\n bottom: 20px;\n left: 20px;\n z-index: 2000;\n pointer-events: none;\n display: none;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n animation: storysplat-hint-fadein 0.4s ease-out;\n }\n\n .storysplat-wasd-hint.visible {\n display: flex;\n pointer-events: auto;\n }\n\n .storysplat-wasd-hint-row {\n display: flex;\n gap: 4px;\n justify-content: center;\n }\n\n .storysplat-wasd-key {\n width: 36px;\n height: 36px;\n background: rgba(255, 255, 255, 0.1);\n border: 1.5px solid rgba(255, 255, 255, 0.25);\n border-radius: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n font-size: 13px;\n font-weight: 600;\n color: rgba(255, 255, 255, 0.5);\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n cursor: pointer;\n user-select: none;\n -webkit-user-select: none;\n transition: background 0.1s ease, border-color 0.1s ease, color 0.1s ease, box-shadow 0.1s ease;\n }\n\n .storysplat-wasd-key:hover {\n background: rgba(255, 255, 255, 0.15);\n border-color: rgba(255, 255, 255, 0.35);\n color: rgba(255, 255, 255, 0.7);\n }\n\n .storysplat-wasd-key.active {\n background: rgba(255, 255, 255, 0.3);\n border-color: rgba(255, 255, 255, 0.6);\n color: rgba(255, 255, 255, 0.95);\n box-shadow: 0 0 8px rgba(255, 255, 255, 0.15);\n }\n\n .storysplat-wasd-hint-label {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n font-size: 10px;\n color: rgba(255, 255, 255, 0.35);\n margin-top: 2px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n\n /* Orbit hint (explore orbit mode — drag to rotate / double-click to refocus) */\n .storysplat-orbit-hint {\n position: absolute;\n bottom: 20px;\n left: 20px;\n z-index: 2000;\n pointer-events: none;\n display: none;\n flex-direction: column;\n align-items: flex-start;\n gap: 8px;\n animation: storysplat-hint-fadein 0.4s ease-out;\n }\n\n .storysplat-orbit-hint.visible {\n display: flex;\n }\n\n .storysplat-orbit-hint-row {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .storysplat-orbit-hint-icon {\n width: 28px;\n height: 28px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(255, 255, 255, 0.1);\n border: 1.5px solid rgba(255, 255, 255, 0.25);\n border-radius: 6px;\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n flex-shrink: 0;\n }\n\n .storysplat-orbit-hint-icon svg {\n width: 16px;\n height: 16px;\n fill: rgba(255, 255, 255, 0.5);\n }\n\n .storysplat-orbit-hint-label {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n font-size: 11px;\n color: rgba(255, 255, 255, 0.45);\n white-space: nowrap;\n letter-spacing: 0.3px;\n }\n\n /* Double-tap hint (explore mode) */\n .storysplat-doubletap-hint {\n position: absolute;\n bottom: 90px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 2000;\n pointer-events: none;\n display: none;\n align-items: center;\n gap: 8px;\n padding: 8px 16px;\n background: rgba(0, 0, 0, 0.5);\n border: 1px solid rgba(255, 255, 255, 0.15);\n border-radius: 20px;\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n animation: storysplat-hint-fadein 0.4s ease-out;\n transition: opacity 0.4s ease-out;\n }\n\n .storysplat-doubletap-hint.visible {\n display: flex;\n }\n\n .storysplat-doubletap-hint.fading {\n opacity: 0;\n }\n\n .storysplat-doubletap-hint-icon {\n width: 20px;\n height: 20px;\n flex-shrink: 0;\n }\n\n .storysplat-doubletap-hint-icon svg {\n width: 20px;\n height: 20px;\n fill: rgba(255, 255, 255, 0.5);\n }\n\n .storysplat-doubletap-hint-text {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n font-size: 12px;\n color: rgba(255, 255, 255, 0.6);\n white-space: nowrap;\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: ${o.lazyLoadBg||"#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: ${o.lazyLoadBtnBorderRadius||"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: ${o.buttonBg||"rgba(0, 0, 0, 0.3)"};\n border: none;\n border-radius: 8px;\n color: ${o.buttonTextColor||"white"};\n cursor: pointer;\n font-size: 14px;\n backdrop-filter: blur(${o.dropdownBlur||"10px"});\n -webkit-backdrop-filter: blur(${o.dropdownBlur||"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: ${o.buttonHoverBg||"rgba(0, 0, 0, 0.5)"};\n }\n\n .storysplat-waypoint-list-toggle svg {\n width: 16px;\n height: 16px;\n fill: ${i};\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: ${o.dropdownBg||"rgba(0, 0, 0, 0.8)"};\n border-radius: ${o.dropdownBorderRadius||"8px"};\n backdrop-filter: blur(${o.dropdownBlur||"10px"});\n -webkit-backdrop-filter: blur(${o.dropdownBlur||"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: ${i};\n cursor: pointer;\n border-bottom: 1px solid ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.08)"};\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: ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.05)"};\n }\n\n .storysplat-waypoint-item.active {\n background: ${t}40;\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: ${o.watermarkBg||"rgba(0, 0, 0, 0.5)"};\n color: ${i};\n padding: 5px 10px;\n border-radius: 5px;\n font-size: ${o.watermarkFontSize||"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 bottom: 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\n /* Scene Navigation Menu */\n .storysplat-scene-menu-container {\n position: absolute;\n top: 10px;\n left: 10px;\n z-index: 1001;\n font-family: ${o.fontFamily||"inherit"};\n }\n\n .storysplat-scene-menu-toggle {\n display: flex;\n align-items: center;\n gap: 6px;\n background: ${o.buttonBg||"rgba(0, 0, 0, 0.5)"};\n backdrop-filter: blur(${o.dropdownBlur||"10px"});\n -webkit-backdrop-filter: blur(${o.dropdownBlur||"10px"});\n color: ${o.buttonTextColor||i};\n border: 1px solid rgba(255, 255, 255, 0.1);\n border-radius: ${o.buttonBorderRadius||"8px"};\n padding: 8px 12px;\n cursor: pointer;\n font-size: ${o.buttonFontSize||"13px"};\n font-family: inherit;\n transition: background 0.2s, border-color 0.2s;\n }\n\n .storysplat-scene-menu-toggle:hover {\n background: ${o.buttonHoverBg||"rgba(0, 0, 0, 0.7)"};\n border-color: rgba(255, 255, 255, 0.2);\n }\n\n .storysplat-scene-menu-toggle svg {\n width: 16px;\n height: 16px;\n fill: currentColor;\n flex-shrink: 0;\n }\n\n .storysplat-scene-menu-toggle.open svg {\n transform: rotate(90deg);\n }\n\n .storysplat-scene-menu-label {\n white-space: nowrap;\n }\n\n .storysplat-scene-menu-dropdown {\n display: none;\n position: absolute;\n top: calc(100% + 6px);\n left: 0;\n min-width: 220px;\n max-width: 320px;\n max-height: 60vh;\n overflow-y: auto;\n background: ${o.dropdownBg||"rgba(0, 0, 0, 0.75)"};\n backdrop-filter: blur(${o.dropdownBlur||"12px"});\n -webkit-backdrop-filter: blur(${o.dropdownBlur||"12px"});\n border: 1px solid rgba(255, 255, 255, 0.1);\n border-radius: ${o.dropdownBorderRadius||"10px"};\n padding: 6px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);\n }\n\n .storysplat-scene-menu-dropdown.open {\n display: block;\n }\n\n .storysplat-scene-menu-dropdown::-webkit-scrollbar {\n width: 4px;\n }\n\n .storysplat-scene-menu-dropdown::-webkit-scrollbar-thumb {\n background: rgba(255, 255, 255, 0.2);\n border-radius: 2px;\n }\n\n .storysplat-scene-menu-folder {\n margin-bottom: 2px;\n }\n\n .storysplat-scene-menu-folder-header {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 8px;\n color: ${i};\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n opacity: 0.7;\n cursor: pointer;\n border-radius: 6px;\n transition: opacity 0.15s;\n user-select: none;\n }\n\n .storysplat-scene-menu-folder-header:hover {\n opacity: 1;\n }\n\n .storysplat-scene-menu-folder-header svg {\n width: 10px;\n height: 10px;\n fill: currentColor;\n transition: transform 0.15s;\n flex-shrink: 0;\n }\n\n .storysplat-scene-menu-folder-header.collapsed svg {\n transform: rotate(-90deg);\n }\n\n .storysplat-scene-menu-folder-children {\n padding-left: 12px;\n }\n\n .storysplat-scene-menu-folder-children.collapsed {\n display: none;\n }\n\n .storysplat-scene-menu-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 10px;\n color: ${i};\n font-size: ${o.buttonFontSize||"13px"};\n cursor: pointer;\n border-radius: 6px;\n transition: background 0.15s;\n text-decoration: none;\n }\n\n .storysplat-scene-menu-item:hover {\n background: rgba(255, 255, 255, 0.1);\n }\n\n .storysplat-scene-menu-item-thumb {\n width: 32px;\n height: 32px;\n border-radius: 4px;\n object-fit: cover;\n flex-shrink: 0;\n background: rgba(255, 255, 255, 0.05);\n }\n\n .storysplat-scene-menu-item-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n @media (max-width: 768px) {\n .storysplat-scene-menu-label {\n display: none;\n }\n\n .storysplat-scene-menu-toggle {\n padding: 8px 10px;\n }\n\n .storysplat-scene-menu-dropdown {\n min-width: 200px;\n max-width: 80vw;\n }\n }\n `+function(t,e){if("standard"===e)return`\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 `;if("pro"===e)return"\n /* ===== PRO (Dark) Template Overrides ===== */\n\n /* Top-right bar: holds mode toggle + waypoint list */\n .storysplat-top-right-bar {\n position: absolute;\n top: 10px;\n right: 10px;\n display: flex;\n align-items: center;\n gap: 8px;\n z-index: 1000;\n }\n\n .storysplat-top-right-bar.with-fullscreen {\n right: 45px;\n }\n\n /* Override absolute positioning when inside top-right-bar */\n .storysplat-top-right-bar .storysplat-waypoint-list-container,\n .storysplat-top-right-bar .storysplat-waypoint-list-container.with-fullscreen,\n .storysplat-top-right-bar .storysplat-waypoint-list-container.no-fullscreen {\n position: relative;\n top: auto;\n right: auto;\n }\n\n .storysplat-scroll-controls {\n width: auto;\n padding: 8px 12px;\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-text {\n display: none;\n }\n\n .storysplat-progress-container {\n width: 100%;\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-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 in top-right bar, horizontal layout */\n .storysplat-mode-container {\n position: relative;\n bottom: auto;\n left: auto;\n margin-top: 0;\n }\n\n .storysplat-mode-toggle {\n flex-direction: row;\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.3);\n border: none;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n }\n\n .storysplat-mode-btn:hover {\n background: rgba(0, 0, 0, 0.5);\n }\n\n .storysplat-explore-controls {\n flex-direction: row;\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 padding: 6px 10px;\n bottom: 10px;\n }\n\n .storysplat-top-right-bar {\n right: 10px;\n gap: 6px;\n }\n\n .storysplat-top-right-bar.with-fullscreen {\n right: 40px;\n }\n\n .storysplat-btn {\n padding: 6px 12px;\n font-size: 12px;\n }\n }\n\n @media (max-width: 540px) {\n .storysplat-wasd-hint {\n transform: translateY(-50%);\n }\n .storysplat-orbit-hint {\n transform: translateY(-50%);\n }\n }\n ";return""}(t,e)+function(t){if(!t)return"";const e=t.elements,n=t.mobile;if(!e&&!n)return"";let o="\n/* ===== Custom UI Layout Overrides ===== */\n";const i=t=>{let e="";for(const[n,o]of Object.entries(t)){if(!o)continue;const t=d[n];if(!t)continue;const i=h(o,n);e+=` ${t} { ${p.has(n)?"position: absolute !important; z-index: 1001 !important; ":""}${i}; }\n`,"waypointDropdown"===n&&(e+=` ${t}.with-fullscreen { ${i}; }\n`,e+=` ${t}.no-fullscreen { ${i}; }\n`)}return e};e&&Object.keys(e).length>0&&(o+="@media (min-width: 769px) {\n",o+=i(e),o+="}\n");n&&Object.keys(n).length>0&&(o+="@media (max-width: 768px) {\n",o+=i(n),o+="}\n");return o}(o?.uiLayout)}const d={helpButton:".storysplat-help-btn",muteButton:".storysplat-mute-btn",fullscreenButton:".storysplat-fullscreen-btn",watermark:".storysplat-watermark",waypointDropdown:".storysplat-waypoint-list-container",scrollControls:".storysplat-scroll-controls",waypointBanner:".storysplat-waypoint-info",progressText:".storysplat-progress-text",modeToggle:".storysplat-mode-container",exploreControls:".storysplat-explore-controls",prevButton:".storysplat-btn-prev",playButton:".storysplat-btn-play",nextButton:".storysplat-btn-next",hotspotPopup:".storysplat-hotspot-popup",portalPopup:".storysplat-portal-popup",relightingButton:".storysplat-relight-btn",sceneMenu:".storysplat-scene-menu-container"},p=new Set(["progressText","modeToggle","exploreControls","prevButton","playButton","nextButton"]);function h(t,e){const{anchor:n,offsetX:o,offsetY:i}=t,[s,a]=n.split("-"),r="center"!==s||a?s:"center",l=a||("center"===s?"center":"left"),c=["top: auto !important","right: auto !important","bottom: auto !important","left: auto !important"];"top"===r?c.push(`top: ${i}px !important`):"bottom"===r?c.push(`bottom: ${i}px !important`):c.push("top: 50% !important"),"waypointBanner"===e?"center"===l?c.push("left: 0 !important","right: 0 !important"):"left"===l?(c.push(`left: ${o}px !important`),c.push("right: auto !important")):(c.push(`right: ${o}px !important`),c.push("left: auto !important")):"left"===l?c.push(`left: ${o}px !important`):"right"===l?c.push(`right: ${o}px !important`):c.push("left: 50% !important");const d="center"===l&&"waypointBanner"!==e,p="center"===r;return"scrollControls"===e?d&&p?c.push("transform: translate(-50%, -50%) !important"):d?c.push("transform: translateX(-50%) !important"):p?c.push("transform: translateY(-50%) !important"):c.push("transform: none !important"):d&&p?c.push("transform: translate(-50%, -50%) !important"):d?c.push("transform: translateX(-50%) !important"):p&&c.push("transform: translateY(-50%) !important"),null!=t.width&&t.width>0&&c.push(`width: ${t.width}px !important`),null!=t.height&&t.height>0&&c.push(`height: ${t.height}px !important`),c.join("; ")}function u(t="#CC5833",e="minimal",n){const o=document.getElementById("storysplat-viewer-styles");o&&o.remove();const i=document.createElement("style");return i.id="storysplat-viewer-styles",i.textContent=c(t,e,n),document.head.appendChild(i),i}function m(t,e,n){const o=document.createElement("div");o.className="storysplat-preloader";const i=!!e,s=document.createElement("div");s.className="storysplat-preloader-content";const a=document.createElement("div");if(a.className="storysplat-preloader-media",i){const t=document.createElement("img");t.className="storysplat-preloader-image",t.src=e,t.alt="Custom Logo",a.appendChild(t)}else{const t=document.createElement("span");t.className="storysplat-preloader-logo-text",t.textContent="StorySplat",a.appendChild(t);const e=document.createElement("div");e.className="storysplat-loader-container";const n=document.createElement("div");n.className="storysplat-blob";const o=document.createElement("div");o.className="storysplat-core",e.appendChild(n),e.appendChild(o),a.appendChild(e)}s.appendChild(a);const r=document.createElement("div");r.className="storysplat-preloader-progress";const c=document.createElement("div");c.className="storysplat-preloader-text",c.textContent=`${l(n,"loading")} 0%`;const d=document.createElement("div");return d.className="storysplat-preloader-bar",r.appendChild(c),r.appendChild(d),s.appendChild(r),o.appendChild(s),t.appendChild(o),i||function(){if(document.querySelector('link[href*="Plus+Jakarta+Sans"]'))return;const t=document.createElement("link");t.rel="stylesheet",t.href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@700&display=swap",document.head.appendChild(t)}(),o}function g(t){t.classList.add("hidden"),setTimeout(()=>t.remove(),500)}function f(t,e,n={}){const{uiColor:o="#4CAF50",showScrollControls:i=!0,showModeToggle:s=!0,showFullscreenButton:a=!0,showHelpButton:r=!1,showMuteButton:c=!1,showPreloader:d=!0,allowedCameraModes:p=["tour","explore"],defaultCameraMode:h="tour",customPreloaderLogoUrl:g,buttonLabels:f,hideWatermark:y=!1,watermarkText:v,watermarkLink:x,sceneId:b,showWaypointList:w=!0,template:S="minimal",hideProgressText:_=!1,viewerTheme:M,showRelightingToggle:E=!1,showSceneMenu:C}=n,T={tour:l(f,"tour"),explore:l(f,"explore"),walk:l(f,"walk"),orbit:l(f,"orbit"),fly:l(f,"fly"),previous:l(f,"previous"),next:l(f,"next"),fullscreen:l(f,"fullscreen"),waypoints:l(f,"waypoints"),close:l(f,"close"),yes:l(f,"yes"),cancel:l(f,"cancel"),vr:l(f,"vr"),ar:l(f,"ar"),loading:l(f,"loading"),helpTitle:l(f,"helpTitle"),helpCameraModes:l(f,"helpCameraModes"),helpTourDesc:l(f,"helpTourDesc"),helpExploreDesc:l(f,"helpExploreDesc"),helpWalkDesc:l(f,"helpWalkDesc"),helpTourControls:l(f,"helpTourControls"),helpTourScroll:l(f,"helpTourScroll"),helpTourDrag:l(f,"helpTourDrag"),helpExploreControls:l(f,"helpExploreControls"),helpExploreLMB:l(f,"helpExploreLMB"),helpExploreRMB:l(f,"helpExploreRMB"),helpExploreWASD:l(f,"helpExploreWASD"),helpExploreShift:l(f,"helpExploreShift"),helpExploreScroll:l(f,"helpExploreScroll"),helpExploreDblClick:l(f,"helpExploreDblClick"),helpWalkControls:l(f,"helpWalkControls"),helpWalkClick:l(f,"helpWalkClick"),helpWalkWASD:l(f,"helpWalkWASD"),helpWalkMouse:l(f,"helpWalkMouse"),helpWalkShift:l(f,"helpWalkShift"),helpWalkSpace:l(f,"helpWalkSpace"),hotspotDefaultTitle:l(f,"hotspotDefaultTitle"),openExternalLink:l(f,"openExternalLink"),mute:l(f,"mute"),unmute:l(f,"unmute"),scenes:l(f,"scenes")},A={};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-scene-menu-container, .storysplat-hotspot-popup, .storysplat-portal-popup, .storysplat-wasd-hint, .storysplat-orbit-hint, .storysplat-doubletap-hint, .storysplat-look-zone, .storysplat-joystick-container, .storysplat-relight-btn, .storysplat-fps-counter, .storysplat-xr-btn, .storysplat-mode-container, .storysplat-explore-controls, .storysplat-top-right-bar").forEach(t=>t.remove()),u(o,S,M),t.classList.add("storysplat-viewer-container"),d&&(A.preloader=m(t,g,f));const P=document.createElement("div");if(P.className="storysplat-waypoint-info",P.innerHTML='\n <h2 class="storysplat-waypoint-title"></h2>\n <p class="storysplat-waypoint-description"></p>\n ',t.appendChild(P),A.waypointInfo=P,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">${T.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">${T.next}</button>\n </div>\n <div class="storysplat-explore-controls">\n ${p.includes("explore")?`<button class="storysplat-explore-btn" data-explore-mode="orbit">${T.orbit}</button>\n <button class="storysplat-explore-btn" data-explore-mode="fly">${T.fly}</button>`:""}\n ${p.includes("walk")?`<button class="storysplat-explore-btn" data-explore-mode="walk">${T.walk}</button>`:""}\n </div>\n ${s&&p.includes("tour")&&(p.includes("explore")||p.includes("walk"))?`\n <div class="storysplat-mode-container">\n <div class="storysplat-mode-toggle">\n ${p.includes("tour")?`<button class="storysplat-mode-btn ${"tour"===h?"selected":""}" data-mode="tour">${T.tour}</button>`:""}\n ${p.includes("explore")&&p.includes("walk")?`<button class="storysplat-mode-btn ${"explore"===h||"walk"===h?"selected":""}" data-mode="explore">${T.explore}</button>`:""}\n ${p.includes("explore")&&!p.includes("walk")?`<button class="storysplat-mode-btn ${"explore"===h?"selected":""}" data-mode="explore">${T.explore}</button>`:""}\n ${!p.includes("explore")&&p.includes("walk")?`<button class="storysplat-mode-btn ${"walk"===h?"selected":""}" data-mode="walk">${T.walk}</button>`:""}\n </div>\n </div>\n `:""}\n </div>\n `,t.appendChild(e),A.scrollControls=e,A.progressBar=e.querySelector(".storysplat-progress-bar"),A.progressText=e.querySelector(".storysplat-progress-text"),A.exploreControls=e.querySelector(".storysplat-explore-controls"),A.modeContainer=e.querySelector(".storysplat-mode-container"),A.prevButton=e.querySelector(".storysplat-btn-prev"),A.playButton=e.querySelector(".storysplat-btn-play"),A.nextButton=e.querySelector(".storysplat-btn-next"),"pro"===S){const e=document.createElement("div");e.className="storysplat-top-right-bar "+(a?"with-fullscreen":""),t.appendChild(e),A.modeContainer&&e.appendChild(A.modeContainer)}const n=M?.uiLayout,o=t=>n?.elements?.[t]||n?.mobile?.[t];o("progressText")&&A.progressText&&t.appendChild(A.progressText),o("exploreControls")&&A.exploreControls&&t.appendChild(A.exploreControls),o("prevButton")&&A.prevButton&&t.appendChild(A.prevButton),o("playButton")&&A.playButton&&t.appendChild(A.playButton),o("nextButton")&&A.nextButton&&t.appendChild(A.nextButton),"pro"!==S&&o("modeToggle")&&A.modeContainer&&t.appendChild(A.modeContainer),_&&A.progressText&&(A.progressText.style.display="none")}if(a){const e=document.createElement("button");e.className="storysplat-fullscreen-btn",e.setAttribute("aria-label",T.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),A.fullscreenButton=e}if(c){const e=document.createElement("button");e.className="storysplat-mute-btn",e.setAttribute("aria-label",T.mute);const n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.classList.add("storysplat-unmuted-icon"),n.setAttribute("viewBox","0 0 24 24");const o=document.createElementNS("http://www.w3.org/2000/svg","path");o.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"),n.appendChild(o),e.appendChild(n);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),A.muteButton=e}if(E){const e=document.createElement("button");e.className="storysplat-relight-btn active",e.setAttribute("aria-label","Toggle Relighting");const n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("viewBox","0 0 24 24");const o=document.createElementNS("http://www.w3.org/2000/svg","path");o.setAttribute("d","M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7z"),n.appendChild(o),e.appendChild(n),t.appendChild(e),A.relightingButton=e}if(w&&e.waypoints&&e.waypoints.length>0){const n=document.createElement("div");n.className="storysplat-waypoint-list-container "+(a?"with-fullscreen":"no-fullscreen");const o=e.waypoints.map((t,e)=>`<div class="storysplat-waypoint-item" data-waypoint-index="${e}">${t.name||`Waypoint ${e+1}`}</div>`).join("");n.innerHTML=`\n <button class="storysplat-waypoint-list-toggle" aria-label="${T.waypoints}">\n ${T.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 ${o}\n </div>\n `;const i=t.querySelector(".storysplat-top-right-bar");i?i.appendChild(n):t.appendChild(n),A.waypointListContainer=n;const s=n.querySelector(".storysplat-waypoint-list-toggle"),r=n.querySelector(".storysplat-waypoint-list-dropdown");s?.addEventListener("click",t=>{t.stopPropagation(),s.classList.toggle("open"),r?.classList.toggle("open")}),document.addEventListener("click",t=>{n.contains(t.target)||(s?.classList.remove("open"),r?.classList.remove("open"))})}if(e.portals&&e.portals.length>0&&!1!==C){const n=document.createElement("div");n.className="storysplat-scene-menu-container";let o=10;r&&(o+=35),c&&(o+=35),E&&(o+=35),n.style.left=`${o}px`;const i=T.scenes,s=document.createElement("button");s.className="storysplat-scene-menu-toggle",s.setAttribute("aria-label",i);const a=document.createElementNS("http://www.w3.org/2000/svg","svg");a.setAttribute("viewBox","0 0 24 24");const l=document.createElementNS("http://www.w3.org/2000/svg","path");l.setAttribute("d","M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"),a.appendChild(l),s.appendChild(a);const d=document.createElement("span");d.className="storysplat-scene-menu-label",d.textContent=i,s.appendChild(d);const p=document.createElement("div");p.className="storysplat-scene-menu-dropdown",function(t,e){const n={children:new Map,items:[]};for(const t of e){const e=t.menuPath?.trim();if(!e){n.items.push(t);continue}const o=e.split("/").map(t=>t.trim()).filter(Boolean);let i=n;for(const t of o)i.children.has(t)||i.children.set(t,{name:t,children:new Map,items:[]}),i=i.children.get(t);i.items.push(t)}function o(t){const e=document.createElement("div");if(e.className="storysplat-scene-menu-item",e.setAttribute("data-portal-id",t.id),e.setAttribute("data-target-scene-id",t.targetSceneId),t.targetSceneThumbnail){const n=document.createElement("img");n.className="storysplat-scene-menu-item-thumb",n.src=t.targetSceneThumbnail,n.alt="",n.loading="lazy",e.appendChild(n)}const n=document.createElement("span");return n.className="storysplat-scene-menu-item-label",n.textContent=t.title||t.targetSceneName||"Untitled",e.appendChild(n),e}function i(t,e){for(const[,n]of e.children){const e=document.createElement("div");e.className="storysplat-scene-menu-folder";const o=document.createElement("div");o.className="storysplat-scene-menu-folder-header";const s=document.createElementNS("http://www.w3.org/2000/svg","svg");s.setAttribute("viewBox","0 0 24 24");const a=document.createElementNS("http://www.w3.org/2000/svg","path");a.setAttribute("d","M7 10l5 5 5-5z"),s.appendChild(a),o.appendChild(s),o.appendChild(document.createTextNode(n.name)),e.appendChild(o);const r=document.createElement("div");r.className="storysplat-scene-menu-folder-children",i(r,n),e.appendChild(r),t.appendChild(e)}for(const n of e.items)t.appendChild(o(n))}i(t,n)}(p,e.portals),n.appendChild(s),n.appendChild(p),t.appendChild(n),A.sceneMenuContainer=n,s.addEventListener("click",t=>{t.stopPropagation(),s.classList.toggle("open"),p.classList.toggle("open")}),p.querySelectorAll(".storysplat-scene-menu-folder-header").forEach(t=>{t.addEventListener("click",e=>{e.stopPropagation(),t.classList.toggle("collapsed");const n=t.nextElementSibling;n&&n.classList.toggle("collapsed")})}),document.addEventListener("click",t=>{n.contains(t.target)||(s.classList.remove("open"),p.classList.remove("open"))})}const L=document.createElement("button");L.className="storysplat-xr-btn storysplat-vr-btn",L.setAttribute("aria-label",T.vr),L.textContent=T.vr,t.appendChild(L),A.vrButton=L;const k=document.createElement("button");if(k.className="storysplat-xr-btn storysplat-ar-btn",k.setAttribute("aria-label",T.ar),k.textContent=T.ar,t.appendChild(k),A.arButton=k,r){const e=document.createElement("button");e.className="storysplat-help-btn",e.setAttribute("title",T.helpTitle),e.textContent="?",t.appendChild(e),A.helpButton=e;const n=document.createElement("div");n.className="storysplat-help-panel";const o=document.createElement("h3");o.textContent=T.helpTitle,n.appendChild(o);const i=document.createElement("div");i.className="storysplat-help-tabs";[{key:"tour",label:T.tour},{key:"explore",label:T.explore}].forEach((t,e)=>{const n=document.createElement("button");n.className="storysplat-help-tab"+(0===e?" active":""),n.setAttribute("data-tab",t.key),n.textContent=t.label,i.appendChild(n)}),n.appendChild(i);const s=(t,e)=>{const n=document.createElement("div");n.className="storysplat-help-row";const o=document.createElement("span");o.className="storysplat-help-key",o.textContent=t;const i=document.createElement("span");return i.className="storysplat-help-desc",i.textContent=e.replace(/^[•\s]+/,""),n.appendChild(o),n.appendChild(i),n},a=(t,e,n,o)=>{const i=document.createElement("div");i.className="storysplat-help-tab-content"+(o?" active":""),i.setAttribute("data-tab",t);const a=document.createElement("p");return a.style.cssText="margin-bottom:6px;color:rgba(255,255,255,0.5)",a.textContent=e,i.appendChild(a),n.forEach(([t,e])=>i.appendChild(s(t,e))),i};n.appendChild(a("tour",T.helpTourDesc,[["Scroll",T.helpTourScroll],["Drag",T.helpTourDrag]],!0));const r=[["LMB",T.helpExploreLMB],["RMB",T.helpExploreRMB],["WASD",T.helpExploreWASD],["Shift",T.helpExploreShift],["Scroll",T.helpExploreScroll],["Dbl-click",T.helpExploreDblClick]];p.includes("walk")&&r.push(["",`— ${T.walk} —`],["Click",T.helpWalkClick],["WASD",T.helpWalkWASD],["Mouse",T.helpWalkMouse],["Shift",T.helpWalkShift],["Space",T.helpWalkSpace]),n.appendChild(a("explore",T.helpExploreDesc,r,!1)),t.appendChild(n),A.helpPanel=n;const l=n.querySelectorAll(".storysplat-help-tab"),c=n.querySelectorAll(".storysplat-help-tab-content");l.forEach(t=>{t.addEventListener("click",t=>{const e=t.currentTarget.getAttribute("data-tab");l.forEach(t=>t.classList.remove("active")),c.forEach(t=>t.classList.remove("active")),t.currentTarget.classList.add("active"),n.querySelector(`.storysplat-help-tab-content[data-tab="${e}"]`)?.classList.add("active")})})}const z=document.createElement("div");z.className="storysplat-hotspot-popup",z.id="hotspotContent",z.innerHTML=`\n <h2 class="storysplat-hotspot-popup-title"></h2>\n <div class="storysplat-hotspot-popup-content"></div>\n <button class="storysplat-hotspot-popup-close">${T.close}</button>\n `,t.appendChild(z),A.hotspotPopup=z;const R=z.querySelector(".storysplat-hotspot-popup-close");R?.addEventListener("click",()=>{z.classList.remove("visible","fullscreen")});const D=document.createElement("div");D.className="storysplat-portal-popup",D.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">${T.yes}</button>\n <button class="storysplat-portal-popup-btn storysplat-portal-popup-cancel">${T.cancel}</button>\n </div>\n `,t.appendChild(D),A.portalPopup=D;const I=document.createElement("div");I.className="storysplat-joystick-container",I.innerHTML='\n <div class="storysplat-joystick-base"></div>\n <div class="storysplat-joystick-thumb"></div>\n ',t.appendChild(I),A.joystick=I,A.joystickThumb=I.querySelector(".storysplat-joystick-thumb");const F=document.createElement("div");F.className="storysplat-look-zone",F.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(F),A.lookZone=F;const B=document.createElement("div");B.className="storysplat-wasd-hint";const U=document.createElement("div");U.className="storysplat-orbit-hint-row",U.style.marginBottom="6px";const $=document.createElement("div");$.className="storysplat-orbit-hint-icon";const V=document.createElementNS("http://www.w3.org/2000/svg","svg");V.setAttribute("viewBox","0 0 24 24");const O=document.createElementNS("http://www.w3.org/2000/svg","path");O.setAttribute("d","M13.64 21.97C13.14 22.21 12.54 22 12.31 21.5L10.13 16.76L7.62 18.78C7.45 18.92 7.24 19 7 19C6.44 19 6 18.56 6 18V3C6 2.44 6.44 2 7 2C7.24 2 7.47 2.09 7.64 2.23L7.65 2.22L19.14 11.86C19.57 12.22 19.31 12.92 18.76 12.96L14.78 13.21L16.97 17.95C17.21 18.45 17 19.05 16.5 19.28L13.64 21.97Z"),V.appendChild(O),$.appendChild(V),U.appendChild($);const W=document.createElement("div");W.className="storysplat-orbit-hint-label";const N=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);W.textContent=N?"Double-tap to refocus":"Double-click to refocus",U.appendChild(W),B.appendChild(U);const G=document.createElement("div");G.className="storysplat-wasd-hint-row",["Q","W","E"].forEach(t=>{const e=document.createElement("div");e.className="storysplat-wasd-key",e.textContent=t,e.setAttribute("data-keycode",`Key${t}`),G.appendChild(e)}),B.appendChild(G);const H=document.createElement("div");H.className="storysplat-wasd-hint-row",["A","S","D"].forEach(t=>{const e=document.createElement("div");e.className="storysplat-wasd-key",e.textContent=t,e.setAttribute("data-keycode",`Key${t}`),H.appendChild(e)}),B.appendChild(H);const X=document.createElement("div");X.className="storysplat-wasd-hint-label",X.textContent="Move",B.appendChild(X),t.appendChild(B),A.wasdHint=B;const q=document.createElement("div");q.className="storysplat-orbit-hint";const j=document.createElement("div");j.className="storysplat-orbit-hint-row";const Y=document.createElement("div");Y.className="storysplat-orbit-hint-icon";const Z=document.createElementNS("http://www.w3.org/2000/svg","svg");Z.setAttribute("viewBox","0 0 24 24");const Q=document.createElementNS("http://www.w3.org/2000/svg","path");Q.setAttribute("d","M13 6v5h5V7.75L22.25 12 18 16.25V13h-5v5h3.25L12 22.25 7.75 18H11v-5H6v3.25L1.75 12 6 7.75V11h5V6H7.75L12 1.75 16.25 6H13z"),Z.appendChild(Q),Y.appendChild(Z),j.appendChild(Y);const K=document.createElement("div");K.className="storysplat-orbit-hint-label",K.textContent="Drag to rotate",j.appendChild(K),q.appendChild(j);const J=document.createElement("div");J.className="storysplat-orbit-hint-row";const tt=document.createElement("div");tt.className="storysplat-orbit-hint-icon";const et=document.createElementNS("http://www.w3.org/2000/svg","svg");et.setAttribute("viewBox","0 0 24 24");const nt=document.createElementNS("http://www.w3.org/2000/svg","path");nt.setAttribute("d","M13.64 21.97C13.14 22.21 12.54 22 12.31 21.5L10.13 16.76L7.62 18.78C7.45 18.92 7.24 19 7 19C6.44 19 6 18.56 6 18V3C6 2.44 6.44 2 7 2C7.24 2 7.47 2.09 7.64 2.23L7.65 2.22L19.14 11.86C19.57 12.22 19.31 12.92 18.76 12.96L14.78 13.21L16.97 17.95C17.21 18.45 17 19.05 16.5 19.28L13.64 21.97Z"),et.appendChild(nt),tt.appendChild(et),J.appendChild(tt);const ot=document.createElement("div");ot.className="storysplat-orbit-hint-label";const it=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);ot.textContent=it?"Double-tap to refocus":"Double-click to refocus",J.appendChild(ot),q.appendChild(J),t.appendChild(q),A.orbitHint=q;const st=document.createElement("div");st.className="storysplat-doubletap-hint";const at=document.createElement("div");at.className="storysplat-doubletap-hint-icon";const rt=document.createElementNS("http://www.w3.org/2000/svg","svg");rt.setAttribute("viewBox","0 0 24 24");const lt=document.createElementNS("http://www.w3.org/2000/svg","path");lt.setAttribute("d","M13.64 21.97C13.14 22.21 12.54 22 12.31 21.5L10.13 16.76L7.62 18.78C7.45 18.92 7.24 19 7 19C6.44 19 6 18.56 6 18V3C6 2.44 6.44 2 7 2C7.24 2 7.47 2.09 7.64 2.23L7.65 2.22L19.14 11.86C19.57 12.22 19.31 12.92 18.76 12.96L14.78 13.21L16.97 17.95C17.21 18.45 17 19.05 16.5 19.28L13.64 21.97Z"),rt.appendChild(lt),at.appendChild(rt),st.appendChild(at);const ct=document.createElement("div");ct.className="storysplat-doubletap-hint-text";const dt=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);if(ct.textContent=dt?"Double-tap to focus on a point":"Double-click to focus on a point",st.appendChild(ct),t.appendChild(st),A.doubleTapHint=st,!y){const e=document.createElement("div");e.className="storysplat-watermark";const n=x||(b?`https://storysplat.com?ref=${b}`:"https://storysplat.com");e.innerHTML=v?`<a href="${n}" target="_blank">${v}</a>`:`Created with <a href="${n}" target="_blank">StorySplat</a>`,t.appendChild(e),A.watermark=e}if(n.debugMode){const e=document.createElement("div");e.className="storysplat-fps-counter",e.textContent="-- FPS",t.appendChild(e),A.fpsCounter=e}return A}function y(t,e,n="tour",o,i){const s=i?.container,a=i?.hideProgressText,l=t.prevButton||t.scrollControls?.querySelector(".storysplat-btn-prev"),c=t.nextButton||t.scrollControls?.querySelector(".storysplat-btn-next"),d=t.playButton||t.scrollControls?.querySelector(".storysplat-btn-play");if(l&&l.addEventListener("click",()=>e.prevWaypoint()),c&&c.addEventListener("click",()=>e.nextWaypoint()),d){const t=t=>{d.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>'};d.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.helpButton.classList.toggle("active")}),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 n=document;if(n.fullscreenElement||n.webkitFullscreenElement){n.exitFullscreen?n.exitFullscreen():n.webkitExitFullscreen&&n.webkitExitFullscreen();const e=t.fullscreenButton.querySelector(".storysplat-expand-icon"),o=t.fullscreenButton.querySelector(".storysplat-compress-icon");e&&(e.style.display="block"),o&&(o.style.display="none")}else{e?.requestFullscreen?e.requestFullscreen():e?.webkitRequestFullscreen&&e.webkitRequestFullscreen?.();const n=t.fullscreenButton.querySelector(".storysplat-expand-icon"),o=t.fullscreenButton.querySelector(".storysplat-compress-icon");n&&(n.style.display="none"),o&&(o.style.display="block")}});const n=()=>{const e=document,n=e.fullscreenElement||e.webkitFullscreenElement,o=t.fullscreenButton.querySelector(".storysplat-expand-icon"),i=t.fullscreenButton.querySelector(".storysplat-compress-icon");o&&(o.style.display=n?"none":"block"),i&&(i.style.display=n?"block":"none")};document.addEventListener("fullscreenchange",n),document.addEventListener("webkitfullscreenchange",n)}}const p=e=>{const n=t.scrollControls?.querySelector(".storysplat-progress-container"),o=t.scrollControls?.querySelector(".storysplat-scroll-buttons"),i=e?"":"none";t.progressText&&(t.progressText.style.display=e&&!a?"":"none"),n&&(n.style.display=i),o&&(o.style.display=i),t.prevButton&&t.prevButton.parentElement===s&&(t.prevButton.style.display=i),t.playButton&&t.playButton.parentElement===s&&(t.playButton.style.display=i),t.nextButton&&t.nextButton.parentElement===s&&(t.nextButton.style.display=i)},h=e=>{t.exploreControls&&(e?t.exploreControls.classList.add("visible"):t.exploreControls.classList.remove("visible"))},u=s||t.scrollControls?.parentElement;if(p("tour"===n),h("explore"===n||"walk"===n),e.setCameraMode){const t=u?.querySelectorAll(".storysplat-mode-btn");t?.forEach(n=>{n.addEventListener("click",()=>{const o=n.getAttribute("data-mode");o&&e.setCameraMode&&(e.setCameraMode(o),t.forEach(t=>t.classList.remove("selected")),n.classList.add("selected"),p("tour"===o),h("explore"===o||"walk"===o))})});const n=!!u?.querySelector('.storysplat-mode-btn[data-mode="walk"]');e.on("modeChange",({mode:e})=>{p("tour"===e),h("explore"===e||"walk"===e);const o="walk"===e?n?"walk":"explore":e;t?.forEach(t=>{const e=t.getAttribute("data-mode");t.classList.toggle("selected",e===o)})})}let m=0,g=-1;e.on("progressUpdate",({progress:e})=>{const n=100*Math.max(0,Math.min(1,e)),i=Math.round(n),s=performance.now();var a,l;s-m<33||(m=s,t.progressBar&&(t.progressBar.style.width=`${n}%`),t.progressText&&i!==g&&(g=i,t.progressText.innerHTML="",t.progressText.textContent=(a=o,l=i,(a?.percentageFormat||r.percentageFormat).replace("{n}",String(l)))))});let f=-1;e.on("waypointChange",({index:n,waypoint:o})=>{if(n===f)return;if(f=n,!t.waypointInfo)return;const i=t.waypointInfo.querySelector(".storysplat-waypoint-title"),s=t.waypointInfo.querySelector(".storysplat-waypoint-description");if(!i||!s)return;const a=o||e.getWaypoints?.()[n];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 v(e,n,o){const i=e.querySelector(".storysplat-hotspot-popup");if(!i)return;const s=i.querySelector(".storysplat-hotspot-popup-title"),a=i.querySelector(".storysplat-hotspot-popup-content"),r=i.querySelector(".storysplat-hotspot-popup-close");i.style.cssText="",i.classList.remove("fullscreen");const c=n.activationMode||"click",d=n.photoUrl||n.popupVideoUrl||"iframe"===n.contentType&&n.iframeUrl||"model"===n.contentType&&n.modelUrl;"click"===c&&d&&i.classList.add("fullscreen");const p=n.backgroundAlpha??.75;if(n.backgroundColor){const t=n.backgroundColor.replace("#","");if(6===t.length){const e=parseInt(t.substring(0,2),16),n=parseInt(t.substring(2,4),16),o=parseInt(t.substring(4,6),16);i.style.backgroundColor=`rgba(${e}, ${n}, ${o}, ${p})`}else i.style.backgroundColor=n.backgroundColor}else i.style.backgroundColor=`rgba(0, 0, 0, ${p})`;n.textColor&&(i.style.color=n.textColor,s&&(s.style.color=n.textColor)),n.fontFamily&&(i.style.fontFamily=n.fontFamily),n.fontSize&&(i.style.fontSize=`${n.fontSize}px`),r&&n.closeButtonColor&&(r.style.backgroundColor=n.closeButtonColor),s&&(s.textContent=n.title||l(o,"hotspotDefaultTitle"));let h="";if("iframe"===n.contentType&&n.iframeUrl&&(h+=`<iframe src="${n.iframeUrl}" title="${n.title||"Embedded content"}"></iframe>`),n.popupVideoUrl&&"video"===n.contentType&&(h+=`<div class="video-container"><video src="${n.popupVideoUrl}" controls playsinline webkit-playsinline preload="metadata"></video></div>`),!n.photoUrl||n.contentType&&"image"!==n.contentType&&"gif"!==n.contentType||(h+=`<img src="${n.photoUrl}" alt="${n.title||"Hotspot image"}" />`),"model"===n.contentType&&n.modelUrl&&(h+='<div class="model-container"><div class="model-loading">Loading 3D model...</div><canvas class="model-canvas"></canvas></div>'),n.information&&(h+=`<p>${n.information}</p>`),n.externalLinkUrl){const t=n.externalLinkButtonColor||"#007bff";h+=`\n <div onclick="window.open('${n.externalLinkUrl}', '_blank', 'noopener,noreferrer')"\n class="storysplat-hotspot-popup-link" style="background-color: ${t}">\n ${n.externalLinkText||l(o,"openExternalLink")}\n </div>\n `}if(a&&(a.innerHTML=h,"model"===n.contentType&&n.modelUrl)){const e=a.querySelector(".model-canvas");e&&function(e,n,o){const i=e.parentElement;if(!i)return;const s=i.getBoundingClientRect();e.width=s.width*window.devicePixelRatio,e.height=s.height*window.devicePixelRatio;const a=new t.Application(e,{graphicsDeviceOptions:{alpha:!0,antialias:!0,preserveDrawingBuffer:!1}});a.setCanvasResolution(t.RESOLUTION_AUTO),a.setCanvasFillMode(t.FILLMODE_NONE);const r=new t.Entity("popup-camera");r.addComponent("camera",{clearColor:new t.Color(0,0,0,0),fov:45,nearClip:.01,farClip:1e3}),a.root.addChild(r);const l=new t.Entity("popup-key-light");l.addComponent("light",{type:"directional",color:new t.Color(1,1,1),intensity:1.2,castShadows:!1}),l.setEulerAngles(45,30,0),a.root.addChild(l);const c=new t.Entity("popup-fill-light");c.addComponent("light",{type:"directional",color:new t.Color(.7,.75,.85),intensity:.6,castShadows:!1}),c.setEulerAngles(-30,-60,0),a.root.addChild(c),a.start();let d=0,p=-20,h=5,u=new t.Vec3(0,0,0),m=!1,g=0,f=0;function y(){const e=d*t.math.DEG_TO_RAD,n=p*t.math.DEG_TO_RAD,o=h*Math.cos(n)*Math.sin(e),i=h*Math.sin(n),s=h*Math.cos(n)*Math.cos(e);r.setPosition(u.x+o,u.y+i,u.z+s),r.lookAt(u)}e.addEventListener("mousedown",t=>{m=!0,g=t.clientX,f=t.clientY,t.preventDefault()}),e.addEventListener("mousemove",t=>{if(!m)return;const e=t.clientX-g,n=t.clientY-f;d-=.4*e,p=Math.max(-89,Math.min(89,p-.4*n)),g=t.clientX,f=t.clientY,y()}),e.addEventListener("mouseup",()=>{m=!1}),e.addEventListener("mouseleave",()=>{m=!1}),e.addEventListener("wheel",t=>{h=Math.max(.1,h*(1+.001*t.deltaY)),y(),t.preventDefault()},{passive:!1});let v=0,x=0;e.addEventListener("touchstart",t=>{if(1===t.touches.length)m=!0,g=t.touches[0].clientX,f=t.touches[0].clientY;else if(2===t.touches.length){m=!1;const e=t.touches[1].clientX-t.touches[0].clientX,n=t.touches[1].clientY-t.touches[0].clientY;v=Math.sqrt(e*e+n*n),x=h}t.preventDefault()},{passive:!1}),e.addEventListener("touchmove",t=>{if(1===t.touches.length&&m){const e=t.touches[0].clientX-g,n=t.touches[0].clientY-f;d-=.4*e,p=Math.max(-89,Math.min(89,p-.4*n)),g=t.touches[0].clientX,f=t.touches[0].clientY,y()}else if(2===t.touches.length){const e=t.touches[1].clientX-t.touches[0].clientX,n=t.touches[1].clientY-t.touches[0].clientY,o=Math.sqrt(e*e+n*n);v>0&&(h=Math.max(.1,x*(v/o)),y())}t.preventDefault()},{passive:!1}),e.addEventListener("touchend",()=>{m=!1,v=0});const b=new ResizeObserver(t=>{for(const n of t){const{width:t,height:o}=n.contentRect;t>0&&o>0&&(e.width=t*window.devicePixelRatio,e.height=o*window.devicePixelRatio,a.resizeCanvas(t,o))}});b.observe(i);const w=new t.Asset("popup-model","container",{url:n});w.on("load",()=>{const e=i.querySelector(".model-loading");e&&e.remove();const n=w.resource.instantiateRenderEntity();a.root.addChild(n);const o=[];if(n.findComponents("render").forEach(t=>{o.push(...t.meshInstances)}),o.length>0){const e=new t.BoundingBox;e.copy(o[0].aabb);for(let t=1;t<o.length;t++)e.add(o[t].aabb);u.copy(e.center);const n=e.halfExtents,i=Math.max(n.x,n.y,n.z);h=2.5*i}y()}),w.on("error",t=>{const e=i.querySelector(".model-loading");e&&(e.textContent="Failed to load model"),console.error("Model hotspot load error:",t)}),a.assets.add(w),a.assets.load(w),y(),o.__modelViewerCleanup=()=>{b.disconnect(),a.destroy()}}(e,n.modelUrl,i)}i.classList.add("visible");if(i.querySelector("video")){const t=()=>{const t=document,n=t.fullscreenElement||t.webkitFullscreenElement;n&&e.contains(n)?e.style.overflow="visible":e.style.overflow="hidden"};document.addEventListener("fullscreenchange",t),document.addEventListener("webkitfullscreenchange",t),i.__fullscreenCleanup=()=>{document.removeEventListener("fullscreenchange",t),document.removeEventListener("webkitfullscreenchange",t),e.style.overflow="hidden"}}}function x(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 b(t,e,n,o,i){if(t.joystickThumb)if(e){t.joystickThumb.classList.add("active");const e=Math.sqrt(n*n+o*o),s=Math.min(e,i),a=e>0?s/e:0,r=n*a,l=o*a;t.joystickThumb.style.transform=`translate(${r}px, ${l}px)`}else t.joystickThumb.classList.remove("active"),t.joystickThumb.style.transform="translate(0, 0)"}function w(t,e){t.lookZone&&(e?t.lookZone.classList.add("active"):t.lookZone.classList.remove("active"))}function S(t,e){t.wasdHint&&(e?t.wasdHint.classList.add("visible"):t.wasdHint.classList.remove("visible"))}function _(t,e){t.orbitHint&&(e?t.orbitHint.classList.add("visible"):t.orbitHint.classList.remove("visible"))}function M(t){if(!t.wasdHint)return null;const e=t.wasdHint.querySelectorAll(".storysplat-wasd-key[data-keycode]");if(0===e.length)return null;const n=new Map;e.forEach(t=>{const e=t.getAttribute("data-keycode");e&&n.set(e,t)});const o=new Set,i=t=>{const e=n.get(t.code);e&&e.classList.add("active")},s=t=>{const e=n.get(t.code);e&&!o.has(t.code)&&e.classList.remove("active")};document.addEventListener("keydown",i),document.addEventListener("keyup",s);const a=new Map;return e.forEach(t=>{const e=t.getAttribute("data-keycode");if(!e)return;const n=()=>{a.has(e)&&(a.delete(e),o.delete(e),t.classList.remove("active"),document.dispatchEvent(new KeyboardEvent("keyup",{code:e,key:e.replace("Key","").toLowerCase(),bubbles:!0,cancelable:!0})))};t.addEventListener("pointerdown",n=>{var i;n.preventDefault(),n.stopPropagation(),t.setPointerCapture(n.pointerId),i=n.pointerId,a.has(e)||(a.set(e,i),o.add(e),t.classList.add("active"),document.dispatchEvent(new KeyboardEvent("keydown",{code:e,key:e.replace("Key","").toLowerCase(),bubbles:!0,cancelable:!0})))}),t.addEventListener("pointerup",t=>{t.stopPropagation(),n()}),t.addEventListener("pointercancel",()=>{n()}),t.addEventListener("pointerleave",()=>{n()}),t.addEventListener("contextmenu",t=>t.preventDefault())}),()=>{document.removeEventListener("keydown",i),document.removeEventListener("keyup",s),a.forEach((t,e)=>{const o=n.get(e);o&&o.classList.remove("active"),document.dispatchEvent(new KeyboardEvent("keyup",{code:e,key:e.replace("Key","").toLowerCase(),bubbles:!0,cancelable:!0}))}),a.clear(),o.clear()}}function E(t,e){t.doubleTapHint&&(t.doubleTapHint.classList.add("fading"),setTimeout(()=>{t.doubleTapHint?.classList.remove("visible","fading")},400))}function C(t,e){if(!t.waypointListContainer)return;t.waypointListContainer.querySelectorAll(".storysplat-waypoint-item").forEach((t,n)=>{n===e?t.classList.add("active"):t.classList.remove("active")})}const T=new t.Vec3,A=new t.Vec3,P=new t.Pose,L=new t.InputFrame({move:[0,0,0],rotate:[0,0,0]}),k=(t,e,n)=>{const o=Math.sqrt(t[0]*t[0]+t[1]*t[1]);if(o<e)return void t.fill(0);const i=(o-e)/(n-e);t[0]*=i/o,t[1]*=i/o},z=(e,n,o,i,s=new t.Vec3)=>{const{fov:a,aspectRatio:r,horizontalFov:l,projection:c,orthoHeight:d}=e,p=e.system?.app,{width:h,height:u}=p?.graphicsDevice?.clientRect||{width:1920,height:1080};s.set(-n/h*2,o/u*2,0);const m=A.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(d*r,d,0);return s.mul(m),s};class R{constructor(e,n,o={}){this.enabled=!0,this._mode="orbit",this._enableOrbit=!0,this._enableFly=!0,this.enablePan=!0,this._pose=new t.Pose,this._preFocusMode="orbit",this._inputsDetached=!1,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.autoMoveForward=!1,this.autoMoveSpeedFactor=1,this.joystickEventName="joystick",this._collisionEntities=[],this._collisionRadius=.3,this._prevPosition=new t.Vec3,this._prevPositionValid=!1,this._collisionTestVec=new t.Vec3,this._voxelCollision=null,this._voxelSplatEntity=null,this._voxelSplatMatrix=new t.Mat4,this._voxelInvSplatMatrix=new t.Mat4,this._voxelFilePos=new t.Vec3,this._flyToActive=!1,this._flyToIsFocus=!1,this._flyToStartPos=new t.Vec3,this._flyToEndPos=new t.Vec3,this._flyToStartAngles=new t.Vec3,this._flyToEndAngles=new t.Vec3,this._flyToProgress=0,this._flyToDuration=.6,this._destroyHandler=null,this.camera=e,this.app=n;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=n.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._gamepadInput.attach(s),this._flyMobileInput.on("joystick:position:left",([t,e,n,o])=>{(t<0||"fly"===this._mode||!this.enabled)&&this.app.fire(`${this.joystickEventName}:left`,t,e,n,o)}),this._flyMobileInput.on("joystick:position:right",([t,e,n,o])=>{(t<0||"fly"===this._mode||!this.enabled)&&this.app.fire(`${this.joystickEventName}:right`,t,e,n,o)}),this._pose.look(this.camera.getPosition(),t.Vec3.ZERO),this._setMode("orbit"),this._controller=this._orbitController,void 0!==o.enableOrbit&&(this.enableOrbit=o.enableOrbit),void 0!==o.enableFly&&(this.enableFly=o.enableFly),void 0!==o.enablePan&&(this.enablePan=o.enablePan),o.focusPoint&&(this.focusPoint=o.focusPoint),void 0!==o.moveSpeed&&(this.moveSpeed=o.moveSpeed),void 0!==o.moveFastSpeed&&(this.moveFastSpeed=o.moveFastSpeed),void 0!==o.moveSlowSpeed&&(this.moveSlowSpeed=o.moveSlowSpeed),void 0!==o.rotateSpeed&&(this.rotateSpeed=o.rotateSpeed),void 0!==o.rotateTouchSens&&(this.rotateTouchSens=o.rotateTouchSens),void 0!==o.rotateJoystickSens&&(this.rotateJoystickSens=o.rotateJoystickSens),void 0!==o.zoomSpeed&&(this.zoomSpeed=o.zoomSpeed),void 0!==o.zoomPinchSens&&(this.zoomPinchSens=o.zoomPinchSens),void 0!==o.focusDamping&&(this.focusDamping=o.focusDamping),void 0!==o.rotateDamping&&(this.rotateDamping=o.rotateDamping),void 0!==o.moveDamping&&(this.moveDamping=o.moveDamping),void 0!==o.zoomDamping&&(this.zoomDamping=o.zoomDamping),o.pitchRange&&(this.pitchRange=o.pitchRange),o.yawRange&&(this.yawRange=o.yawRange),o.zoomRange&&(this.zoomRange=o.zoomRange),o.gamepadDeadZone&&(this.gamepadDeadZone=o.gamepadDeadZone),o.mobileInputLayout&&(this.mobileInputLayout=o.mobileInputLayout),void 0!==o.invertRotation&&(this.invertRotation=o.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(T)}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");this._desktopInput.read(),this._orbitMobileInput.read(),this._flyMobileInput.read(),this._gamepadInput.read();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,"fly"===e)this.syncFromCamera();else{const t=this.camera.getPosition();this._pose.look(t,this._lastFocusPoint),this._startZoomDist=t.distance(this._lastFocusPoint),this._pose.distance=this._startZoomDist}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}if(this._controller.attach(this._pose,!1),"orbit"===this._mode||"focus"===this._mode){const t=this.app.graphicsDevice.canvas;this._flyMobileInput.detach(),this._inputsDetached||this._orbitMobileInput.attach(t)}else if("fly"===this._mode){const t=this.app.graphicsDevice.canvas;this._orbitMobileInput.detach(),this._inputsDetached||this._flyMobileInput.attach(t)}this.app.fire("cameracontrols:modechange",this._mode)}}setMode(t){this._setMode(t)}_computeTweenAngles(e,n){const o=new t.Vec3(0,0,-1);this.camera.getRotation().transformVector(o,o);const i=Math.atan2(-o.y,Math.sqrt(o.x*o.x+o.z*o.z))*t.math.RAD_TO_DEG,s=Math.atan2(-o.x,-o.z)*t.math.RAD_TO_DEG;this._flyToStartAngles.set(-i,s,0);const a=(new t.Vec3).sub2(n,e);if(a.lengthSq()<1e-6)return void this._flyToEndAngles.set(-i,s,0);a.normalize();const r=Math.atan2(-a.y,Math.sqrt(a.x*a.x+a.z*a.z))*t.math.RAD_TO_DEG;let l=Math.atan2(-a.x,-a.z)*t.math.RAD_TO_DEG;const c=l-s;c>180?l-=360:c<-180&&(l+=360),this._flyToEndAngles.set(-r,l,0)}focus(t,e=!1){this._lastFocusPoint.copy(t);const n=this.camera.getPosition();this._startZoomDist=e?this._startZoomDist:n.distance(t),this._flyToStartPos.copy(n),this._flyToEndPos.lerp(n,t,.5),this._computeTweenAngles(this._flyToEndPos,t),this._flyToProgress=0,this._flyToActive=!0,this._flyToIsFocus=!0}flyTo(t,e=2){this._lastFocusPoint.copy(t);const n=this.camera.getPosition();this._flyToStartPos.copy(n);const o=n.distance(t),i=o>.001?Math.min(.5,50/o):0;this._flyToEndPos.lerp(n,t,i),this._computeTweenAngles(this._flyToEndPos,t),this._flyToProgress=0,this._flyToActive=!0,this._flyToIsFocus=!1}look(t,e=!1){"focus"!==this._mode&&(this._preFocusMode=this._mode),this._setMode("focus");const n=e?T.copy(this.camera.getPosition()).sub(t).normalize().mulScalar(this._startZoomDist).add(t):this.camera.getPosition();this._controller.attach(P.look(n,t))}reset(t,e){"focus"!==this._mode&&(this._preFocusMode=this._mode),this._setMode("focus"),this._controller.attach(P.look(e,t))}syncFromCamera(t){const e=this.camera.getPosition().clone();if(this._prevPositionValid=!1,t){this._lastFocusPoint.copy(t);const n=e.distance(t);this._startZoomDist=n,this._pose.distance=n,this._pose.look(e,t);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)),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 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));const o=this.camera.forward.clone();this._lastFocusPoint.copy(e).add(o.mulScalar(10));const i=10;this._startZoomDist=i,this._pose.distance=i}this._controller.attach(this._pose,!1)}syncFromPose(e,n,o){this._prevPositionValid=!1,this.camera.setPosition(e),this.camera.setRotation(n);const i=new t.Vec3(0,0,-1);n.transformVector(i,i);const s=Math.atan2(-i.y,Math.sqrt(i.x*i.x+i.z*i.z))*t.math.RAD_TO_DEG,a=Math.atan2(-i.x,-i.z)*t.math.RAD_TO_DEG;this._pose.position.copy(e),this._pose.angles.x=-s,this._pose.angles.y=a,this._pose.angles.z=0;let r=this._pose.angles.y;for(;r>180;)r-=360;for(;r<-180;)r+=360;if(this._pose.angles.y=r,this._lastYaw=r,this._pose.angles.x=Math.max(-89,Math.min(89,this._pose.angles.x)),o)this._lastFocusPoint.copy(o);else{const t=this.camera.forward.clone();this._lastFocusPoint.copy(e).add(t.mulScalar(10))}const l=e.distance(this._lastFocusPoint);this._startZoomDist=l,this._pose.distance=l,this._controller.attach(this._pose,!1)}setFocusDistance(t,e){this._lastFocusPoint.copy(t),this._startZoomDist=e,this._pose.distance=e}enable(){if(this._desktopInput.read(),this._orbitMobileInput.read(),this._flyMobileInput.read(),this._gamepadInput.read(),this.enabled=!0,this._inputsDetached){const t=this.app.graphicsDevice.canvas;this._desktopInput.attach(t),"fly"===this._mode?this._flyMobileInput.attach(t):this._orbitMobileInput.attach(t),this._gamepadInput.attach(t),this._inputsDetached=!1}}disable(){this.enabled=!1,this.autoMoveForward=!1,this._desktopInput.read(),this._orbitMobileInput.read(),this._flyMobileInput.read(),this._gamepadInput.read()}detachInputSources(){this._desktopInput.detach(),this._orbitMobileInput.detach(),this._flyMobileInput.detach(),this._gamepadInput.detach(),this._inputsDetached=!0}reattachMobileInput(){if(!this._inputsDetached)return;const t=this.app.graphicsDevice.canvas;this._flyMobileInput.attach(t)}update(e){if(!this.enabled)return;const{keyCode:n}=t.KeyboardMouseSource,{key:o,button:i,mouse:s,wheel:a}=this._desktopInput.read(),{touch:r,pinch:l,count:c}=this._orbitMobileInput.read(),{leftInput:d,rightInput:p}=this._flyMobileInput.read(),{leftStick:h,rightStick:u}=this._gamepadInput.read();k(h,this.gamepadDeadZone.x,this.gamepadDeadZone.y),k(u,this.gamepadDeadZone.x,this.gamepadDeadZone.y),this._state.axis.add(T.set(o[n.D]-o[n.A]+(o[n.RIGHT]-o[n.LEFT]),o[n.E]-o[n.Q],o[n.W]-o[n.S]+(o[n.UP]-o[n.DOWN])));for(let t=0;t<this._state.mouse.length;t++)this._state.mouse[t]+=i[t];this._state.shift+=o[n.SHIFT],this._state.ctrl+=o[n.CTRL],this._state.touches+=c[0];const m=+("orbit"===this._mode),g=+("fly"===this._mode),f=+(this._state.touches>1),y=+(this._state.shift||this._state.mouse[1]),v=+this._flyMobileInput.layout.endsWith("joystick"),x=(this._state.shift?this.moveFastSpeed:this._state.ctrl?this.moveSlowSpeed:this.moveSpeed)*e,b=60*this.zoomSpeed*e,w=b*this.zoomPinchSens,S=60*this.rotateSpeed*e,_=S*this.rotateTouchSens,M=this.rotateSpeed*this.rotateJoystickSens*60*e,{deltas:E}=L,C=T.set(0,0,0),P=this._state.axis.clone().normalize();C.add(P.mulScalar(g*x*this.keyboardSpeedMultiplier));const R=z(this.cameraComponent,s[0],s[1],this._pose.distance);C.add(R.mulScalar(m*y*+this.enablePan));const D=A.set(0,0,a[0]);C.add(D.mulScalar(m*b)),E.move.append([C.x,C.y,C.z]),C.set(0,0,0);const I=A.set(s[0],s[1],0);C.add(I.mulScalar((1-m*y)*S)),this.invertRotation&&(C.y=-C.y),E.rotate.append([C.x,C.y,C.z]),C.set(0,0,0);const F=A.set(d[0],0,-d[1]);C.add(F.mulScalar(g*x));const B=z(this.cameraComponent,r[0],r[1],this._pose.distance);C.add(B.mulScalar(m*f*+this.enablePan));const U=A.set(0,0,l[0]);C.add(U.mulScalar(m*f*w)),E.move.append([C.x,C.y,C.z]),C.set(0,0,0);const $=A.set(r[0],r[1],0);C.add($.mulScalar(m*(1-f)*_));const V=A.set(p[0],p[1],0);C.add(V.mulScalar(g*(v?M:_))),this.invertRotation&&(C.y=-C.y),E.rotate.append([C.x,C.y,C.z]),C.set(0,0,0);const O=A.set(h[0],0,-h[1]);if(C.add(O.mulScalar(g*x)),E.move.append([C.x,C.y,C.z]),this.autoMoveForward&&g&&E.rotate.length()>.001&&(this.autoMoveForward=!1),this.autoMoveForward&&g){const t=this.moveSpeed*this.autoMoveSpeedFactor*e;E.move.append([0,0,t])}C.set(0,0,0);const W=A.set(u[0],u[1],0);if(C.add(W.mulScalar(g*M)),this.invertRotation&&(C.y=-C.y),E.rotate.append([C.x,C.y,C.z]),this.app.xr?.active)return void L.read();if("focus"===this._mode){const t=E.move.length()+E.rotate.length()>0,e=this._focusController.complete?.()??!1;(t||e)&&this._setMode(this._preFocusMode)}this._pose.copy(this._controller.update(L,e));const N=this._collisionEntities.length>0||null!==this._voxelCollision;if("fly"!==this._mode&&"orbit"!==this._mode||!N)"fly"!==this._mode&&"orbit"!==this._mode&&(this._prevPositionValid=!1);else{if(this._prevPositionValid){const t=this._pose.position,e=this._prevPosition,n=this._collisionTestVec;let o=!1;n.set(t.x,e.y,e.z),this.checkCollision(n)&&(t.x=e.x,o=!0),n.set(t.x,t.y,e.z),this.checkCollision(n)&&(t.y=e.y,o=!0),n.set(t.x,t.y,t.z),this.checkCollision(n)&&(t.z=e.z,o=!0),o&&this._controller.attach(this._pose,!1)}this._prevPosition.copy(this._pose.position),this._prevPositionValid=!0}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}if(this._flyToActive){if(E.move.length()+E.rotate.length()>0)this._flyToActive=!1,this._flyToIsFocus?this.syncFromCamera(this._lastFocusPoint):this.syncFromCamera();else{this._flyToProgress=Math.min(1,this._flyToProgress+e/this._flyToDuration);const n=1-Math.pow(1-this._flyToProgress,3);if(this._pose.position.lerp(this._flyToStartPos,this._flyToEndPos,n),this._pose.angles.x=t.math.lerpAngle(this._flyToStartAngles.x,this._flyToEndAngles.x,n),this._pose.angles.y=t.math.lerpAngle(this._flyToStartAngles.y,this._flyToEndAngles.y,n),this._pose.angles.z=0,this._flyToProgress>=1){if(this._flyToActive=!1,this._prevPositionValid=!1,this._flyToIsFocus){const t=this._pose.position.distance(this._lastFocusPoint);this._pose.look(this._pose.position,this._lastFocusPoint),this._pose.distance=t,this._startZoomDist=t,this._lastYaw=this._pose.angles.y}this._controller.attach(this._pose,!1)}}}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}setVoxelCollision(t,e){this._voxelCollision=t,this._voxelSplatEntity=e,this._prevPositionValid=!1}checkCollision(t){const e=this._collisionRadius;for(const n of this._collisionEntities){const o=n.getPosition(),i=n.getLocalScale(),s=n._collisionMeshType;let a,r,l,c,d,p;const h=n._collisionBounds;h?(c=h.center.x,d=h.center.y,p=h.center.z,a=h.halfExtents.x,r=h.halfExtents.y,l=h.halfExtents.z):"floor"===s||"plane"===s?(c=o.x,d=o.y,p=o.z,a=i.x/2,r=.05,l=i.z/2):(c=o.x,d=o.y,p=o.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-d),g=Math.abs(t.z-p);if(u<a+e&&m<r+e&&g<l+e)return!0}if(this._voxelCollision){const n=this._voxelFilePos;if(this._voxelSplatEntity?(this._voxelSplatMatrix.copy(this._voxelSplatEntity.getWorldTransform()),this._voxelInvSplatMatrix.copy(this._voxelSplatMatrix).invert(),this._voxelInvSplatMatrix.transformPoint(t,n)):n.set(t.x,-t.y,-t.z),this._voxelCollision.checkSphereSolid(n.x,n.y,n.z,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 D(t,e,n){return t+4*e+16*n}function I(t,e,n){const o=2*e;return n<32?1==(t[o]>>>n&1):1==(t[o+1]>>>n-32&1)}function F(t,e){let n=t&(1<<e)-1,o=0;for(;n;)o++,n&=n-1;return o}class B{constructor(t,e,n){this.nodes=t,this.leafData=e,this.meta=n;const o=n.leafSize*n.voxelResolution,i=Math.pow(2,n.treeDepth)*o;this.gridMinX=n.gridBounds.min[0],this.gridMinY=n.gridBounds.min[1],this.gridMinZ=n.gridBounds.min[2],this.gridMaxX=this.gridMinX+i,this.gridMaxY=this.gridMinY+i,this.gridMaxZ=this.gridMinZ+i,this.voxelRes=n.voxelResolution,this.leafBlockSize=n.leafSize*n.voxelResolution}static async load(t){console.log(`[VoxelCollision.load] Fetching JSON: ${t.substring(0,100)}...`);const e=await fetch(t);if(!e.ok)throw new Error(`[VoxelCollision.load] Failed to fetch .voxel.json: ${e.status} ${e.statusText}`);const n=await e.json();console.log(`[VoxelCollision.load] JSON parsed — nodeCount: ${n.nodeCount}, leafDataCount: ${n.leafDataCount}, res: ${n.voxelResolution}, hasBinUrl: ${!!n.binUrl}`);const o=n.binUrl??t.replace(/\.voxel\.json(\?.*)?$/,(t,e)=>`.voxel.bin${e??""}`);console.log(`[VoxelCollision.load] Fetching BIN (${n.binUrl?"embedded URL":"derived URL"}): ${o.substring(0,100)}...`);const i=await fetch(o);if(!i.ok)throw new Error(`[VoxelCollision.load] Failed to fetch .voxel.bin: ${i.status} ${i.statusText} — URL: ${o.substring(0,120)}`);const s=await i.arrayBuffer();console.log(`[VoxelCollision.load] BIN fetched: ${s.byteLength} bytes`);const a=4*(n.nodeCount+n.leafDataCount);if(s.byteLength<a)throw new Error(`[VoxelCollision.load] Binary truncated: expected ${a} bytes, got ${s.byteLength}`);const r=4*n.nodeCount,l=new Uint32Array(s,0,n.nodeCount),c=new Uint32Array(s,r,n.leafDataCount),d=n.leafSize*n.voxelResolution,p=Math.pow(2,n.treeDepth)*d;return console.log(`[VoxelCollision.load] OK — ${n.nodeCount} nodes, ${n.numMixedLeaves} mixed leaves, grid: [${n.gridBounds.min}] → [${n.gridBounds.max}], octreeBounds: [${n.gridBounds.min[0]},${n.gridBounds.min[1]},${n.gridBounds.min[2]}] → [${n.gridBounds.min[0]+p},${n.gridBounds.min[1]+p},${n.gridBounds.min[2]+p}] (extent=${p})`),new B(l,c,n)}getMetadata(){return this.meta}checkPointSolid(t,e,n){return!(t<this.gridMinX||t>=this.gridMaxX||e<this.gridMinY||e>=this.gridMaxY||n<this.gridMinZ||n>=this.gridMaxZ)&&(0!==this.nodes.length&&this._descendPoint(0,t,e,n,this.gridMinX,this.gridMinY,this.gridMinZ,this.gridMaxX,this.gridMaxY,this.gridMaxZ))}_descendPoint(t,e,n,o,i,s,a,r,l,c){const d=this.nodes[t];if(-16777216==(0|d))return!0;const p=d>>>24&255,h=16777215&d;if(0===p){const t=Math.max(0,Math.min(3,Math.floor((e-i)/this.voxelRes))),r=Math.max(0,Math.min(3,Math.floor((n-s)/this.voxelRes))),l=Math.max(0,Math.min(3,Math.floor((o-a)/this.voxelRes)));return I(this.leafData,h,D(t,r,l))}const u=.5*(i+r),m=.5*(s+l),g=.5*(a+c),f=(e>=u?1:0)|(n>=m?2:0)|(o>=g?4:0);if(!(p>>f&1))return!1;const y=h+F(p,f);return this._descendPoint(y,e,n,o,1&f?u:i,2&f?m:s,4&f?g:a,1&f?r:u,2&f?l:m,4&f?c:g)}checkSphereSolid(t,e,n,o){return!(t+o<this.gridMinX||t-o>=this.gridMaxX||e+o<this.gridMinY||e-o>=this.gridMaxY||n+o<this.gridMinZ||n-o>=this.gridMaxZ)&&(0!==this.nodes.length&&this._descendSphere(0,t,e,n,o*o,this.gridMinX,this.gridMinY,this.gridMinZ,this.gridMaxX,this.gridMaxY,this.gridMaxZ))}_descendSphere(t,e,n,o,i,s,a,r,l,c,d){const p=this.nodes[t];if(-16777216==(0|p))return!0;const h=p>>>24&255,u=16777215&p;if(0===h){const t=s,l=a,c=r,d=this.voxelRes;for(let s=0;s<4;s++)for(let a=0;a<4;a++)for(let r=0;r<4;r++){if(!I(this.leafData,u,D(r,a,s)))continue;const p=t+r*d,h=l+a*d,m=c+s*d,g=p+d,f=h+d,y=m+d,v=Math.max(p-e,0,e-g),x=Math.max(h-n,0,n-f),b=Math.max(m-o,0,o-y);if(v*v+x*x+b*b<=i)return!0}return!1}const m=.5*(s+l),g=.5*(a+c),f=.5*(r+d);for(let t=0;t<8;t++){if(!(h>>t&1))continue;const p=1&t?m:s,y=2&t?g:a,v=4&t?f:r,x=1&t?l:m,b=2&t?c:g,w=4&t?d:f,S=Math.max(p-e,0,e-x),_=Math.max(y-n,0,n-b),M=Math.max(v-o,0,o-w);if(S*S+_*_+M*M<=i){const s=u+F(h,t);if(this._descendSphere(s,e,n,o,i,p,y,v,x,b,w))return!0}}return!1}findGround(t,e,n){const o=n??this.gridMaxY;return t<this.gridMinX||t>=this.gridMaxX||e<this.gridMinZ||e>=this.gridMaxZ||0===this.nodes.length?null:this._findGround(0,t,e,o,this.gridMinX,this.gridMinY,this.gridMinZ,this.gridMaxX,this.gridMaxY,this.gridMaxZ)}_findGround(t,e,n,o,i,s,a,r,l,c){if(s>=o)return null;const d=this.nodes[t];if(-16777216==(0|d))return Math.min(l,o);const p=d>>>24&255,h=16777215&d;if(0===p){const t=Math.max(0,Math.min(3,Math.floor((e-i)/this.voxelRes))),r=Math.max(0,Math.min(3,Math.floor((n-a)/this.voxelRes)));for(let e=3;e>=0;e--){const n=s+(e+1)*this.voxelRes;if(!(n>o)&&I(this.leafData,h,D(t,e,r)))return n}return null}const u=.5*(i+r),m=.5*(s+l),g=.5*(a+c),f=e>=u?1:0,y=n>=g?4:0,v=2|f|y,x=0|f|y;for(const t of[v,x]){if(!(p>>t&1))continue;const d=1&t?u:i,f=2&t?m:s,y=4&t?g:a,v=1&t?r:u,x=2&t?l:m,b=4&t?c:g,w=h+F(p,t),S=this._findGround(w,e,n,o,d,f,y,v,x,b);if(null!==S)return S}return null}findGroundWorld(t,e,n,o){const i=t,s=-e,a=-n;if(i<this.gridMinX||i>=this.gridMaxX||s<this.gridMinZ||s>=this.gridMaxZ)return null;if(0===this.nodes.length)return null;const r=a-o,l=this._findCeiling(0,i,s,r,this.gridMinX,this.gridMinY,this.gridMinZ,this.gridMaxX,this.gridMaxY,this.gridMaxZ);return null!==l?-l:null}_findCeiling(t,e,n,o,i,s,a,r,l,c){if(l<o)return null;const d=this.nodes[t];if(-16777216==(0|d))return Math.max(s,o);const p=d>>>24&255,h=16777215&d;if(0===p){const t=Math.max(0,Math.min(3,Math.floor((e-i)/this.voxelRes))),r=Math.max(0,Math.min(3,Math.floor((n-a)/this.voxelRes)));for(let e=0;e<4;e++){const n=s+(e+1)*this.voxelRes;if(!(n<o)&&I(this.leafData,h,D(t,e,r)))return n}return null}const u=.5*(i+r),m=.5*(s+l),g=.5*(a+c),f=e>=u?1:0,y=n>=g?4:0,v=0|f|y,x=2|f|y;for(const t of[v,x]){if(!(p>>t&1))continue;const d=1&t?u:i,f=2&t?m:s,y=4&t?g:a,v=1&t?r:u,x=2&t?l:m,b=4&t?c:g,w=h+F(p,t),S=this._findCeiling(w,e,n,o,d,f,y,v,x,b);if(null!==S)return S}return null}isVoxelSolid(t,e,n){if(t<this.gridMinX||e<this.gridMinY||n<this.gridMinZ||t>=this.gridMaxX||e>=this.gridMaxY||n>=this.gridMaxZ)return!1;if(0===this.nodes.length)return!1;const o=t-this.gridMinX,i=e-this.gridMinY,s=n-this.gridMinZ;let a=0,r=this.gridMaxX-this.gridMinX;for(;;){const l=this.nodes[a];if(-16777216==(0|l))return!0;const c=l>>>24&255,d=16777215&l;if(0===c){const a=this.meta.leafSize*this.voxelRes,r=t-o%a,l=e-i%a,c=n-s%a,p=Math.floor((t-r)/this.voxelRes),h=Math.floor((e-l)/this.voxelRes),u=Math.floor((n-c)/this.voxelRes);return I(this.leafData,d,D(Math.max(0,Math.min(3,p)),Math.max(0,Math.min(3,h)),Math.max(0,Math.min(3,u))))}r/=2;const p=(o&r?1:0)|(i&r?1:0)<<1|(s&r?1:0)<<2;if(!(c>>p&1))return!1;a=d+F(c,p)}}querySphere(t,e,n,o){const i=this.voxelRes;let s=t,a=e,r=n,l=0,c=0,d=0,p=!1,h=0,u=0,m=0,g=0;for(let t=0;t<4;t++){const t=Math.floor((s-o)/i)*i,e=Math.floor((a-o)/i)*i,n=Math.floor((r-o)/i)*i,f=Math.floor((s+o)/i)*i,y=Math.floor((a+o)/i)*i,v=Math.floor((r+o)/i)*i;let x=-1/0,b=0,w=0,S=0;for(let l=n;l<=v;l+=i)for(let n=e;n<=y;n+=i)for(let e=t;e<=f;e+=i){if(!this.isVoxelSolid(Math.round(e),Math.round(n),Math.round(l)))continue;const t=e,c=n,d=l,p=e+i,h=n+i,u=l+i,m=s-Math.max(t,Math.min(s,p)),g=a-Math.max(c,Math.min(a,h)),f=r-Math.max(d,Math.min(r,u)),y=m*m+g*g+f*f;if(y>=o*o)continue;const v=(p-t)/2+o-Math.abs(s-(t+p)/2),_=(h-c)/2+o-Math.abs(a-(c+h)/2),M=(u-d)/2+o-Math.abs(r-(d+u)/2);if(v<=0||_<=0||M<=0)continue;const E=o-Math.sqrt(y);E>x&&(x=E,v<=_&&v<=M?(b=Math.sign(s-(t+p)/2)*v,w=0,S=0):_<=M?(b=0,w=Math.sign(a-(c+h)/2)*_,S=0):(b=0,w=0,S=Math.sign(r-(d+u)/2)*M))}if(x<=0)break;if(g>0){const t=b*h+w*u+S*m;t<0&&(b-=t*h,w-=t*u,S-=t*m)}s+=b,a+=w,r+=S,l+=b,c+=w,d+=S,p=!0;const _=Math.sqrt(b*b+w*w+S*S);_>1e-6&&(h=b/_,u=w/_,m=S/_,g++)}return p?{x:l,y:c,z:d}:null}collectSolidAABBs(t=2e5){const e=[];return this._collectSolid(0,this.gridMinX,this.gridMinY,this.gridMinZ,this.gridMaxX,this.gridMaxY,this.gridMaxZ,e,t),new Float32Array(e)}collectLeafAABBs(t=5e5){if(this.nodes.length>0){const t=this.nodes[0],e=t>>>24&255,n=16777215&t,o=.5*(this.gridMinX+this.gridMaxX),i=.5*(this.gridMinY+this.gridMaxY),s=.5*(this.gridMinZ+this.gridMaxZ),a=["---","X--","-Y-","XY-","--Z","X-Z","-YZ","XYZ"],r=[];for(let t=0;t<8;t++)e&1<<t&&r.push(`${t}(${a[t]})`);console.log(`[VoxelCollision] Root: mask=0b${e.toString(2).padStart(8,"0")} base=${n} mid=(${o.toFixed(1)},${i.toFixed(1)},${s.toFixed(1)}) active=[${r.join(",")}]`)}const e=[];return this._collectLeaves(0,this.gridMinX,this.gridMinY,this.gridMinZ,this.gridMaxX,this.gridMaxY,this.gridMaxZ,e,t),new Float32Array(e)}_collectLeaves(t,e,n,o,i,s,a,r,l){if(r.length/4>=l)return;const c=this.nodes[t];if(-16777216==(0|c))return void r.push(.5*(e+i),.5*(n+s),.5*(o+a),.5*(i-e));const d=c>>>24&255;if(0===d)return void r.push(.5*(e+i),.5*(n+s),.5*(o+a),.5*(i-e));const p=.5*(e+i),h=.5*(n+s),u=.5*(o+a);for(let t=0;t<8;t++){if(!(d>>t&1))continue;if(r.length/4>=l)return;const m=1&t?p:e,g=2&t?h:n,f=4&t?u:o,y=1&t?i:p,v=2&t?s:h,x=4&t?a:u,b=(16777215&c)+F(d,t);this._collectLeaves(b,m,g,f,y,v,x,r,l)}}_collectSolid(t,e,n,o,i,s,a,r,l){if(r.length/4>=l)return;const c=this.nodes[t];if(-16777216==(0|c)){const t=.5*(e+i),l=.5*(n+s),c=.5*(o+a),d=.5*(i-e);return void r.push(t,l,c,d)}const d=c>>>24&255,p=16777215&c;if(0===d){const t=this.voxelRes,i=.5*t;for(let s=0;s<4;s++)for(let a=0;a<4;a++)for(let c=0;c<4;c++){if(r.length/4>=l)return;I(this.leafData,p,D(c,a,s))&&r.push(e+c*t+i,n+a*t+i,o+s*t+i,i)}return}const h=.5*(e+i),u=.5*(n+s),m=.5*(o+a);for(let t=0;t<8;t++){if(!(d>>t&1))continue;if(r.length/4>=l)return;const c=1&t?h:e,g=2&t?u:n,f=4&t?m:o,y=1&t?i:h,v=2&t?s:u,x=4&t?a:m,b=p+F(d,t);this._collectSolid(b,c,g,f,y,v,x,r,l)}}}function U(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,n,o={}){this.enabled=!1,this.velocity=new t.Vec3,this.isGrounded=!1,this.yaw=0,this.pitch=0,this.keys={},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.autoMoveForward=!1,this.autoMoveSpeedFactor=1,this.headBobEnabled=!0,this.horizontalVelocity=new t.Vec3,this.targetVelocity=new t.Vec3,this.bobTimer=0,this.bobOffsetY=0,this.bobOffsetX=0,this.currentTime=0,this.lastGroundedTime=-1/0,this.lastJumpPressTime=-1/0,this.coyoteTime=.15,this.jumpBufferTime=.15,this.landingDipAmount=0,this.previousVelocityY=0,this.wasGroundedLastFrame=!1,this.collisionEntities=[],this.floorEntity=null,this.voxelCollision=null,this._lastVoxelUrl=null,this._splatEntity=null,this._invSplatMatrix=new t.Mat4,this._splatMatrix=new t.Mat4,this.keydownHandler=null,this.keyupHandler=null,this.mousemoveHandler=null,this.mousedownHandler=null,this.mouseupHandler=null,this.mouseIsDown=!1,this.lastMouseX=0,this.lastMouseY=0,this.joystickMoveX=0,this.joystickMoveZ=0,this.joystickLookX=0,this.joystickLookY=0,this.tmpVec=new t.Vec3,this.tmpVec2=new t.Vec3,this.forward=new t.Vec3,this.right=new t.Vec3,this._debugMaterial=null,this._debugVisible=!1,this._originalMaterials=new Map,this._voxelDebugEntity=null,this._voxelDebugVisible=!1,this.camera=e,this.app=n,void 0!==o.moveSpeed&&(this.moveSpeed=o.moveSpeed),void 0!==o.sprintMultiplier&&(this.sprintMultiplier=o.sprintMultiplier),void 0!==o.lookSensitivity&&(this.lookSensitivity=o.lookSensitivity),void 0!==o.playerHeight&&(this.playerHeight=o.playerHeight),void 0!==o.gravity&&(this.gravity=o.gravity),void 0!==o.maxFallSpeed&&(this.maxFallSpeed=o.maxFallSpeed),void 0!==o.jumpVelocity&&(this.jumpVelocity=o.jumpVelocity),void 0!==o.collisionRadius&&(this.collisionRadius=o.collisionRadius),void 0!==o.stepHeight&&(this.stepHeight=o.stepHeight),void 0!==o.groundCheckDistance&&(this.groundCheckDistance=o.groundCheckDistance),void 0!==o.moveDamping&&(this.moveDamping=o.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 n=[];e.forEach((e,o)=>{if("custom"===e.meshType&&e.customMeshUrl){const t=this.loadCustomCollisionMesh(e,o);return void n.push(t)}if("custom"===e.meshType&&e.meshData&&e.meshData.length>=9){const n=new t.Entity(`collision-custom-meshdata-${o}`);return this.configureCollisionEntity(n,e),void this.computeBoundsFromMeshData(n,e.meshData)}let i=null;switch(e.meshType){case"cube":i=new t.Entity(`collision-cube-${o}`),i.addComponent("render",{type:"box"});break;case"sphere":i=new t.Entity(`collision-sphere-${o}`),i.addComponent("render",{type:"sphere"});break;case"floor":i=new t.Entity(`collision-floor-${o}`),i.addComponent("render",{type:"plane"}),this.floorEntity=i;break;default:i=new t.Entity(`collision-plane-${o}`),i.addComponent("render",{type:"plane"})}i&&this.configureCollisionEntity(i,e)}),n.length>0&&await Promise.all(n),console.log("[CharacterController] Created",this.collisionEntities.length,"collision entities")}async loadCustomCollisionMesh(e,n){const o=e.customMeshUrl;if(o){console.log("[CharacterController] Loading custom collision mesh:",o);try{const i=o.split("?")[0].split(".").pop()?.toLowerCase()||"glb",s="gltf"===i||"glb"===i?"container":"model",a=new t.Asset(`collision-custom-${n}`,s,{url:o});await new Promise((i,r)=>{a.ready(()=>{try{const o=new t.Entity(`collision-custom-${n}`);if("container"===s){const t=a.resource;if(t&&t.instantiateRenderEntity){const e=t.instantiateRenderEntity();for(;e.children.length>0;)o.addChild(e.children[0]);e.destroy()}}else o.addComponent("model",{asset:a});this.configureCollisionEntity(o,e),this.computeAndStoreBounds(o),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:",o,t),r(t)}),this.app.assets.add(a),this.app.assets.load(a)})}catch(t){console.error("[CharacterController] Failed to load custom collision mesh:",o,t)}}}computeAndStoreBounds(e){const n=new t.BoundingBox;let o=!1;const i=e=>{if(e.render&&e.render.meshInstances)for(const t of e.render.meshInstances)t.aabb&&(o?n.add(t.aabb):(n.copy(t.aabb),o=!0));for(const n of e.children)n instanceof t.Entity&&i(n)};i(e),o&&(e._collisionBounds=n)}computeBoundsFromMeshData(e,n){const o=new t.Vec3(1/0,1/0,1/0),i=new t.Vec3(-1/0,-1/0,-1/0);for(let t=0;t<n.length;t+=3){const e=n[t],s=n[t+1],a=-n[t+2];o.x=Math.min(o.x,e),o.y=Math.min(o.y,s),o.z=Math.min(o.z,a),i.x=Math.max(i.x,e),i.y=Math.max(i.y,s),i.z=Math.max(i.z,a)}const s=e.getWorldTransform(),a=[new t.Vec3(o.x,o.y,o.z),new t.Vec3(i.x,o.y,o.z),new t.Vec3(o.x,i.y,o.z),new t.Vec3(i.x,i.y,o.z),new t.Vec3(o.x,o.y,i.z),new t.Vec3(i.x,o.y,i.z),new t.Vec3(o.x,i.y,i.z),new t.Vec3(i.x,i.y,i.z)],r=new t.Vec3(1/0,1/0,1/0),l=new t.Vec3(-1/0,-1/0,-1/0);for(const t of a)s.transformPoint(t,t),r.x=Math.min(r.x,t.x),r.y=Math.min(r.y,t.y),r.z=Math.min(r.z,t.z),l.x=Math.max(l.x,t.x),l.y=Math.max(l.y,t.y),l.z=Math.max(l.z,t.z);const c=new t.BoundingBox;c.center.set((r.x+l.x)/2,(r.y+l.y)/2,(r.z+l.z)/2),c.halfExtents.set((l.x-r.x)/2,(l.y-r.y)/2,(l.z-r.z)/2),e._collisionBounds=c}configureCollisionEntity(e,n){const o=U(n.position,[0,0,0]);e.setPosition(o[0],o[1],-o[2]);const i=U(n.rotation,[0,0,0]),s=i[0],a=i[1]/2,r=s/2,l=i[2]/2,c=Math.cos(a),d=Math.sin(a),p=Math.cos(r),h=Math.sin(r),u=Math.cos(l),m=Math.sin(l),g=c*p*u+d*h*m,f=c*h*u+d*p*m,y=d*p*u-c*h*m,v=c*p*m-d*h*u,x=new t.Quat(-f,-y,v,g);if("plane"===n.meshType){const n=(new t.Quat).setFromEulerAngles(90,0,0);e.setRotation((new t.Quat).mul2(x,n))}else e.setRotation(x);const b=U(n.scaling,[1,1,1]),w=n.meshType;if("plane"===w){const t=.01;e.setLocalScale(3*b[0],b[2]*t,3*b[1])}else"cube"===w||"sphere"===w?e.setLocalScale(3*b[0],3*b[1],3*b[2]):"floor"===w?e.setLocalScale(100*b[0],1*b[1],100*b[2]):e.setLocalScale(b[0],b[1],b[2]);this.setEntityVisibility(e,!1),e._collisionMeshType=n.meshType,this.app.root.addChild(e),"custom"!==w&&this.computePrimitiveBounds(e),this.collisionEntities.push(e)}computePrimitiveBounds(e){const n=e.getWorldTransform().data,o=new t.BoundingBox;o.center.set(n[12],n[13],n[14]),o.halfExtents.set(.5*(Math.abs(n[0])+Math.abs(n[4])+Math.abs(n[8])),.5*(Math.abs(n[1])+Math.abs(n[5])+Math.abs(n[9])),.5*(Math.abs(n[2])+Math.abs(n[6])+Math.abs(n[10]))),e._collisionBounds=o}setEntityVisibility(e,n){e.render&&(e.render.enabled=n);for(const o of e.children)o instanceof t.Entity&&this.setEntityVisibility(o,n)}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.bobTimer=0,this.bobOffsetY=0,this.bobOffsetX=0,this.currentTime=0,this.lastGroundedTime=-1/0,this.lastJumpPressTime=-1/0,this.landingDipAmount=0,this.previousVelocityY=0,this.wasGroundedLastFrame=!1,this.setupInputHandlers(),console.log("[CharacterController] Enabled")}disable(){this.enabled&&(this.enabled=!1,this.autoMoveForward=!1,this.joystickMoveX=0,this.joystickMoveZ=0,this.joystickLookX=0,this.joystickLookY=0,this.removeInputHandlers(),this.mouseIsDown=!1,console.log("[CharacterController] Disabled"))}setupInputHandlers(){const t=this.app.graphicsDevice.canvas;this.keydownHandler=t=>{this.keys[t.code]=!0,"Space"===t.code&&(this.lastJumpPressTime=this.currentTime)},this.keyupHandler=t=>{this.keys[t.code]=!1,"Space"===t.code&&this.velocity.y>0&&(this.velocity.y*=.5)},this.mousemoveHandler=t=>{if(!this.mouseIsDown)return;const e=t.clientX-this.lastMouseX,n=t.clientY-this.lastMouseY;this.lastMouseX=t.clientX,this.lastMouseY=t.clientY,this.autoMoveForward&&Math.abs(e)+Math.abs(n)>2&&(this.autoMoveForward=!1),this.yaw-=e*this.lookSensitivity*100,this.pitch-=n*this.lookSensitivity*100,this.pitch=Math.max(-89,Math.min(89,this.pitch))},this.mousedownHandler=t=>{0===t.button&&(this.mouseIsDown=!0,this.lastMouseX=t.clientX,this.lastMouseY=t.clientY)},this.mouseupHandler=t=>{0===t.button&&(this.mouseIsDown=!1)},document.addEventListener("keydown",this.keydownHandler),document.addEventListener("keyup",this.keyupHandler),document.addEventListener("mousemove",this.mousemoveHandler),t.addEventListener("mousedown",this.mousedownHandler),document.addEventListener("mouseup",this.mouseupHandler)}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.mousedownHandler&&t.removeEventListener("mousedown",this.mousedownHandler),this.mouseupHandler&&document.removeEventListener("mouseup",this.mouseupHandler),this.keys={}}checkCollision(t,e){for(const n of this.collisionEntities){const o=n.getPosition(),i=n.getLocalScale();if("floor"===n._collisionMeshType)continue;let s,a,r,l,c,d;const p=n._collisionBounds;p?(l=p.center.x,c=p.center.y,d=p.center.z,s=p.halfExtents.x,a=p.halfExtents.y,r=p.halfExtents.z):(l=o.x,c=o.y,d=o.z,s=i.x/2,a=i.y/2,r=i.z/2);const h=Math.abs(t.x-l),u=Math.abs(t.y-c),m=Math.abs(t.z-d);if(h<s+e&&u<a+this.playerHeight/2&&m<r+e)return!0}return!1}resolveVoxelCollision(t,e){if(!this.voxelCollision)return!1;const n=this.tmpVec;this.worldToFile(t,n);const o=this.voxelCollision.querySphere(n.x,n.y,n.z,e);if(!o)return!1;const i=this.tmpVec2;i.set(o.x,o.y,o.z);const s=this.tmpVec;return this.fileVecToWorld(i,s),t.x+=s.x,t.y+=s.y,t.z+=s.z,!0}checkGround(t){if(this.floorEntity){const e=this.floorEntity.getPosition(),n=this.floorEntity.getLocalScale(),o=n.x/2,i=n.z/2;if(Math.abs(t.x-e.x)<o&&Math.abs(t.z-e.z)<i)return e.y}let e=null;for(const n of this.collisionEntities){const o=n.getPosition(),i=n.getLocalScale(),s=n._collisionMeshType;if("floor"===s||"plane"===s||"sphere"===s)continue;let a,r,l,c,d,p;const h=n._collisionBounds;h?(c=h.center.x,d=h.center.y,p=h.center.z,a=h.halfExtents.x,r=h.halfExtents.y,l=h.halfExtents.z):(c=o.x,d=o.y,p=o.z,a=i.x/2,r=i.y/2,l=i.z/2);const u=d+r;Math.abs(t.x-c)<a+this.collisionRadius&&Math.abs(t.z-p)<l+this.collisionRadius&&t.y>=u-this.stepHeight&&(null===e||u>e)&&(e=u)}if(this.voxelCollision){const n=this.tmpVec;this.worldToFile(t,n);const o=this.voxelCollision.findGround(n.x,n.z,n.y+this.stepHeight);if(null!==o){const t=this.tmpVec2;t.set(n.x,o,n.z);const i=this._splatEntity?this._splatMatrix.data:null;let s;s=i?i[1]*t.x+i[5]*t.y+i[9]*t.z+i[13]:-o,(null===e||s>e)&&(e=s)}}return e}update(e){if(!this.enabled)return;if(this.updateSplatTransform(),this.currentTime+=e,0!==this.joystickLookX||0!==this.joystickLookY){const t=120;this.yaw-=this.joystickLookX*t*e,this.pitch-=this.joystickLookY*t*e,this.pitch=Math.max(-89,Math.min(89,this.pitch))}let n=(this.keys.KeyD||this.keys.ArrowRight?1:0)-(this.keys.KeyA||this.keys.ArrowLeft?1:0),o=(this.keys.KeyW||this.keys.ArrowUp?1:0)-(this.keys.KeyS||this.keys.ArrowDown?1:0);n=Math.max(-1,Math.min(1,n+this.joystickMoveX)),o=Math.max(-1,Math.min(1,o+this.joystickMoveZ)),this.autoMoveForward&&0===n&&0===o&&(o=this.autoMoveSpeedFactor);const 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(o*a)),this.targetVelocity.add(this.tmpVec2.copy(this.right).mulScalar(n*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.bobOffsetY-this.landingDipAmount,l.x-=this.bobOffsetX*this.right.x,l.z-=this.bobOffsetX*this.right.z;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),this.resolveVoxelCollision(c,this.collisionRadius);const d=this.wasGroundedLastFrame,p=this.checkGround(c),h=c.y-this.playerHeight;null!==p&&h<=p+this.groundCheckDistance?(c.y=p+this.playerHeight,this.velocity.y=0,this.isGrounded=!0):(null===p||h>p+this.stepHeight)&&(this.isGrounded=!1),this.isGrounded&&(this.lastGroundedTime=this.currentTime);const u=this.currentTime-this.lastGroundedTime<=this.coyoteTime,m=this.currentTime-this.lastJumpPressTime<=this.jumpBufferTime;u&&m&&(this.velocity.y=this.jumpVelocity,this.isGrounded=!1,this.lastGroundedTime=-1/0,this.lastJumpPressTime=-1/0),this.isGrounded&&!d&&(this.landingDipAmount=Math.min(.02*Math.abs(this.previousVelocityY),$.MAX_LANDING_DIP)),this.landingDipAmount*=1-Math.min(1,e*$.LANDING_DIP_DECAY),this.landingDipAmount<.001&&(this.landingDipAmount=0);const g=Math.sqrt(this.horizontalVelocity.x*this.horizontalVelocity.x+this.horizontalVelocity.z*this.horizontalVelocity.z),f=this.isGrounded&&g>.5;if(this.headBobEnabled&&f){const t=i?$.SPRINT_BOB_FREQ:$.WALK_BOB_FREQ,n=i?$.SPRINT_BOB_AMP_Y:$.WALK_BOB_AMP_Y,o=i?$.SPRINT_BOB_AMP_X:$.WALK_BOB_AMP_X;this.bobTimer+=e*t,this.bobOffsetY=Math.sin(this.bobTimer)*n,this.bobOffsetX=Math.cos(.5*this.bobTimer)*o}else{const t=1-Math.min(1,8*e);this.bobOffsetY*=t,this.bobOffsetX*=t,Math.abs(this.bobOffsetY)<1e-4&&(this.bobOffsetY=0),Math.abs(this.bobOffsetX)<1e-4&&(this.bobOffsetX=0),0===this.bobOffsetY&&0===this.bobOffsetX&&(this.bobTimer=0)}const y=c.x+this.bobOffsetX*this.right.x,v=c.y+this.bobOffsetY-this.landingDipAmount,x=c.z+this.bobOffsetX*this.right.z;this.camera.setPosition(y,v,x),this.camera.setEulerAngles(this.pitch,this.yaw,0),this.previousVelocityY=this.velocity.y,this.wasGroundedLastFrame=this.isGrounded}destroy(){this.disable();for(const t of this.collisionEntities)t.destroy();this.collisionEntities=[],this.floorEntity=null,this.voxelCollision=null}async initVoxelCollision(t){if(t!==this._lastVoxelUrl||!this.voxelCollision)try{console.log("[CharacterController] Loading voxel collision:",t),this.voxelCollision=await B.load(t),this._lastVoxelUrl=t,console.log("[CharacterController] Voxel collision ready")}catch(t){console.warn("[CharacterController] Voxel collision load failed (falling back to primitives):",t),this.voxelCollision=null}}setSplatEntity(t){this._splatEntity=t}updateSplatTransform(){this._splatEntity&&(this._splatMatrix.copy(this._splatEntity.getWorldTransform()),this._invSplatMatrix.copy(this._splatMatrix).invert())}worldToFile(t,e){return this._splatEntity?this._invSplatMatrix.transformPoint(t,e):e.set(t.x,-t.y,-t.z),e}fileVecToWorld(t,e){if(this._splatEntity){const n=this._splatMatrix.data;e.set(n[0]*t.x+n[4]*t.y+n[8]*t.z,n[1]*t.x+n[5]*t.y+n[9]*t.z,n[2]*t.x+n[6]*t.y+n[10]*t.z)}else e.set(t.x,-t.y,-t.z);return e}get voxelCollisionInstance(){return this.voxelCollision}get collisionMeshEntities(){return this.collisionEntities}get grounded(){return this.isGrounded}getVelocity(){return this.velocity.clone()}setPosition(t,e,n){this.camera.setPosition(t,e,n)}setRotation(t,e){this.pitch=t,this.yaw=e,this.camera.setEulerAngles(t,e,0)}setJoystickMove(t,e){this.joystickMoveX=t,this.joystickMoveZ=e}setJoystickLook(t,e){this.joystickLookX=t,this.joystickLookY=e}setCollisionDebug(e){if(this._debugVisible=e,e&&!this._debugMaterial){const e=new t.StandardMaterial;e.diffuse=new t.Color(0,1,0),e.opacity=1,e.cull=t.CULLFACE_NONE,e.update(),this._debugMaterial=e}for(const t of this.collisionEntities)this._setDebugVisibility(t,e)}get collisionDebugVisible(){return this._debugVisible}_setDebugVisibility(e,n){if(e.render)if(e.render.enabled=!0,n&&this._debugMaterial)for(const t of e.render.meshInstances)this._originalMaterials.has(t.id)||this._originalMaterials.set(t.id,t.material),t.material=this._debugMaterial;else{for(const t of e.render.meshInstances){const e=this._originalMaterials.get(t.id);e&&(t.material=e)}e.render.enabled=!1}for(const o of e.children)o instanceof t.Entity&&this._setDebugVisibility(o,n)}get voxelDebugVisible(){return this._voxelDebugVisible}setVoxelDebug(e){if(this._voxelDebugVisible=e,!e)return void(this._voxelDebugEntity&&(this._voxelDebugEntity.enabled=!1));if(this._voxelDebugEntity)return void(this._voxelDebugEntity.enabled=!0);if(!this.voxelCollision)return void console.warn("[CharacterController] No voxel collision loaded, cannot show debug");const n=this.voxelCollision.collectSolidAABBs(1e5),o=n.length/4;if(0===o)return void console.warn("[CharacterController] Voxel octree has no solid voxels");console.log(`[CharacterController] Building voxel debug mesh: ${o} solid voxels`);const i=new Float32Array(24*o*3),s=[[0,0,0,1,0,0],[1,0,0,1,0,1],[1,0,1,0,0,1],[0,0,1,0,0,0],[0,1,0,1,1,0],[1,1,0,1,1,1],[1,1,1,0,1,1],[0,1,1,0,1,0],[0,0,0,0,1,0],[1,0,0,1,1,0],[1,0,1,1,1,1],[0,0,1,0,1,1]];for(let t=0;t<o;t++){const e=n[4*t],o=n[4*t+1],a=n[4*t+2],r=n[4*t+3],l=24*t*3;for(let t=0;t<12;t++){const n=s[t],c=l+6*t;i[c]=e+(2*n[0]-1)*r,i[c+1]=o+(2*n[1]-1)*r,i[c+2]=a+(2*n[2]-1)*r,i[c+3]=e+(2*n[3]-1)*r,i[c+4]=o+(2*n[4]-1)*r,i[c+5]=a+(2*n[5]-1)*r}}const a=new t.Mesh(this.app.graphicsDevice);a.setPositions(i),a.update(t.PRIMITIVE_LINES);const r=new t.StandardMaterial;r.diffuse=new t.Color(0,.8,.2),r.emissive=new t.Color(0,.8,.2),r.useLighting=!1,r.update();const l=new t.MeshInstance(a,r),c=new t.Entity("voxel-debug");c.addComponent("render",{type:"asset",meshInstances:[l],castShadows:!1,receiveShadows:!1}),this._splatEntity?this._splatEntity.addChild(c):this.app.root.addChild(c),this._voxelDebugEntity=c}clearVoxelDebug(){this._voxelDebugEntity&&(this._voxelDebugEntity.destroy(),this._voxelDebugEntity=null),this._voxelDebugVisible=!1}}$.WALK_BOB_FREQ=10,$.WALK_BOB_AMP_Y=.025,$.WALK_BOB_AMP_X=.012,$.SPRINT_BOB_FREQ=14,$.SPRINT_BOB_AMP_Y=.04,$.SPRINT_BOB_AMP_X=.02,$.LANDING_DIP_DECAY=8,$.MAX_LANDING_DIP=.15;const V="\n#define MAX_RELIGHT_LIGHTS 16\n\nuniform float uRelightFade;\nuniform vec3 uRelightAmbient;\nuniform vec3 uRelightLightPos[MAX_RELIGHT_LIGHTS];\nuniform vec3 uRelightLightColor[MAX_RELIGHT_LIGHTS];\nuniform float uRelightLightRange[MAX_RELIGHT_LIGHTS];\nuniform vec3 uRelightLightDir[MAX_RELIGHT_LIGHTS];\nuniform vec2 uRelightLightCosAngles[MAX_RELIGHT_LIGHTS];\n\nvec3 rl_gammaToLinear(vec3 c) { return pow(c, vec3(2.2)); }\nvec3 rl_linearToGamma(vec3 c) { return pow(max(c, vec3(0.0)), vec3(1.0/2.2)); }\n\nvoid applyRelighting(vec3 center, inout vec4 color) {\n if (uRelightFade <= 0.0) return;\n\n // center is effectively world-space in both paths:\n // - Unified (CopyToWorkBuffer): explicitly world-space\n // - Non-unified: model-space, but splat entities have identity transform\n vec3 worldPos = center;\n\n vec3 lightSum = vec3(0.0);\n for (int i = 0; i < MAX_RELIGHT_LIGHTS; ++i) {\n vec3 lc = uRelightLightColor[i];\n if (lc.r + lc.g + lc.b <= 0.0) continue;\n float range = uRelightLightRange[i];\n if (range <= 0.0) {\n // Directional / hemispheric: uniform contribution\n lightSum += lc;\n } else {\n // Point / spot: inverse-square falloff\n float dist = length(uRelightLightPos[i] - worldPos);\n dist = max(dist, 0.5); // Prevent singularity near light\n float atten = 1.0 / (dist * dist);\n // Soft range cutoff so light doesn't bleed infinitely\n float rangeFade = clamp(1.0 - dist / range, 0.0, 1.0);\n atten *= rangeFade;\n // Spotlight cone attenuation (point lights have dir=vec3(0), skipped)\n vec3 lightDir = uRelightLightDir[i];\n if (dot(lightDir, lightDir) > 0.0) {\n vec3 toSplat = normalize(worldPos - uRelightLightPos[i]);\n float cosAngle = dot(toSplat, lightDir);\n float cosOuter = uRelightLightCosAngles[i].y;\n float cosInner = uRelightLightCosAngles[i].x;\n float spotFade = clamp((cosAngle - cosOuter) / max(cosInner - cosOuter, 0.001), 0.0, 1.0);\n atten *= spotFade;\n }\n lightSum += lc * atten;\n }\n }\n\n vec3 linear = rl_gammaToLinear(color.rgb);\n vec3 relit = linear * (uRelightAmbient + lightSum);\n color.rgb = rl_linearToGamma(mix(linear, relit, uRelightFade));\n}\n",O="\nconst MAX_RELIGHT_LIGHTS: i32 = 16;\n\nuniform uRelightFade: f32;\nuniform uRelightAmbient: vec3f;\nuniform uRelightLightPos: array<vec3f, 16>;\nuniform uRelightLightColor: array<vec3f, 16>;\nuniform uRelightLightRange: array<f32, 16>;\nuniform uRelightLightDir: array<vec3f, 16>;\nuniform uRelightLightCosAngles: array<vec2f, 16>;\n\nfn rl_gammaToLinear(c: vec3f) -> vec3f { return pow(c, vec3f(2.2)); }\nfn rl_linearToGamma(c: vec3f) -> vec3f { return pow(max(c, vec3f(0.0)), vec3f(1.0/2.2)); }\n\nfn applyRelighting(center: vec3f, color: ptr<function, vec4f>) {\n if (uniform.uRelightFade <= 0.0) { return; }\n\n // center is effectively world-space in both paths:\n // - Unified (CopyToWorkBuffer): explicitly world-space\n // - Non-unified: model-space, but splat entities have identity transform\n let worldPos = center;\n\n var lightSum = vec3f(0.0);\n for (var i: i32 = 0; i < MAX_RELIGHT_LIGHTS; i++) {\n let lc = uniform.uRelightLightColor[i];\n if (lc.r + lc.g + lc.b <= 0.0) { continue; }\n let range = uniform.uRelightLightRange[i];\n if (range <= 0.0) {\n // Directional / hemispheric: uniform contribution\n lightSum += lc;\n } else {\n // Point / spot: inverse-square falloff\n let dist = length(uniform.uRelightLightPos[i] - worldPos);\n let safeDist = max(dist, 0.5); // Prevent singularity near light\n var atten = 1.0 / (safeDist * safeDist);\n // Soft range cutoff so light doesn't bleed infinitely\n let rangeFade = clamp(1.0 - dist / range, 0.0, 1.0);\n atten *= rangeFade;\n // Spotlight cone attenuation (point lights have dir=vec3(0), skipped)\n let lightDir = uniform.uRelightLightDir[i];\n if (dot(lightDir, lightDir) > 0.0) {\n let toSplat = normalize(worldPos - uniform.uRelightLightPos[i]);\n let cosAngle = dot(toSplat, lightDir);\n let cosOuter = uniform.uRelightLightCosAngles[i].y;\n let cosInner = uniform.uRelightLightCosAngles[i].x;\n let spotFade = clamp((cosAngle - cosOuter) / max(cosInner - cosOuter, 0.001), 0.0, 1.0);\n atten *= spotFade;\n }\n lightSum += lc * atten;\n }\n }\n\n let linear = rl_gammaToLinear((*color).rgb);\n let relit = linear * (uniform.uRelightAmbient + lightSum);\n (*color) = vec4f(rl_linearToGamma(mix(linear, relit, uniform.uRelightFade)), (*color).a);\n}\n",W=V+"\nvoid modifySplatCenter(inout vec3 center) {}\n\nvoid modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) {}\n\nvoid modifySplatColor(vec3 center, inout vec4 color) {\n applyRelighting(center, color);\n}\n",N=O+"\nfn modifySplatCenter(center: ptr<function, vec3f>) {}\n\nfn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr<function, vec4f>, scale: ptr<function, vec3f>) {}\n\nfn modifySplatColor(center: vec3f, color: ptr<function, vec4f>) {\n applyRelighting(center, color);\n}\n";let G=null;const H=16;function X(){if(G)return G;const e=t.createScript("gsplatRelighting");return Object.assign(e.prototype,{material:null,_effectInitialized:!1,_shadersNeedApplication:!1,_shaderManagedExternally:!1,_ambientArray:[1,1,1],_posArray:null,_colorArray:null,_rangeArray:null,_dirArray:null,_cosAnglesArray:null,relightFade:1,ambientR:1,ambientG:1,ambientB:1,initialize(){this.material=null,this._effectInitialized=!1,this._shadersNeedApplication=!1,this._ambientArray=[1,1,1],this._posArray=new Float32Array(48),this._colorArray=new Float32Array(48),this._rangeArray=new Float32Array(H),this._dirArray=new Float32Array(48),this._cosAnglesArray=new Float32Array(32),this.on("enable",()=>{this._shaderManagedExternally?this._discoverMaterial():this._effectInitialized?this._applyShaders():this._shadersNeedApplication=!0}),this.on("disable",()=>{this._shaderManagedExternally||this._removeShaders()}),this.entity.gsplat&&(this._effectInitialized=!0,this.enabled&&(this._shaderManagedExternally?this._discoverMaterial():this._applyShaders()))},update(t){this._effectInitialized?(!this._shaderManagedExternally&&this._shadersNeedApplication&&(this._applyShaders(),this._shadersNeedApplication=!1),(this.material||(this._shaderManagedExternally?this._discoverMaterial():this._applyShaders(),this.material))&&(this._ambientArray[0]=this.ambientR,this._ambientArray[1]=this.ambientG,this._ambientArray[2]=this.ambientB,this._setUniform("uRelightAmbient",this._ambientArray),this._setUniform("uRelightFade",this.relightFade),this.material.update())):this.entity.gsplat&&(this._effectInitialized=!0,this.enabled&&!this._shaderManagedExternally&&this._shadersNeedApplication?(this._applyShaders(),this._shadersNeedApplication=!1):this.enabled&&this._shaderManagedExternally&&!this.material&&this._discoverMaterial())},setRelightFade(t){this.relightFade=Math.max(0,Math.min(1,t))},setAmbientColor(t){const e=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(t);e&&(this.ambientR=parseInt(e[1],16)/255,this.ambientG=parseInt(e[2],16)/255,this.ambientB=parseInt(e[3],16)/255)},updateLights(t){const e=Math.min(t.length,H);for(let n=0;n<e;n++){const e=t[n];this._posArray[3*n]=e.position.x,this._posArray[3*n+1]=e.position.y,this._posArray[3*n+2]=e.position.z,this._colorArray[3*n]=e.color.r*e.intensity,this._colorArray[3*n+1]=e.color.g*e.intensity,this._colorArray[3*n+2]=e.color.b*e.intensity,this._rangeArray[n]=e.range,e.direction?(this._dirArray[3*n]=e.direction.x,this._dirArray[3*n+1]=e.direction.y,this._dirArray[3*n+2]=e.direction.z):(this._dirArray[3*n]=0,this._dirArray[3*n+1]=0,this._dirArray[3*n+2]=0),this._cosAnglesArray[2*n]=e.cosInnerAngle??1,this._cosAnglesArray[2*n+1]=e.cosOuterAngle??1}for(let t=e;t<H;t++)this._colorArray[3*t]=0,this._colorArray[3*t+1]=0,this._colorArray[3*t+2]=0,this._dirArray[3*t]=0,this._dirArray[3*t+1]=0,this._dirArray[3*t+2]=0;this._setUniform("uRelightLightPos[0]",this._posArray),this._setUniform("uRelightLightColor[0]",this._colorArray),this._setUniform("uRelightLightRange[0]",this._rangeArray),this._setUniform("uRelightLightDir[0]",this._dirArray),this._setUniform("uRelightLightCosAngles[0]",this._cosAnglesArray)},getShaderGLSL:()=>W,getShaderWGSL:()=>N,_discoverMaterial(){const t=this.entity.gsplat;if(t)if(t.unified)this.material=this.app.scene.gsplat?.material??null;else{const e=t.material;e?this.material=e:t.once("load",()=>{this.material=t.material??null})}},_applyShaders(){const t=this.entity.gsplat;if(t)if(t.unified){if(this.material=this.app.scene.gsplat?.material??null,!this.material)return void console.warn("[Relighting] Unified gsplat template material not available");this._applyShaderToMaterial(this.material)}else{const e=t.material;e?(this.material=e,this._applyShaderToMaterial(e)):t.once("load",()=>{this.material=t.material??null,this.material&&this._applyShaderToMaterial(this.material)})}},_applyShaderToMaterial(t){const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl",o="wgsl"===n?this.getShaderWGSL():this.getShaderGLSL();t.getShaderChunks(n).set("gsplatModifyVS",o),t.update()},_removeShaders(){if(!this.material)return;const t=this.app.graphicsDevice,e=t?.isWebGPU?"wgsl":"glsl";this.material.getShaderChunks(e).delete("gsplatModifyVS"),this.material.update(),this.material=null},_setUniform(t,e){this.material?.setParameter(t,e)},destroy(){this._shaderManagedExternally||this._removeShaders()}}),G=e,e}const q={get class(){return X()}};let j=null;function Y(){if(j)return j;const e=t.createScript("gsplatRevealRadial");return Object.assign(e.prototype,{effectTime:0,material:null,_effectInitialized:!1,_shadersNeedApplication:!1,_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:500,_autoEndRadius:0,bandWidth:1,initialize(){this.effectTime=0,this.material=null,this._effectInitialized=!1,this._shadersNeedApplication=!1,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",()=>{this.effectTime=0,this._effectInitialized?this._applyShaders():this._shadersNeedApplication=!0}),this.on("disable",()=>{this._removeShaders()}),this.entity.gsplat&&(this._effectInitialized=!0,this._detectSceneRadius(),this.enabled&&this._applyShaders())},update(t){if(this._effectInitialized){if(this._shadersNeedApplication&&(this._applyShaders(),this.material&&(this._shadersNeedApplication=!1)),this.material){if(this.effectTime+=t,this._isEffectComplete())return console.log("[RevealEffect] Effect complete, disabling"),void(this.enabled=!1);this._updateUniforms(),this.material.update()}}else this.entity.gsplat&&(this._effectInitialized=!0,this._detectSceneRadius(),this.enabled&&this._shadersNeedApplication&&(this._applyShaders(),this._shadersNeedApplication=!1))},_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);const t=this._autoEndRadius>0?this._autoEndRadius:this.endRadius;this._setUniform("uEndRadius",t),this._setUniform("uBandWidth",this.bandWidth)},_detectSceneRadius(){try{const t=this.entity.gsplat,e=t&&t.instance,n=e?.meshInstance??e?.meshInstances?.[0],o=n?.aabb;if(o){const t=o.halfExtents,e=Math.sqrt(t.x*t.x+t.y*t.y+t.z*t.z);this._autoEndRadius=1.2*e}}catch(t){}},_getCompletionTime(){const t=this._autoEndRadius>0?this._autoEndRadius:this.endRadius,e=this.delay;if(0===this.acceleration)return e+t/this.speed;const n=this.speed*this.speed+2*this.acceleration*t;if(n<0)return 1/0;return e+(-this.speed+Math.sqrt(n))/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;\nuniform float uBandWidth;\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 modifySplatCenter(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 * uBandWidth;\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 * uBandWidth && g_liftTime > 0.0) {\n float normalizedDist = distToLiftWave / uBandWidth;\n float liftAmount = (1.0 - normalizedDist) * sin(normalizedDist * 3.14159);\n center.y += liftAmount * uOscillationIntensity * 0.9;\n }\n}\n\nvoid modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) {\n\n // Store original scale for shape preservation\n vec3 origScale = scale;\n float origSize = gsplatGetSizeFromScale(scale);\n\n // Determine scale factor and phase\n float scaleFactor;\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 scaleFactor = (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 scale = vec3(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 scaleFactor = (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 scaleFactor = 0.1;\n }\n\n // Apply scale\n if (scaleFactor >= 1.0) {\n // Fully revealed: original shape and size (no-op)\n return;\n } else if (isLiftWave) {\n // Lift wave: lerp from spherical dots to original shape\n float t = (scaleFactor - 0.1) * 1.111111; // normalize [0.1, 1.0] to [0, 1]\n float dotSize = scaleFactor * 0.05;\n float finalSize = mix(dotSize, origSize, t);\n\n // Lerp between spherical (uniform) and scaled original\n vec3 sphericalScale = vec3(finalSize);\n vec3 scaledOrig = origScale * scaleFactor;\n scale = mix(sphericalScale, scaledOrig, t);\n } else {\n // Dot phase: spherical with absolute size, but don't make small splats larger\n float targetSize = min(scaleFactor * 0.05, origSize);\n gsplatMakeSpherical(scale, targetSize);\n }\n}\n\nvoid revealColorEffect(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 * uBandWidth && g_dist <= g_liftWavePos + 0.5 * uBandWidth) {\n float distToLift = abs(g_dist - g_liftWavePos);\n float liftIntensity = smoothstep(1.5 * uBandWidth, 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 * uBandWidth)) {\n float distToDot = abs(g_dist - g_dotWavePos);\n float dotIntensity = smoothstep(1.0 * uBandWidth, 0.0, distToDot);\n color.rgb += uDotTint * dotIntensity;\n }\n}\n\nvoid modifySplatColor(vec3 center, inout vec4 color) {\n // EFFECT_HOOK\n revealColorEffect(center, color);\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;\nuniform uBandWidth: 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 modifySplatCenter(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 * uniform.uBandWidth;\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 * uniform.uBandWidth && g_liftTime > 0.0) {\n let normalizedDist = distToLiftWave / uniform.uBandWidth;\n let liftAmount = (1.0 - normalizedDist) * sin(normalizedDist * 3.14159);\n (*center).y += liftAmount * uniform.uOscillationIntensity * 0.9;\n }\n}\n\nfn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr<function, vec4f>, scale: ptr<function, vec3f>) {\n // Store original scale for shape preservation\n let origScale = *scale;\n let origSize = gsplatGetSizeFromScale(*scale);\n\n // Determine scale factor and phase\n var scaleFactor: 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 scaleFactor = 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 *scale = vec3f(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 scaleFactor = 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 scaleFactor = 0.1;\n }\n\n // Apply scale\n if (scaleFactor >= 1.0) {\n // Fully revealed: original shape and size (no-op)\n return;\n } else if (isLiftWave) {\n // Lift wave: lerp from spherical dots to original shape\n let t = (scaleFactor - 0.1) * 1.111111; // normalize [0.1, 1.0] to [0, 1]\n let dotSize = scaleFactor * 0.05;\n let finalSize = mix(dotSize, origSize, t);\n\n // Lerp between spherical (uniform) and scaled original\n let sphericalScale = vec3f(finalSize);\n let scaledOrig = origScale * scaleFactor;\n *scale = mix(sphericalScale, scaledOrig, t);\n } else {\n // Dot phase: spherical with absolute size, but don't make small splats larger\n let targetSize = min(scaleFactor * 0.05, origSize);\n gsplatMakeSpherical(scale, targetSize);\n }\n}\n\nfn revealColorEffect(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 * uniform.uBandWidth && g_dist <= g_liftWavePos + 0.5 * uniform.uBandWidth) {\n let distToLift = abs(g_dist - g_liftWavePos);\n let liftIntensity = smoothstep(1.5 * uniform.uBandWidth, 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 * uniform.uBandWidth)) {\n let distToDot = abs(g_dist - g_dotWavePos);\n let dotIntensity = smoothstep(1.0 * uniform.uBandWidth, 0.0, distToDot);\n (*color) = vec4f((*color).rgb + uniform.uDotTint * dotIntensity, (*color).a);\n }\n}\n\nfn modifySplatColor(center: vec3f, color: ptr<function, vec4f>) {\n // EFFECT_HOOK\n revealColorEffect(center, color);\n}\n",_applyShaders(){const t=this.entity.gsplat;if(t)if(t.unified){if(this.material=this.app.scene.gsplat?.material??null,!this.material)return void console.warn("[RevealEffect] Unified gsplat template material not available");this._applyShaderToMaterial(this.material)}else{const e=t.material;e?(this.material=e,this._applyShaderToMaterial(e)):t.once("load",()=>{this.material=t.material??null,this.material&&this._applyShaderToMaterial(this.material)})}},_applyShaderToMaterial(t){const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl";let o="wgsl"===n?this.getShaderWGSL():this.getShaderGLSL();const i=this.entity.script&&this.entity.script.gsplatRelighting;if(i&&i.enabled){i._shaderManagedExternally=!0;o=("wgsl"===n?O:V)+"\n"+o.replace("// EFFECT_HOOK","applyRelighting(center, color);"),console.log("[RevealEffect] Combined shader with relighting")}t.getShaderChunks(n).set("gsplatModifyVS",o),t.update()},_removeShaders(){if(!this.material)return;const t=this.entity.script&&this.entity.script.gsplatRelighting;if(t&&t.enabled){t.material=this.material;const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl",o="wgsl"===n?t.getShaderWGSL():t.getShaderGLSL();return this.material.getShaderChunks(n).set("gsplatModifyVS",o),this.material.update(),t._shaderManagedExternally=!1,this.material=null,void console.log("[RevealEffect] Handed material back to relighting")}const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl";this.material.getShaderChunks(n).delete("gsplatModifyVS"),this.material.update(),this.material=null},_setUniform(t,e){this.material?.setParameter(t,e)},destroy(){this._removeShaders()}}),j=e,e}let Z=null;let Q=null;let K=null;function J(){if(K)return K;const e=t.createScript("gsplatRevealBloom");return Object.assign(e.prototype,{effectTime:0,material:null,_effectInitialized:!1,_shadersNeedApplication:!1,_bloomTintArray:[0,0,0],_settleTintArray:[0,0,0],_centerArray:[0,0,0],center:null,speed:25,acceleration:8,delay:0,oscillationIntensity:.2,dotTint:null,waveTint:null,endRadius:500,_autoEndRadius:0,initialize(){this.effectTime=0,this.material=null,this._effectInitialized=!1,this._shadersNeedApplication=!1,this._bloomTintArray=[0,0,0],this._settleTintArray=[0,0,0],this._centerArray=[0,0,0],this.center||(this.center=new t.Vec3(0,0,0)),this.dotTint||(this.dotTint=new t.Color(1,.6,.15)),this.waveTint||(this.waveTint=new t.Color(.2,.5,1)),this.on("enable",()=>{this.effectTime=0,this._effectInitialized?this._applyShaders():this._shadersNeedApplication=!0}),this.on("disable",()=>{this._removeShaders()}),this.entity.gsplat&&(this._effectInitialized=!0,this._detectSceneRadius(),this.enabled&&this._applyShaders())},update(t){if(this._effectInitialized){if(this._shadersNeedApplication&&(this._applyShaders(),this.material&&(this._shadersNeedApplication=!1)),this.material){if(this.effectTime+=t,this._isEffectComplete())return console.log("[BloomReveal] Effect complete, disabling"),void(this.enabled=!1);this._updateUniforms(),this.material.update()}}else this.entity.gsplat&&(this._effectInitialized=!0,this._detectSceneRadius(),this.enabled&&this._shadersNeedApplication&&(this._applyShaders(),this.material&&(this._shadersNeedApplication=!1)))},_detectSceneRadius(){try{const t=this.entity.gsplat,e=t&&t.instance,n=e?.meshInstance??e?.meshInstances?.[0],o=n?.aabb;if(o){const t=o.halfExtents,e=Math.sqrt(t.x*t.x+t.y*t.y+t.z*t.z);this._autoEndRadius=1.2*e}}catch(t){}},_getCompletionTime(){const t=this._autoEndRadius>0?this._autoEndRadius:this.endRadius;if(0===this.acceleration)return t/this.speed+.5;const e=this.speed*this.speed+2*this.acceleration*t;if(e<0)return 1/0;return(-this.speed+Math.sqrt(e))/this.acceleration+.5},_isEffectComplete(){return this.effectTime>=this._getCompletionTime()},_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("uWaveSpeed",this.speed),this._setUniform("uAcceleration",this.acceleration),this._setUniform("uBandWidth",3),this._setUniform("uOvershoot",1+Math.max(0,this.oscillationIntensity)),this._bloomTintArray[0]=this.dotTint.r,this._bloomTintArray[1]=this.dotTint.g,this._bloomTintArray[2]=this.dotTint.b,this._setUniform("uBloomTint",this._bloomTintArray),this._settleTintArray[0]=this.waveTint.r,this._settleTintArray[1]=this.waveTint.g,this._settleTintArray[2]=this.waveTint.b,this._setUniform("uSettleTint",this._settleTintArray)},getShaderGLSL:()=>"\nuniform float uTime;\nuniform vec3 uCenter;\nuniform float uWaveSpeed;\nuniform float uAcceleration;\nuniform float uBandWidth;\nuniform float uOvershoot; // e.g. 1.2 = 20% overshoot\nuniform vec3 uBloomTint; // warm color at wavefront\nuniform vec3 uSettleTint; // cool color trailing behind\n\n// Shared per-splat state\nfloat g_dist;\nfloat g_bloom; // <0.01 = hidden, 0..1 = blooming, 1..overshoot = overshoot, settled = 1.0\n\nfloat hash(vec3 p) {\n return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453);\n}\n\nvoid initBloom(vec3 center) {\n g_dist = length(center - uCenter);\n\n // Wave position with acceleration\n float wavePos = uWaveSpeed * uTime + 0.5 * uAcceleration * uTime * uTime;\n\n // Per-splat jitter so the wavefront isn't a perfect circle\n float jitter = (hash(center) - 0.5) * uBandWidth * 0.4;\n float effectiveDist = g_dist + jitter;\n\n if (effectiveDist > wavePos + uBandWidth * 0.5) {\n // Ahead of wave: hidden\n g_bloom = 0.0;\n } else if (effectiveDist > wavePos - uBandWidth * 0.5) {\n // Inside the bloom band: transitioning 0 → 1\n g_bloom = smoothstep(wavePos + uBandWidth * 0.5, wavePos - uBandWidth * 0.5, effectiveDist);\n } else {\n // Behind the wave: bloom complete, calculate overshoot settle\n float distBehind = (wavePos - uBandWidth * 0.5) - effectiveDist;\n // Time since this splat's bloom completed (approximate)\n float settleTime = distBehind / max(uWaveSpeed + uAcceleration * uTime, 1.0);\n // Overshoot: peaks at settle start, decays to 1.0 quickly\n float overshootDecay = exp(-settleTime * 6.0);\n g_bloom = 1.0 + (uOvershoot - 1.0) * overshootDecay;\n }\n}\n\nvoid modifySplatCenter(inout vec3 center) {\n initBloom(center);\n}\n\nvoid modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) {\n if (g_bloom < 0.01) {\n scale = vec3(0.0);\n return;\n }\n\n // Fully settled\n if (g_bloom >= 0.99 && g_bloom <= 1.01) {\n return;\n }\n\n float origSize = gsplatGetSizeFromScale(scale);\n\n if (g_bloom <= 1.0) {\n // Bloom phase: transition from dot to full size\n float t = g_bloom;\n\n if (t < 0.15) {\n // Tiny spherical spark\n float dotSize = min(t * 0.4 * origSize, origSize);\n gsplatMakeSpherical(scale, dotSize);\n } else if (t < 0.5) {\n // Growing spherical dot\n float size = mix(0.06, 0.5, (t - 0.15) / 0.35) * origSize;\n gsplatMakeSpherical(scale, min(size, origSize));\n } else {\n // Transitioning from spherical to original shape\n float morphT = (t - 0.5) / 0.5; // 0→1\n float sizeFactor = mix(0.5, 1.0, morphT);\n scale *= sizeFactor;\n }\n } else {\n // Overshoot phase: slightly larger than original\n scale *= g_bloom;\n }\n}\n\nvoid bloomColorEffect(vec3 center, inout vec4 color) {\n if (g_bloom < 0.01) return;\n\n if (g_bloom <= 1.0) {\n // Bloom phase: bright tint, strongest at wavefront (g_bloom 0.2-0.6)\n float waveFrontIntensity = sin(clamp(g_bloom, 0.0, 1.0) * 3.14159);\n color.rgb += uBloomTint * waveFrontIntensity * 0.5;\n } else if (g_bloom > 1.005) {\n // Overshoot phase: brief cool flash as splat settles\n float overshootAmount = (g_bloom - 1.0) / max(uOvershoot - 1.0, 0.01);\n color.rgb += uSettleTint * overshootAmount * 0.3;\n }\n}\n\nvoid modifySplatColor(vec3 center, inout vec4 color) {\n // EFFECT_HOOK\n bloomColorEffect(center, color);\n}\n",getShaderWGSL:()=>"\nuniform uTime: f32;\nuniform uCenter: vec3f;\nuniform uWaveSpeed: f32;\nuniform uAcceleration: f32;\nuniform uBandWidth: f32;\nuniform uOvershoot: f32;\nuniform uBloomTint: vec3f;\nuniform uSettleTint: vec3f;\n\nvar<private> g_dist: f32;\nvar<private> g_bloom: f32;\n\nfn hash(p: vec3f) -> f32 {\n return fract(sin(dot(p, vec3f(127.1, 311.7, 74.7))) * 43758.5453);\n}\n\nfn initBloom(center: vec3f) {\n g_dist = length(center - uniform.uCenter);\n\n let wavePos = uniform.uWaveSpeed * uniform.uTime + 0.5 * uniform.uAcceleration * uniform.uTime * uniform.uTime;\n\n let jitter = (hash(center) - 0.5) * uniform.uBandWidth * 0.4;\n let effectiveDist = g_dist + jitter;\n\n if (effectiveDist > wavePos + uniform.uBandWidth * 0.5) {\n g_bloom = 0.0;\n } else if (effectiveDist > wavePos - uniform.uBandWidth * 0.5) {\n g_bloom = smoothstep(wavePos + uniform.uBandWidth * 0.5, wavePos - uniform.uBandWidth * 0.5, effectiveDist);\n } else {\n let distBehind = (wavePos - uniform.uBandWidth * 0.5) - effectiveDist;\n let settleTime = distBehind / max(uniform.uWaveSpeed + uniform.uAcceleration * uniform.uTime, 1.0);\n let overshootDecay = exp(-settleTime * 6.0);\n g_bloom = 1.0 + (uniform.uOvershoot - 1.0) * overshootDecay;\n }\n}\n\nfn modifySplatCenter(center: ptr<function, vec3f>) {\n initBloom(*center);\n}\n\nfn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr<function, vec4f>, scale: ptr<function, vec3f>) {\n if (g_bloom < 0.01) {\n *scale = vec3f(0.0);\n return;\n }\n\n if (g_bloom >= 0.99 && g_bloom <= 1.01) {\n return;\n }\n\n let origSize = gsplatGetSizeFromScale(*scale);\n\n if (g_bloom <= 1.0) {\n let t = g_bloom;\n\n if (t < 0.15) {\n let dotSize = min(t * 0.4 * origSize, origSize);\n gsplatMakeSpherical(scale, dotSize);\n } else if (t < 0.5) {\n let size = mix(0.06, 0.5, (t - 0.15) / 0.35) * origSize;\n gsplatMakeSpherical(scale, min(size, origSize));\n } else {\n let morphT = (t - 0.5) / 0.5;\n let sizeFactor = mix(0.5, 1.0, morphT);\n *scale *= sizeFactor;\n }\n } else {\n *scale *= g_bloom;\n }\n}\n\nfn bloomColorEffect(center: vec3f, color: ptr<function, vec4f>) {\n if (g_bloom < 0.01) {\n return;\n }\n\n if (g_bloom <= 1.0) {\n let waveFrontIntensity = sin(clamp(g_bloom, 0.0, 1.0) * 3.14159);\n (*color) = vec4f((*color).rgb + uniform.uBloomTint * waveFrontIntensity * 0.5, (*color).a);\n } else if (g_bloom > 1.005) {\n let overshootAmount = (g_bloom - 1.0) / max(uniform.uOvershoot - 1.0, 0.01);\n (*color) = vec4f((*color).rgb + uniform.uSettleTint * overshootAmount * 0.3, (*color).a);\n }\n}\n\nfn modifySplatColor(center: vec3f, color: ptr<function, vec4f>) {\n // EFFECT_HOOK\n bloomColorEffect(center, color);\n}\n",_applyShaders(){const t=this.entity.gsplat;if(t)if(t.unified){if(this.material=this.app.scene.gsplat?.material??null,!this.material)return void(this._shadersNeedApplication=!0);this._applyShaderToMaterial(this.material)}else{const e=t.material;e?(this.material=e,this._applyShaderToMaterial(e)):this._shadersNeedApplication=!0}},_applyShaderToMaterial(t){const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl";let o="wgsl"===n?this.getShaderWGSL():this.getShaderGLSL();const i=this.entity.script&&this.entity.script.gsplatRelighting;if(i&&i.enabled){i._shaderManagedExternally=!0;o=("wgsl"===n?O:V)+"\n"+o.replace("// EFFECT_HOOK","applyRelighting(center, color);")}t.getShaderChunks(n).set("gsplatModifyVS",o),t.update()},_removeShaders(){if(!this.material)return;const t=this.entity.script&&this.entity.script.gsplatRelighting;if(t&&t.enabled){t.material=this.material;const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl",o="wgsl"===n?t.getShaderWGSL():t.getShaderGLSL();return this.material.getShaderChunks(n).set("gsplatModifyVS",o),this.material.update(),t._shaderManagedExternally=!1,void(this.material=null)}const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl";this.material.getShaderChunks(n).delete("gsplatModifyVS"),this.material.update(),this.material=null},_setUniform(t,e){this.material?.setParameter(t,e)},destroy(){this._removeShaders()}}),K=e,e}const tt={fast:{speed:10,acceleration:2,delay:.5,oscillationIntensity:.1,dotTint:{r:0,g:1,b:1},waveTint:{r:1,g:.5,b:0},endRadius:500},medium:{speed:5,acceleration:0,delay:2,oscillationIntensity:.2,dotTint:{r:0,g:1,b:1},waveTint:{r:1,g:.5,b:0},endRadius:500},slow:{speed:3,acceleration:0,delay:3,oscillationIntensity:.25,dotTint:{r:0,g:1,b:1},waveTint:{r:1,g:.5,b:0},endRadius:500}},et={fast:{speed:8,acceleration:25,delay:0,oscillationIntensity:.15,dotTint:{r:1,g:.6,b:.15},waveTint:{r:.2,g:.5,b:1},endRadius:500},medium:{speed:5,acceleration:18,delay:0,oscillationIntensity:.2,dotTint:{r:1,g:.6,b:.15},waveTint:{r:.2,g:.5,b:1},endRadius:500},slow:{speed:3,acceleration:10,delay:0,oscillationIntensity:.25,dotTint:{r:1,g:.6,b:.15},waveTint:{r:.2,g:.5,b:1},endRadius:500}};function nt(t,e="bloom"){if("none"===t)return;return("bloom"===e?et:tt)[t]}var ot,it={},st={},at={};function rt(){if(ot)return at;ot=1,Object.defineProperty(at,"__esModule",{value:!0}),at.loop=at.conditional=at.parse=void 0;at.parse=function t(e,n){var o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:o;if(Array.isArray(n))n.forEach(function(n){return t(e,n,o,i)});else if("function"==typeof n)n(e,o,i,t);else{var s=Object.keys(n)[0];Array.isArray(n[s])?(i[s]={},t(e,n[s],o,i[s])):i[s]=n[s](e,o,i,t)}return o};at.conditional=function(t,e){return function(n,o,i,s){e(n,o,i)&&s(n,t,o,i)}};return at.loop=function(t,e){return function(n,o,i,s){for(var a=[],r=n.pos;e(n,o,i);){var l={};if(s(n,t,o,l),n.pos===r)break;r=n.pos,a.push(l)}return a}},at}var lt,ct,dt={};function pt(){if(lt)return dt;lt=1,Object.defineProperty(dt,"__esModule",{value:!0}),dt.readBits=dt.readArray=dt.readUnsigned=dt.readString=dt.peekBytes=dt.readBytes=dt.peekByte=dt.readByte=dt.buildStream=void 0;dt.buildStream=function(t){return{data:t,pos:0}};var t=function(){return function(t){return t.data[t.pos++]}};dt.readByte=t;dt.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)}};dt.readBytes=e;dt.peekBytes=function(t){return function(e){return e.data.subarray(e.pos,e.pos+t)}};dt.readString=function(t){return function(n){return Array.from(e(t)(n)).map(function(t){return String.fromCharCode(t)}).join("")}};dt.readUnsigned=function(t){return function(n){var o=e(2)(n);return t?(o[1]<<8)+o[0]:(o[0]<<8)+o[1]}};dt.readArray=function(t,n){return function(o,i,s){for(var a="function"==typeof n?n(o,i,s):n,r=e(t),l=new Array(a),c=0;c<a;c++)l[c]=r(o);return l}};return dt.readBits=function(t){return function(e){for(var n=function(t){return t.data[t.pos++]}(e),o=new Array(8),i=0;i<8;i++)o[7-i]=!!(n&1<<i);return Object.keys(t).reduce(function(e,n){var i=t[n];return i.length?e[n]=function(t,e,n){for(var o=0,i=0;i<n;i++)o+=t[e+i]&&Math.pow(2,n-i-1);return o}(o,i.index,i.length):e[n]=o[i.index],e},{})}},dt}var ht,ut={};var mt,gt,ft={};var yt=function(){if(gt)return it;gt=1,Object.defineProperty(it,"__esModule",{value:!0}),it.decompressFrames=it.decompressFrame=it.parseGIF=void 0;var t,e=(ct||(ct=1,function(t){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var e=rt(),n=pt(),o={blocks:function(t){for(var e=[],o=t.data.length,i=0,s=(0,n.readByte)()(t);0!==s&&s;s=(0,n.readByte)()(t)){if(t.pos+s>=o){var a=o-t.pos;e.push((0,n.readBytes)(a)(t)),i+=a;break}e.push((0,n.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,n.readBytes)(2)},{byteSize:(0,n.readByte)()},{extras:(0,n.readBits)({future:{index:0,length:3},disposal:{index:3,length:3},userInput:{index:6},transparentColorGiven:{index:7}})},{delay:(0,n.readUnsigned)(!0)},{transparentColorIndex:(0,n.readByte)()},{terminator:(0,n.readByte)()}]},function(t){var e=(0,n.peekBytes)(2)(t);return 33===e[0]&&249===e[1]}),s=(0,e.conditional)({image:[{code:(0,n.readByte)()},{descriptor:[{left:(0,n.readUnsigned)(!0)},{top:(0,n.readUnsigned)(!0)},{width:(0,n.readUnsigned)(!0)},{height:(0,n.readUnsigned)(!0)},{lct:(0,n.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,n.readArray)(3,function(t,e,n){return Math.pow(2,n.descriptor.lct.size+1)})},function(t,e,n){return n.descriptor.lct.exists}),{data:[{minCodeSize:(0,n.readByte)()},o]}]},function(t){return 44===(0,n.peekByte)()(t)}),a=(0,e.conditional)({text:[{codes:(0,n.readBytes)(2)},{blockSize:(0,n.readByte)()},{preData:function(t,e,o){return(0,n.readBytes)(o.text.blockSize)(t)}},o]},function(t){var e=(0,n.peekBytes)(2)(t);return 33===e[0]&&1===e[1]}),r=(0,e.conditional)({application:[{codes:(0,n.readBytes)(2)},{blockSize:(0,n.readByte)()},{id:function(t,e,o){return(0,n.readString)(o.blockSize)(t)}},o]},function(t){var e=(0,n.peekBytes)(2)(t);return 33===e[0]&&255===e[1]}),l=(0,e.conditional)({comment:[{codes:(0,n.readBytes)(2)},o]},function(t){var e=(0,n.peekBytes)(2)(t);return 33===e[0]&&254===e[1]}),c=[{header:[{signature:(0,n.readString)(3)},{version:(0,n.readString)(3)}]},{lsd:[{width:(0,n.readUnsigned)(!0)},{height:(0,n.readUnsigned)(!0)},{gct:(0,n.readBits)({exists:{index:0},resolution:{index:1,length:3},sort:{index:4},size:{index:5,length:3}})},{backgroundColorIndex:(0,n.readByte)()},{pixelAspectRatio:(0,n.readByte)()}]},(0,e.conditional)({gct:(0,n.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,n.peekByte)()(t);return 33===e||44===e})}];t.default=c}(st)),(t=st)&&t.__esModule?t:{default:t}),n=rt(),o=pt(),i=(ht||(ht=1,Object.defineProperty(ut,"__esModule",{value:!0}),ut.deinterlace=void 0,ut.deinterlace=function(t,e){for(var n=new Array(t.length),o=t.length/e,i=function(o,i){var s=t.slice(i*e,(i+1)*e);n.splice.apply(n,[o*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<o;c+=a[l])i(c,r),r++;return n}),ut),s=(mt||(mt=1,Object.defineProperty(ft,"__esModule",{value:!0}),ft.lzw=void 0,ft.lzw=function(t,e,n){var o,i,s,a,r,l,c,d,p,h,u,m,g,f,y,v,x=4096,b=n,w=new Array(n),S=new Array(x),_=new Array(x),M=new Array(4097);for(r=1+(i=1<<(h=t)),o=i+2,c=-1,s=(1<<(a=h+1))-1,d=0;d<i;d++)S[d]=0,_[d]=d;for(u=m=g=f=y=v=0,p=0;p<b;){if(0===f){if(m<a){u+=e[v]<<m,m+=8,v++;continue}if(d=u&s,u>>=a,m-=a,d>o||d==r)break;if(d==i){s=(1<<(a=h+1))-1,o=i+2,c=-1;continue}if(-1==c){M[f++]=_[d],c=d,g=d;continue}for(l=d,d==o&&(M[f++]=g,d=c);d>i;)M[f++]=_[d],d=S[d];g=255&_[d],M[f++]=g,o<x&&(S[o]=c,_[o]=g,0===(++o&s)&&o<x&&(a++,s+=o)),c=l}f--,w[y++]=M[f],p++}for(p=y;p<b;p++)w[p]=0;return w}),ft);it.parseGIF=function(t){var i=new Uint8Array(t);return(0,n.parse)((0,o.buildStream)(i),e.default)};var a=function(t,e,n){if(t.image){var o=t.image,a=o.descriptor.width*o.descriptor.height,r=(0,s.lzw)(o.data.minCodeSize,o.data.blocks,a);o.descriptor.lct.interlaced&&(r=(0,i.deinterlace)(r,o.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 o.descriptor.lct&&o.descriptor.lct.exists?l.colorTable=o.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)),n&&(l.patch=function(t){for(var e=t.pixels.length,n=new Uint8ClampedArray(4*e),o=0;o<e;o++){var i=4*o,s=t.pixels[o],a=t.colorTable[s]||[0,0,0];n[i]=a[0],n[i+1]=a[1],n[i+2]=a[2],n[i+3]=s!==t.transparentIndex?255:0}return n}(l)),l}console.warn("gif frame does not have associated image.")};return it.decompressFrame=a,it.decompressFrames=function(t,e){return t.frames.filter(function(t){return t.image}).map(function(n){return a(n,t.gct,e)})},it}();class vt{constructor(t,e,n={}){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=n,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 n=await e.arrayBuffer(),o=yt.parseGIF(n);if(this.frames=yt.decompressFrames(o,!0),0===this.frames.length)throw new Error("GIF has no frames");this.gifWidth=o.lsd.width,this.gifHeight=o.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],n=t>0?this.frames[t-1]:null;n&&2===n.disposalType&&this.ctx.clearRect(n.dims.left,n.dims.top,n.dims.width,n.dims.height);const o=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(o,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}}function xt(t,e){return t?{x:t._x??t.x??e.x,y:t._y??t.y??e.y,z:t._z??t.z??e.z}:e}function bt(t){return Math.abs(t)<1e-10?0:t}class wt{constructor(e){this.meshes=new Map,this.onUpdate=()=>{const e=function(t){const e=t.root.findComponent("camera");return e?e.entity:null}(this.app);if(!e?.camera)return;const n=e.camera,o=this.canvas.clientHeight,i=n.projectionMatrix.data[5]*(o/2);this.overlayContainer.style.perspective=`${i}px`;const s=n.viewMatrix.data;var a;this.cameraEl.style.transform=`translateZ(${i}px) ${a=s,`matrix3d(${bt(a[0])},${bt(-a[1])},${bt(a[2])},${bt(a[3])},${bt(a[4])},${bt(-a[5])},${bt(a[6])},${bt(a[7])},${bt(a[8])},${bt(-a[9])},${bt(a[10])},${bt(a[11])},${bt(a[12])},${bt(-a[13])},${bt(a[14])},${bt(a[15])})`}`;const r=e.getPosition();this.meshes.forEach(e=>{if(e.config.billboard){if(e.entity._billboardActive??!0){const n=e.entity.getLocalScale().clone();e.entity.lookAt(r);const o=e.entity.getRotation().clone(),i=(new t.Quat).setFromEulerAngles(0,180,0);e.entity.setRotation((new t.Quat).mul2(o,i)),e.entity.setLocalScale(n)}}if(!e.entity.enabled)return void(e.htmlElement.style.display="none");e.htmlElement.style.display="";const n=e.entity.getWorldTransform().data;e.htmlElement.style.transform=`translate(-50%, -50%) ${function(t){return`matrix3d(${bt(t[0])},${bt(t[1])},${bt(t[2])},${bt(t[3])},${bt(-t[4])},${bt(-t[5])},${bt(-t[6])},${bt(-t[7])},${bt(t[8])},${bt(t[9])},${bt(t[10])},${bt(t[11])},${bt(t[12])},${bt(t[13])},${bt(t[14])},${bt(t[15])})`}(n)} scale(${bt(1/e.pixelWidth)}, ${bt(1/e.pixelHeight)})`})},this.app=e,this.canvas=e.graphicsDevice.canvas,this.overlayContainer=document.createElement("div"),Object.assign(this.overlayContainer.style,{position:"absolute",top:"0",left:"0",width:"100%",height:"100%",overflow:"hidden",pointerEvents:"none",zIndex:"1"}),this.cameraEl=document.createElement("div"),Object.assign(this.cameraEl.style,{position:"absolute",left:"50%",top:"50%",width:"0",height:"0",transformStyle:"preserve-3d"}),this.overlayContainer.appendChild(this.cameraEl);const n=this.canvas.parentElement;n&&("static"===getComputedStyle(n).position&&(n.style.position="relative"),n.appendChild(this.overlayContainer)),e.on("update",this.onUpdate,this),console.log("[HtmlMeshManager] CSS3D overlay initialised")}createMesh(t){let e=t.width||512,n=t.height||512;e<4&&(e=512),n<4&&(n=512),e=Math.round(e),n=Math.round(n);const o=e/n,i=xt(t.scale,{x:1,y:1,z:1}),s=i.x*o,a=i.y,r=this.createEntity(t,s,a),l=this.createDomElement(t,e,n);this.cameraEl.appendChild(l);const c={entity:r,htmlElement:l,config:t,pixelWidth:e,pixelHeight:n,destroy:()=>this.destroyMesh(t.id),update:()=>{}};return this.meshes.set(t.id,c),console.log(`[HtmlMeshManager] Created CSS3D mesh: ${t.id}, pixels=${e}x${n}, world=${s.toFixed(2)}x${a.toFixed(2)}`),c}destroyMesh(t){const e=this.meshes.get(t);e&&(e.entity.destroy(),e.htmlElement.remove(),this.meshes.delete(t))}getMesh(t){return this.meshes.get(t)}getAllMeshes(){return this.meshes}updateVisibility(t,e){this.meshes.forEach(n=>{const o=n.config;if(o.visibilityRange){const i=o.visibilityRange;let s=!0;"percentage"===i.type?s=t>=i.start&&t<=i.end:"waypoint"===i.type&&(s=e>=i.start&&e<=i.end),n.entity.enabled=s}if(o.billboard&&o.billboardRange){const i=o.billboardRange;let s=!1;"percentage"===i.type?s=t>=i.start&&t<=i.end:"waypoint"===i.type&&(s=e>=i.start&&e<=i.end),n.entity._billboardActive=s}else o.billboard&&(n.entity._billboardActive=!0)})}updateMeshTexture(t){}destroy(){this.meshes.forEach((t,e)=>this.destroyMesh(e)),this.app.off("update",this.onUpdate,this),this.overlayContainer.remove()}createEntity(e,n,o){const i=new t.Entity(`htmlMesh-${e.id}`),s=xt(e.position,{x:0,y:0,z:0});i.setPosition(s.x,s.y,-s.z);const a=xt(e.rotation,{x:0,y:0,z:0}),r=a.y/2,l=a.x/2,c=a.z/2,d=Math.cos(r),p=Math.sin(r),h=Math.cos(l),u=Math.sin(l),m=Math.cos(c),g=Math.sin(c),f=d*h*m+p*u*g,y=d*u*m+p*h*g,v=p*h*m-d*u*g,x=d*h*g-p*u*m;return i.setRotation(-y,-v,x,f),i.setLocalScale(n,o,1),this.app.root.addChild(i),i}createDomElement(t,e,n){const o=document.createElement("div");o.id=`html-mesh-${t.id}`,Object.assign(o.style,{position:"absolute",width:`${e}px`,height:`${n}px`,transformStyle:"preserve-3d",backfaceVisibility:"hidden",pointerEvents:"auto",overflow:"hidden",opacity:String(t.opacity??1)});const i=t.contentType||"html",s=t.iframeUrl,a=t.html||t.htmlContent||"";if("iframe"===i&&s){const t=document.createElement("iframe");t.src=s,Object.assign(t.style,{width:"100%",height:"100%",border:"none",display:"block",background:"#fff"}),t.setAttribute("allow","fullscreen; autoplay; encrypted-media"),t.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-popups allow-presentation"),o.appendChild(t)}else if(a){if(t.css){const e=document.createElement("style");e.textContent=t.css,o.appendChild(e)}const e=document.createElement("div");e.style.width="100%",e.style.height="100%",e.innerHTML=a,o.appendChild(e)}else{Object.assign(o.style,{background:"rgba(26, 26, 46, 0.9)",display:"flex",alignItems:"center",justifyContent:"center",color:"#fff",fontFamily:"sans-serif"});const t=document.createElement("div");t.style.textAlign="center";const e=document.createElement("div");e.style.fontSize="24px",e.style.marginBottom="8px",e.textContent="</>";const n=document.createElement("div");n.textContent="HTML Content",t.appendChild(e),t.appendChild(n),o.appendChild(t)}return o}}function St(t,e){const n=new wt(t);console.log(`[HtmlMeshManager] Setting up ${e.length} HTML meshes (CSS3D)`);for(const t of e)console.log(`[HtmlMeshManager] Creating mesh: ${t.id}`,{html:(t.html||t.htmlContent||"").substring(0,80),contentType:t.contentType,iframeUrl:t.iframeUrl,position:t.position,scale:t.scale,width:t.width,height:t.height}),n.createMesh(t);return n}const _t=new Set(["goToWaypoint","nextWaypoint","prevWaypoint","getCurrentWaypointIndex","getWaypointCount","setCameraMode","getCameraMode","setExploreMode","setPosition","setRotation","getPosition","getRotation","play","pause","stop","isPlaying","setProgress","getProgress","muteAll","unmuteAll","isMuted","getHotspots","triggerHotspot","closeHotspot","goToOriginalSplat","goToSplat","getCurrentSplatUrl","isShowingOriginalSplat","getAdditionalSplats","setFrame","getCurrentFrame","getTotalFrames","setFps","getFps","getFrameProgress","setFrameProgress","setButtonLabels","on","off","resize"]),Mt=new Set(["width","height","clientWidth","clientHeight","getBoundingClientRect","addEventListener","removeEventListener"]),Et=new Set(["ownerDocument","parentElement","parentNode","getRootNode","closest","querySelector","querySelectorAll","children","childNodes","firstChild","lastChild","nextSibling","previousSibling","nextElementSibling","previousElementSibling","innerHTML","outerHTML","insertAdjacentHTML","insertAdjacentElement","append","appendChild","removeChild","replaceChild","remove","constructor","__proto__","prototype"]),Ct=["window","document","self","globalThis","top","parent","frames","opener","exports","module","require","define","fetch","XMLHttpRequest","WebSocket","EventSource","localStorage","sessionStorage","indexedDB","caches","Worker","SharedWorker","ServiceWorker","eval","Function","importScripts","Blob","File","FormData","URL","URLSearchParams","Image","Audio","MediaStream","alert","confirm","prompt","location","history","MutationObserver","IntersectionObserver","ResizeObserver","postMessage","BroadcastChannel","Proxy","Reflect","Symbol","close","stop","getComputedStyle","matchMedia","btoa","atob","structuredClone","reportError","createImageBitmap","navigator","screen","crypto","requestIdleCallback"];class Tt{constructor(t){this.isInitialized=!1,this.scriptCleanup=[],this.lastError=null,this.updateCallbacks=[],this.app=null,this.cancelWatchdog=null,this.customScript=t,this.api={}}initialize(t,e){this.api={...t,registerCleanup:t=>this.addCleanup(t)},this.app=e,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||!this.app)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);var e,n;if(/\bimport\s*\(/.test(t))console.warn("[Custom Script] Dynamic import() is not allowed in custom scripts.");else try{const o=this.app;let i=!1;this.cancelWatchdog=()=>{i=!0};const s=()=>{i||(this.updateCallbacks.length>200?(console.warn("[Custom Script] Too many update callbacks; further callbacks ignored."),i=!0):requestAnimationFrame(s))};requestAnimationFrame(s);const a=t=>{if("function"!=typeof t||i)return;const e=()=>{try{t()}catch(t){console.error("[Custom Script] Error in update callback:",t)}};this.updateCallbacks.push(e),o.on("update",e),this.addCleanup(()=>{o.off("update",e);const t=this.updateCallbacks.indexOf(e);-1!==t&&this.updateCallbacks.splice(t,1)})},r=(n=this.api.viewer,new Proxy(n,{get(t,e){if("constructor"!==e&&"__proto__"!==e&&"prototype"!==e&&"string"==typeof e&&_t.has(e)){const n=t[e];return"function"==typeof n?n.bind(t):n}},set:()=>!1,has:(t,e)=>"string"==typeof e&&_t.has(e),ownKeys:()=>[..._t],getPrototypeOf:()=>null})),l=(e=this.api.canvas,new Proxy(e,{get(t,e){if(!Et.has(e)&&"string"==typeof e&&Mt.has(e)){const n=t[e];return"function"==typeof n?n.bind(t):n}},set:()=>!1,getPrototypeOf:()=>null})),c=function(){const t={log:console.log.bind(console),warn:console.warn.bind(console),error:console.error.bind(console),info:console.info.bind(console),debug:console.debug.bind(console)};return Object.freeze(t),t}(),{getScrollPercentage:d,getCurrentWaypointIndex:p}=this.api,h=["viewer","canvas","getScrollPercentage","getCurrentWaypointIndex","registerUpdate","registerCleanup","console","setTimeout","setInterval","clearTimeout","clearInterval","requestAnimationFrame","cancelAnimationFrame","queueMicrotask",...Ct],u="'use strict';\n"+t,m=function(){const t=[],e=[Object.prototype,Array.prototype,String.prototype,Number.prototype,Boolean.prototype,RegExp.prototype,Error.prototype,TypeError.prototype,RangeError.prototype,Map.prototype,Set.prototype,WeakMap.prototype,WeakSet.prototype,Promise.prototype];try{e.push((async()=>{}).constructor.prototype)}catch{}try{e.push(function*(){}.constructor.prototype)}catch{}try{e.push(async function*(){}.constructor.prototype)}catch{}for(const n of e){const e=Object.getOwnPropertyDescriptor(n,"constructor");t.push([n,e]),Object.defineProperty(n,"constructor",{get(){},configurable:!0})}return()=>{for(const[e,n]of t)n?Object.defineProperty(e,"constructor",n):delete e.constructor}}();try{const t=new Function(...h,u),e=[r,l,d,p,a,t=>this.addCleanup(t),c,setTimeout,setInterval,clearTimeout,clearInterval,requestAnimationFrame,cancelAnimationFrame,queueMicrotask,...Ct.map(()=>{})],n=t.apply(null,e);"function"==typeof n&&this.addCleanup(n)}finally{m()}console.log("[Custom Script] Executed successfully")}catch(t){this.lastError=t,console.error("[Custom Script] Execution error:",t)}}cleanup(){this.cancelWatchdog?.(),this.cancelWatchdog=null,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,this.app=null}}function At(t,e,n,o,i,s){if(!o||""===o.trim())return null;console.log("[Custom Script] Initializing with hardened sandbox...");const a=new Tt(o);return a.initialize({viewer:t,canvas:n,getScrollPercentage:i,getCurrentWaypointIndex:s},e),a.execute(),a}class Pt{constructor(t,e,n={}){this.options=n,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 n=new t.Entity(e);return n.addComponent("gsplat",{}),this.app.root.addChild(n),n}async preloadInitialFrames(){const t=Math.min(this.preloadCount,this.frameUrls.length),e=[];for(let n=0;n<t;n++)e.push(this.preloadFrame(n));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(n=>{const o=this.frameUrls[e],i=new t.Asset(`frame_${e}`,"gsplat",{url:o});i.on("load",()=>{this.destroyed||(this.frameAssets.set(e,i),this.loadingFrames.delete(e)),n(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}`),n(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 n=(this.activeEntityIndex+1)%2,o=this.entities[this.activeEntityIndex],i=this.entities[n],s=i.gsplat;s&&(s.asset=e),i.enabled=!0,o.enabled=!1,this.activeEntityIndex=n,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(),n=e-this.lastFrameTime;if(n>=this.frameInterval){this.lastFrameTime=e-n%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,n){this.entities[0].setPosition(t,e,n),this.entities[1].setPosition(t,e,n)}setRotation(t,e,n){this.entities[0].setEulerAngles(t,e,n),this.entities[1].setEulerAngles(t,e,n)}setScale(t,e,n){this.entities[0].setLocalScale(t,e,n),this.entities[1].setLocalScale(t,e,n)}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 Lt{constructor(t,e,n,o){this.viewer=t,this.baseUrl=e,this.sceneId=n,this.ownerId=o,this.timeline=[],this.pendingEvents=[],this.sessionStart=Date.now(),this.chunkIndex=0,this.lastPos={x:0,y:0,z:0},this.lastRot={x:0,y:0,z:0},this.cameraIntervalId=null,this.flushIntervalId=null,this.maxDurationMs=18e5,this.destroyed=!1,this.handleCanvasClick=null,this.sessionId=crypto.randomUUID();try{this.visitorId=sessionStorage.getItem("storysplat_visitor_id")||"",this.visitorId||(this.visitorId=crypto.randomUUID(),sessionStorage.setItem("storysplat_visitor_id",this.visitorId))}catch{this.visitorId=crypto.randomUUID()}this.handleVisibility=()=>{"hidden"===document.visibilityState&&this.beaconFlush(!1)},this.handleUnload=()=>this.beaconFlush(!0),this.setupEventListeners(),this.startCameraSampling(),this.startFlushInterval(),document.addEventListener("visibilitychange",this.handleVisibility),window.addEventListener("beforeunload",this.handleUnload),t.on("error",()=>this.destroy())}ts(){return Date.now()-this.sessionStart}quantize(t,e){return Math.round(t/e)*e}startCameraSampling(){this.cameraIntervalId=setInterval(()=>{if(this.destroyed||this.ts()>this.maxDurationMs)return;const t=this.viewer.getPosition(),e=this.viewer.getRotation(),n=Math.abs(t.x-this.lastPos.x)+Math.abs(t.y-this.lastPos.y)+Math.abs(t.z-this.lastPos.z),o=Math.abs(e.x-this.lastRot.x)+Math.abs(e.y-this.lastRot.y)+Math.abs(e.z-this.lastRot.z);n<.1&&o<2||(this.lastPos={...t},this.lastRot={...e},this.timeline.push({t:this.ts(),k:"c",p:[this.quantize(t.x,.01),this.quantize(t.y,.01),this.quantize(t.z,.01)],r:[this.quantize(e.x,.1),this.quantize(e.y,.1),this.quantize(e.z,.1)]}))},1e3)}setupEventListeners(){const t=this.viewer;t.on("waypointChange",t=>{const e=t?.waypoint?.name||t?.name||`waypoint-${t?.index??0}`;this.pendingEvents.push({type:"waypoint",name:e}),this.timeline.push({t:this.ts(),k:"w",n:e,i:t?.index??0})}),t.on("modeChange",t=>{const e=t?.mode||"unknown";this.pendingEvents.push({type:"mode",name:e}),this.timeline.push({t:this.ts(),k:"m",m:e})}),t.on("hotspotClick",t=>{const e=t?.hotspot?.title||t?.hotspot?.name||"unknown";this.pendingEvents.push({type:"hotspot",name:e});const n=t?.hotspot?.position;this.timeline.push({t:this.ts(),k:"h",n:e,id:t?.hotspot?.id||"",p:n?[n.x,n.y,n.z]:[0,0,0]})}),t.on("portalActivated",t=>{const e=t?.targetSceneName||t?.portal?.name||"unknown";this.pendingEvents.push({type:"portal",name:e}),this.timeline.push({t:this.ts(),k:"P",n:e,tid:t?.targetSceneId||t?.portal?.targetSceneId||""})}),t.on("progressUpdate",t=>{void 0!==t?.progress&&this.timeline.push({t:this.ts(),k:"s",v:Math.round(1e3*t.progress)/1e3})}),t.on("playbackStart",()=>{this.timeline.push({t:this.ts(),k:"pb",a:"start"})}),t.on("playbackStop",()=>{this.timeline.push({t:this.ts(),k:"pb",a:"stop"})}),t.on("playbackComplete",()=>{this.timeline.push({t:this.ts(),k:"pb",a:"complete"})});try{this.handleCanvasClick=t=>{this.timeline.push({t:this.ts(),k:"x",sx:t.clientX,sy:t.clientY})},this.viewer.canvas.addEventListener("click",this.handleCanvasClick)}catch{}}startFlushInterval(){this.flushIntervalId=setInterval(()=>this.flush(),3e4)}buildAnalyticsPayload(){const t=Math.round((Date.now()-this.sessionStart)/1e3),e=this.pendingEvents.splice(0,100);return{sceneId:this.sceneId,ownerId:this.ownerId,type:"analytics",visitorId:this.visitorId,sessionDuration:t,events:e}}buildSessionChunkPayload(t){const e=this.timeline.splice(0),n={sceneId:this.sceneId,ownerId:this.ownerId,type:t?"session-end":"session-chunk",sessionId:this.sessionId,visitorId:this.visitorId,chunkIndex:this.chunkIndex,timeline:e};return 0===this.chunkIndex&&(n.meta={device:/Mobi|Android/i.test(navigator.userAgent)?"mobile":"desktop",viewport:{w:window.innerWidth,h:window.innerHeight},userAgent:navigator.userAgent.slice(0,200),initialMode:this.viewer.getCameraMode()}),t&&(n.duration=Math.round((Date.now()-this.sessionStart)/1e3)),this.chunkIndex++,n}async flush(){if(this.destroyed)return;const t=`${this.baseUrl}/api/track-embed`,e={"Content-Type":"application/json"};if(this.pendingEvents.length>0)try{await fetch(t,{method:"POST",headers:e,body:JSON.stringify(this.buildAnalyticsPayload())})}catch{}if(this.timeline.length>0)try{await fetch(t,{method:"POST",headers:e,body:JSON.stringify(this.buildSessionChunkPayload(!1))})}catch{}}beaconFlush(t){if(this.destroyed)return;const e=`${this.baseUrl}/api/track-embed`;try{const t=this.buildAnalyticsPayload();navigator.sendBeacon(e,new Blob([JSON.stringify(t)],{type:"text/plain"}))}catch{}try{const n=this.buildSessionChunkPayload(t);navigator.sendBeacon(e,new Blob([JSON.stringify(n)],{type:"text/plain"}))}catch{}}destroy(){if(!this.destroyed&&(this.beaconFlush(!0),this.destroyed=!0,this.cameraIntervalId&&clearInterval(this.cameraIntervalId),this.flushIntervalId&&clearInterval(this.flushIntervalId),document.removeEventListener("visibilitychange",this.handleVisibility),window.removeEventListener("beforeunload",this.handleUnload),this.handleCanvasClick))try{this.viewer.canvas.removeEventListener("click",this.handleCanvasClick)}catch{}}}function kt(t,e,n,o){const i=new Lt(t,e,n,o);return{destroy:()=>i.destroy()}}class zt{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))}listenerCount(t){return this.listeners.get(t)?.size||0}}function Rt(){const t=navigator.userAgent||navigator.vendor||window.opera||"";return/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(t)}const Dt={"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 It(t){const e=t.includes("lod-meta.json");return e&&console.log("[SPLAT] Detected LOD streaming format (lod-meta.json)"),e}function Ft(e,n,o={}){if(console.log("[StorySplat Viewer] BUILD 2025-01-22-orbit-fly-v3"),o.lazyLoad??n.uiOptions?.lazyLoad){const t=new zt;let i,s=null;const a=o.lazyLoadThumbnail||n.uiOptions?.lazyLoadThumbnailUrl||n.thumbnailUrl,r=o.lazyLoadButtonText||n.uiOptions?.lazyLoadButtonText||l(n.uiOptions?.buttonLabels,"startExperience"),c=n.uiColor||"#CC5833";console.log("[StorySplat Viewer] Lazy loading enabled, showing start button"),function(t,e){const{thumbnailUrl:n,thumbnailType:o,buttonText:i="Start Experience",uiColor:s="#4CAF50",onStart:a}=e;u(s),t.classList.add("storysplat-viewer-container");const r=document.createElement("div");r.className="storysplat-lazy-load-container";const l=(n?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"}(n):null)||o||"image";let c="";n&&(c+="video"===l?`<video class="storysplat-lazy-load-thumbnail-video" src="${n}" autoplay muted loop playsinline webkit-playsinline></video>`:`<img class="storysplat-lazy-load-thumbnail" src="${n}" alt="Scene preview" />`),c+='<div class="storysplat-lazy-load-overlay"></div>',c+=`\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=c,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:o.lazyLoadThumbnailType||n.uiOptions?.lazyLoadThumbnailType,buttonText:r,uiColor:c,onStart:()=>{console.log("[StorySplat Viewer] User clicked start, initializing viewer..."),s=Ft(e,n,{...o,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",e=>t.emit("loaded",e)),s.on("progress",e=>t.emit("progress",e)),s.on("modeChange",e=>t.emit("modeChange",e)),s.on("hotspotClick",e=>t.emit("hotspotClick",e)),s.on("portalActivated",e=>t.emit("portalActivated",e)),s.on("progressUpdate",e=>t.emit("progressUpdate",e)),s.on("playbackComplete",()=>t.emit("playbackComplete"))}});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,n)=>s?.setPosition(t,e,n),setRotation:(t,e,n)=>s?.setRotation(t,e,n),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,n)=>t.on(e,n),off:(e,n)=>t.off(e,n)}}const i=new zt;if(!o.allowParentStyles){const t=e.style.width,n=e.style.height;e.style.all="initial",e.style.position="relative",e.style.display="block",e.style.width=t||"100%",e.style.height=n||"100%",e.style.overflow="hidden",e.style.fontFamily="system-ui, -apple-system, sans-serif"}const c=!0===o.editor;c&&e.setAttribute("data-storysplat-editor","true"),i.on("error",t=>{console.error("[StorySplat Viewer] Error event:",t.message),c||function(t,e){if(t.hasAttribute("data-storysplat-editor"))return;t.querySelector(".storysplat-error-popup")?.remove();const n=t.querySelector(".storysplat-preloader");n&&g(n);const o=e.includes("Failed to load splat from any URL"),i=document.createElement("div");i.className="storysplat-error-popup",i.innerHTML=o?'\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 d=a(s(n));console.log("[StorySplat Viewer] Creating viewer with config:",d),console.log("[StorySplat Viewer] Scale config:",d.scale),console.log("[StorySplat Viewer] Raw scene data:",{splatScale:n.splatScale,scale:n.scale});let p=null;const h=c?!o.editorSkipUI&&!1!==o.showUI:!1!==o.showUI,m=d.uiColor||"#CC5833",T=d.uiOptions||{},A=o.template||T.uiType||"minimal";let P=T.buttonLabels;const L=t=>"first-person"===t?"tour":"drone"===t?"explore":t,k=d.collisionMeshesData&&d.collisionMeshesData.length>0||!!d.voxelCollisionUrl;let z=(d.allowedCameraModes||["orbit","first-person","drone"]).map(L).filter((t,e,n)=>n.indexOf(t)===e);k&&!z.includes("walk")&&z.push("walk"),!k&&z.includes("walk")&&(z=z.filter(t=>"walk"!==t));let D=L(d.defaultCameraMode||"orbit");z.includes(D)||(D=z[0]||"tour");const I=!!(d.audioEmitters?.length||d.hotspots?.some(t=>t.audioUrl)||d.hotspots?.some(t=>"video"===t.type&&!0!==t.videoMuted)||d.waypoints?.some(t=>t.interactions?.some(t=>"audio"===t.type)));let F={};h&&(F=f(e,d,{uiColor:m,showScrollControls:!T.hideNavigator&&d.waypoints&&d.waypoints.length>0,showModeToggle:z.length>1,showFullscreenButton:!T.hideFullscreenButton,showHelpButton:!T.hideHelpButton&&!T.hideInfoButton,showMuteButton:!T.hideMuteButton&&I,showPreloader:!0,allowedCameraModes:z,defaultCameraMode:D,buttonLabels:P,customPreloaderLogoUrl:T.customPreloaderLogoUrl,hideWatermark:T.hideWatermark,watermarkText:T.watermarkText,watermarkLink:T.watermarkLink,sceneId:n.sceneId,showWaypointList:T.showWaypointList,template:A,debugMode:T.debugMode,hideProgressText:T.hideProgressText,viewerTheme:T.viewerTheme,showRelightingToggle:!!(d.splatRelighting?.allowViewerToggle&&d.lights&&d.lights.length>0),showSceneMenu:!!(d.portals&&d.portals.length>0)}),M(F));const B=document.createElement("canvas");let U;B.id="storysplat-viewer-canvas",B.style.width="100%",B.style.height="100%",B.style.display="block",e.appendChild(B);const W={antialias:!1,alpha:!1,powerPreference:"high-performance"};try{U=new t.Application(B,{graphicsDeviceOptions:W,mouse:new t.Mouse(B),touch:new t.TouchDevice(B),keyboard:new t.Keyboard(window)}),console.log("[StorySplat Viewer] Graphics initialized successfully")}catch(n){console.warn("[StorySplat Viewer] WebGL2 initialization failed, trying WebGL1 fallback:",n);try{U=new t.Application(B,{graphicsDeviceOptions:{...W,preferWebGl2:!1},mouse:new t.Mouse(B),touch:new t.TouchDevice(B),keyboard:new t.Keyboard(window)}),console.log("[StorySplat Viewer] WebGL1 fallback successful")}catch(t){console.error("[StorySplat Viewer] WebGL initialization failed completely:",t);const n=document.createElement("div");n.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 o=document.createElement("h3");o.style.cssText="margin:0 0 10px 0;",o.textContent=l(P,"errorWebGLTitle");const i=document.createElement("p");throw i.style.cssText="margin:0;",i.textContent=l(P,"errorWebGLMessage"),n.appendChild(o),n.appendChild(i),e.appendChild(n),new Error("WebGL initialization failed - browser may not support WebGL")}}B.addEventListener("webglcontextlost",t=>{t.preventDefault(),console.error("[StorySplat Viewer] WebGL context lost"),i.emit("error",new Error("WebGL context lost"))},!1),B.addEventListener("webglcontextrestored",()=>{console.log("[StorySplat Viewer] WebGL context restored")},!1),U.setCanvasFillMode(t.FILLMODE_FILL_WINDOW),U.setCanvasResolution(t.RESOLUTION_AUTO),U.scene.ambientLight=new t.Color(0,0,0),U.start(),console.log("[StorySplat Viewer] App started");try{const t=U.scene.layers.getLayerByName("World"),e=t&&Object.getPrototypeOf(t);if(e&&!e._splitLightsPatched){const t=Object.getOwnPropertyDescriptor(e,"splitLights");if(t?.get){const n=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)),n.call(this)},configurable:!0}),e._splitLightsPatched=!0}}}catch(t){console.warn("[StorySplat Viewer] Could not patch Layer.splitLights:",t)}const N=Rt(),G=d.lodSettings;let H,q,j;if(G&&"auto"!==G.preset)if("custom"===G.preset)q=[G.lodRangeMin??0,G.lodRangeMax??5],j=G.lodDistances??[15,30,80,250,300],H="desktop",console.log("[SPLAT] Using custom LOD settings from scene");else{H=G.preset;const t=Dt[H];q=[...t.range],j=t.lodDistances,console.log("[SPLAT] Using scene-configured LOD preset:",H)}else{N&&G?.mobilePreset?(H=G.mobilePreset,console.log("[SPLAT] Using scene mobile override preset:",H)):!N&&G?.desktopPreset?(H=G.desktopPreset,console.log("[SPLAT] Using scene desktop override preset:",H)):H=function(t){const e=navigator.deviceMemory,n=navigator.hardwareConcurrency||4,o=window.devicePixelRatio||1;let i;i=t?null!=e&&e>=6&&n>=6||null==e&&n>=6&&o>=3?"mobile-max":"mobile":null!=e&&e>=8&&n>=8||null==e&&n>=8?"desktop-max":"desktop";return console.log("[SPLAT] Auto LOD preset resolved:",i,{memory:e,cores:n,dpr:o,isMobile:t}),i}(N);const t=Dt[H];q=[...t.range],j=t.lodDistances}console.log("[SPLAT] Initializing LOD system for device:",N?"mobile":"desktop"),U.scene.gsplat?(U.scene.gsplat.lodUpdateAngle=90,U.scene.gsplat.lodBehindPenalty=2,U.scene.gsplat.radialSorting=!0,U.scene.gsplat.lodUpdateDistance=1,U.scene.gsplat.lodUnderfillLimit=10,U.scene.gsplat.lodRangeMin=q[0],U.scene.gsplat.lodRangeMax=q[1],U.scene.gsplat.colorUpdateDistance=1,U.scene.gsplat.colorUpdateAngle=4,U.scene.gsplat.colorUpdateDistanceLodScale=2,U.scene.gsplat.colorUpdateAngleLodScale=2,console.log("[SPLAT] LOD system configured:",{preset:G?.preset??"auto",lodRangeMin:q[0],lodRangeMax:q[1],lodDistances:j,lodUpdateAngle:90,lodUpdateDistance:1,isMobile:N})):console.warn("[SPLAT] GSplat scene settings not available - LOD may not work optimally");let K=0,tt=!1,et=null,ot=null,it=null;const st=new Set;let at=!1,rt=!1,lt=!1,ct=null;const dt=d.additionalSplats||[],pt=d.keepMeshesInMemory??!1,ht=d.initialSplatExploreMode||"fly";let ut=null,mt=!1;const gt=new Map;let ft=-1,yt=-1,xt=0;const bt=new t.Entity("camera");let wt=new t.Color(.1,.1,.1);if(d.backgroundColor){const e=d.backgroundColor.replace("#","");if(6===e.length){const n=parseInt(e.substring(0,2),16)/255,o=parseInt(e.substring(2,4),16)/255,i=parseInt(e.substring(4,6),16)/255;wt=new t.Color(n,o,i),console.log("[StorySplat Viewer] Background color set from config:",d.backgroundColor)}}bt.addComponent("camera",{clearColor:wt,fov:d.fov||60,nearClip:d.nearClip||.1,farClip:d.farClip||1e4}),bt.addComponent("audiolistener"),console.log("[StorySplat Viewer] Camera settings:",{fov:d.fov,nearClip:d.nearClip,farClip:d.farClip,playerHeight:d.playerHeight});const _t=d.playerHeight||1.6;if(d.waypoints&&d.waypoints.length>0){const t=d.waypoints[0];if(console.log("[StorySplat Viewer] First waypoint raw:",t),t.position){const e=t.position;bt.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 bt.setPosition(0,_t,5);if(t.rotation){const e=ke(t.rotation);bt.setRotation(e),console.log("[StorySplat Viewer] Camera rotation set from waypoint")}}else bt.setPosition(0,_t,5),bt.lookAt(new t.Vec3(0,0,0)),console.log("[StorySplat Viewer] Camera set to default position (0, playerHeight, 5)");U.root.addChild(bt);const Mt=5*(d.cameraMovementSpeed??1),Et=new R(bt,U,{moveSpeed:Mt,moveFastSpeed:2.5*Mt,moveSlowSpeed:.5*Mt,rotateSpeed:800/(d.cameraRotationSensitivity||4e3),enableOrbit:!0,enableFly:!0,enablePan:!0,invertRotation:d.invertCameraRotation,moveDamping:d.cameraDamping??.75,rotateDamping:d.cameraDamping??.75,zoomDamping:.8});let Ct=null;const Tt=d.collisionMeshesData&&d.collisionMeshesData.length>0;if(Tt||!!d.voxelCollisionUrl){const t=null!=d.walkSpeed?d.walkSpeed:Mt;Ct=new $(bt,U,{moveSpeed:t,sprintMultiplier:2,lookSensitivity:1/(d.cameraRotationSensitivity||4e3)*10,playerHeight:d.playerHeight||1.6,gravity:20,jumpVelocity:8,collisionRadius:.3,stepHeight:.3}),Tt&&Ct.createCollisionMeshes(d.collisionMeshesData).then(()=>{console.log("[StorySplat Viewer] Collision meshes loaded for walk mode"),Et.setCollisionEntities(Ct.collisionMeshEntities)}).catch(t=>{console.error("[StorySplat Viewer] Failed to load collision meshes:",t)}),d.voxelCollisionUrl&&Ct.initVoxelCollision(d.voxelCollisionUrl).then(()=>{const t=Ct.voxelCollisionInstance;t&&Et.setVoxelCollision(t,et)}).catch(t=>{console.warn("[StorySplat Viewer] Voxel collision init failed:",t)})}Ct&&null!=d.headBobEnabled&&(Ct.headBobEnabled=d.headBobEnabled),null!=d.doubleTapMoveSpeed&&(Et.autoMoveSpeedFactor=d.doubleTapMoveSpeed,Ct&&(Ct.autoMoveSpeedFactor=d.doubleTapMoveSpeed));let Lt=D,Bt=!1;function $t(t){Bt=t,"walk"===Lt&&Ct?Ct.autoMoveForward=t:"explore"===Lt&&(Et.autoMoveForward=t)}function Vt(){Bt&&$t(!1)}let Ot=!0;"tour"===D&&Et.disable();const Wt=new t.Picker(U,1,1,!0),Nt=new t.Picker(U,1,1,!0),Gt=new t.Layer({name:"Reticle"});U.scene.layers.push(Gt);const Ht=bt.camera.layers;bt.camera.layers=[...Ht,Gt.id];const Xt=new t.Entity("reticle"),qt=new t.StandardMaterial;qt.emissive=new t.Color(1,1,1),qt.diffuse=new t.Color(0,0,0),qt.useLighting=!1,qt.blendType=t.BLEND_NORMAL,qt.opacity=1,qt.depthTest=!1,qt.depthWrite=!1,qt.cull=t.CULLFACE_NONE,qt.update();const jt=new t.TorusGeometry({tubeRadius:.048,ringRadius:.193,segments:24,sides:10}),Yt=t.Mesh.fromGeometry(U.graphicsDevice,jt),Zt=new t.MeshInstance(Yt,qt);Zt.drawOrder=9999,Xt.addComponent("render",{meshInstances:[Zt],castShadows:!1,layers:[Gt.id]}),Xt.enabled=!1,U.root.addChild(Xt);let Qt=0,Kt=0,Jt=new t.Vec3,te=new t.Vec3,ee=!1,ne=0,oe=!1,ie=new t.Vec3(0,1,0),se=new t.Vec3(0,1,0);const ae=new t.Quat,re=new t.Vec3,le=new t.Vec3,ce=new t.Vec3,de=new t.Vec3,pe=new t.Vec3;let he=!0,ue=!1,me=0;let ge=!1,fe=null;let ye=null,ve=null,xe=!1;function be(){ye&&(ye.enabled=!1,xe=!1)}let we=0;U.on("update",e=>{var n,o;F.fpsCounter&&(we+=e,we>=.5&&(we=0,o=1/e,(n=F).fpsCounter&&(n.fpsCounter.textContent=`${Math.round(o)} FPS`))),"walk"===Lt&&Ct?Ct.update(e):Et.update(e),Ot||function(){const t=bt.getPosition();Bn.forEach(e=>{const n=e.hotspotData;if(!n)return;if("proximity"!==(e.mediaTriggerMode||"click"))return;const o=e.getPosition(),i=t.distance(o)<=(e.proximityDistance||5),s=e.wasInProximity||!1;if(i&&!s){if("video"===n.type&&e.videoElement)pi(e,n);else if("gif"===n.type)e.enabled=!0;else if(("sphere"===n.type||"image"===n.type)&&e.audioElements){const t=e.audioElements,o=t.audio;o&&o.paused&&(t.audioCtx&&"suspended"===t.audioCtx.state&&t.audioCtx.resume(),o.play().catch(t=>console.error("[Audio] Hotspot audio play failed (proximity):",t)),console.log("[Audio] Hotspot audio started (proximity):",n.title))}e.wasInProximity=!0}else if(!i&&s){if("video"===n.type&&e.videoElement)hi(e);else if("gif"===n.type)e.enabled=!1;else if(("sphere"===n.type||"image"===n.type)&&e.audioElements){const t=e.audioElements.audio;t&&!t.paused&&(t.pause(),console.log("[Audio] Hotspot audio stopped (proximity):",n.title))}e.wasInProximity=!1}})}(),function(){if(0===qn.size)return;const t=bt.getPosition();qn.forEach((e,n)=>{const{entity:o,config:i,slotId:s,assetReady:a,playing:r}=e;if(!a)return;const l=o.sound?.slot(s);if(l&&i.spatialSound){const s=o.getPosition(),a=t.distance(s),c=i.maxDistance||20;a<=c&&!r?(console.log(`[Audio] Proximity play: ${n}, distance=${a.toFixed(2)}, maxDistance=${c}`),l.play(),e.playing=!0):a>c&&r&&i.stopOnExit&&(console.log(`[Audio] Proximity stop: ${n}, distance=${a.toFixed(2)}`),l.stop(),e.playing=!1)}})}(),function(){if(0===ei.size)return;const t=bt.getPosition();ei.forEach((e,n)=>{const{entity:o,config:i,slotId:s,assetReady:a,playing:r}=e;if(!a)return;const l=o.sound?.slot(s);if(l&&!1!==i.spatialSound){const n=o.getPosition();t.distance(n)<=(i.maxDistance||100)&&!r&&!1!==i.autoplay&&(l.isPlaying||(l.play(),e.playing=!0))}})}(),function(){if(!d.waypoints?.length)return;const e=bt.getPosition();d.waypoints.forEach((n,o)=>{const i=new t.Vec3(n.position?.x??0,n.position?.y??0,-(n.position?.z??0)),s=e.distance(i),a=n.triggerDistance??1;s<=a?ni.has(o)||(ni.add(o),console.log(`[StorySplat] Waypoint ${o} 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 n=qn.get(e);if(n&&n.assetReady&&!n.playing){const t=n.entity.sound?.slot(n.slotId);t&&(t.play(),n.playing=!0)}}})}(n)):ni.has(o)&&(ni.delete(o),console.log(`[StorySplat] Waypoint ${o} 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 n=qn.get(e);if(n&&n.playing){const t=n.entity.sound?.slot(n.slotId);t&&(t.stop(),n.playing=!1)}}}})}(n))})}(),ge&&xe&&function(){if(ye?.enabled&&bt){const t=bt.forward.clone(),e=bt.getPosition(),n=e.clone().add(t.mulScalar(1.5));n.y-=.1,ye.setPosition(n),ye.lookAt(e),ye.rotateLocal(90,0,0)}}(),function(t){if(0===Bo.length)return;const e=[];for(let n=0;n<Bo.length;n++){const o=Bo[n];if(o.age+=t,o.age>=o.lifetime){e.push(n);continue}o.velocity.y-=Vo*t,o.velocity.x*=1-.3*t,o.velocity.z*=1-.3*t;const i=o.entity.getPosition().clone();i.x+=o.velocity.x*t,i.y+=o.velocity.y*t,i.z+=o.velocity.z*t;const s=jo(i,o.radius);if(null!==s){const t=s+o.radius;i.y<t&&o.velocity.y<0&&(i.y=t,o.velocity.y=-o.velocity.y*Oo,Math.abs(o.velocity.y)<.3&&(o.velocity.y=0))}Yo(i,o.velocity,o.radius),o.entity.setPosition(i.x,i.y,i.z);const a=o.lifetime-o.age;a<2&&o.entity.light&&(o.entity.light.intensity=o.baseIntensity*(a/2))}for(let t=e.length-1;t>=0;t--)Zo(Bo[e[t]]),Bo.splice(e[t],1)}(e),(!he||N||"explore"!==Lt&&"walk"!==Lt)&&(ee=!1),ue&&(ee=!1),wi&&(ee=!1);const i=ee?1:0;if(me+=(i-me)*Math.min(1,8*e),Math.abs(me-i)<.01&&(me=i),me>0){te.distance(Jt)<.001?te.copy(Jt):te.lerp(te,Jt,Math.min(1,15*e)),se.lerp(se,ie,Math.min(1,10*e)),se.normalize();const n=bt.getPosition().distance(te),o=Math.max(.24,.12*n);pe.copy(te).addScaled(se,.01),Xt.setPosition(pe),Xt.setLocalScale(o,.45*o,o),qt.opacity=me,qt.emissive.set(1,1,1),qt.update(),ae.setFromDirections(t.Vec3.UP,se),Xt.setRotation(ae),Xt.enabled=!0}else Xt.enabled=!1;at&&st.size>0&&(!function(){if(0===st.size)return;Co.length=0;for(const t of xo){if(!t.enabled||!t.light)continue;const e=t.light,n=t.getPosition(),o=e.type,i="directional"===o,s=i?0:e.range||10;Po||console.log("[Relighting] Light:",t.name,"rawType:",o,"(typeof:",typeof o,")","isDir:",i,"color:",e.color.r.toFixed(3),e.color.g.toFixed(3),e.color.b.toFixed(3),"intensity:",e.intensity,"range:",s,"pos:",n.x.toFixed(2),n.y.toFixed(2),n.z.toFixed(2));const a="spot"===o,r=a?t.forward:void 0;Co.push({position:n,color:e.color,intensity:e.intensity,range:s,direction:a&&r?{x:r.x,y:r.y,z:r.z}:void 0,cosInnerAngle:a?Math.cos((e.innerConeAngle??36)*Math.PI/180):void 0,cosOuterAngle:a?Math.cos((e.outerConeAngle??45)*Math.PI/180):void 0})}!Po&&Co.length>0&&(Po=!0,console.log("[Relighting] Total lights synced:",Co.length));for(const t of st)t.enabled&&t.updateLights(Co)}(),!rt&&it&&(rt=!0,console.log("[StorySplat Viewer] Relighting sync active, lights:",Co.length,"fade:",it.relightFade,"ambient:",[it.ambientR,it.ambientG,it.ambientB],"material:",!!it.material,"totalScripts:",st.size)))}),U.on("postrender",()=>{const e=he&&!N&&("explore"===Lt||"walk"===Lt),n=U.graphicsDevice.canvas;if(!e||oe||!n)return;if(ne++,ne<4)return;ne=0,oe=!0;const o=.25,i=Math.max(1,Math.floor(n.clientWidth*o)),s=Math.max(1,Math.floor(n.clientHeight*o)),a=!!document.pointerLockElement,r=a?Math.floor(.5*n.clientWidth*o):Math.floor(Qt*o),l=a?Math.floor(.5*n.clientHeight*o):Math.floor(Kt*o);try{Nt.resize(i,s);const e=U.scene.layers.getLayerByName("World");if(!e)return void(oe=!1);Nt.prepare(bt.camera,U.scene,[e]);const n=4,o=Math.min(r+n,i-1),a=Math.max(r-n,0),c=Math.min(l+n,s-1),d=Math.max(l-n,0);Promise.all([Nt.getWorldPointAsync(r,l),Nt.getWorldPointAsync(o,l),Nt.getWorldPointAsync(r,c),Nt.getWorldPointAsync(a,l),Nt.getWorldPointAsync(r,d)]).then(e=>{if(oe=!1,lt)return;const n=bt.getPosition(),o=t=>{if(!(t&&isFinite(t.x)&&isFinite(t.y)&&isFinite(t.z)))return!1;const e=n.distance(t);return e>.1&&e<500};let i=null;if(o(e[0]))i=e[0];else for(let t=1;t<e.length;t++)if(o(e[t])){i=e[t];break}if(i){Jt.copy(i);let s=!1;const a=e.filter((t,e)=>t!==i&&o(t));a.length>=2&&(re.sub2(a[0],i),le.sub2(a[1],i),re.lengthSq()>1e-8&&le.lengthSq()>1e-8&&(ce.cross(re,le).normalize(),de.sub2(n,i),ce.dot(de)<0&&ce.mulScalar(-1),ce.lengthSq()>.5&&(ie.copy(ce),s=!0))),!s&&ie.lengthSq()<.5&&ie.copy(t.Vec3.UP),ee=!0}}).catch(()=>{oe=!1})}catch(t){oe=!1}});const Se=60;U.on("joystick:left",(t,e,n,o)=>{if(t<0||e<0)b(F,!1,0,0,Se),"walk"===Lt&&Ct&&Ct.setJoystickMove(0,0);else{const i=n-t,s=o-e;b(F,!0,i,s,Se),"walk"===Lt&&Ct&&Ct.setJoystickMove(i/Se,-s/Se)}}),U.on("joystick:right",(t,e,n,o)=>{t<0||e<0?(w(F,!1),"walk"===Lt&&Ct&&Ct.setJoystickLook(0,0)):(w(F,!0),"walk"===Lt&&Ct&&Ct.setJoystickLook((n-t)/Se,(o-e)/Se))});const _e=F.scrollControls?.querySelectorAll(".storysplat-explore-btn");let Me=!1;const Ee=t=>{_e?.forEach(e=>{const n=e.getAttribute("data-explore-mode");e.classList.toggle("selected",n===t)})};function Ce(e){Vt(),"tour"===Lt&&"tour"!==e&&tt&&In(),Xt.enabled=!1,ee=!1,me=0,"walk"===Lt&&Ct?Ct.disable():"explore"===Lt&&Et.disable(),Lt=e,console.log("[StorySplat Viewer] Switching to mode:",e);const n=Rt();if("walk"===e&&Ct)Ot=!1,Et.disable(),n?(Et.detachInputSources(),Et.reattachMobileInput()):Et.detachInputSources(),Ct.enable(),Ee("walk"),n&&x(F,!0),S(F,!1),_(F,!1),E(F);else if("explore"===e){Ct&&Ct.disable(),un=0,mn=0,gn=!1,Ot=!1,Et.disable(),Et.enable(),Et.enableOrbit=!0,Et.enableFly=!0,Et.enablePan=!0;const t=ht,e=bt.getPosition().clone(),o=bt.getRotation().clone();Et.syncFromPose(e,o),console.log("[StorySplat Viewer] Synced camera from actual camera state for explore mode");(async()=>{try{const t=.25;Wt.resize(Math.floor(vi.clientWidth*t),Math.floor(vi.clientHeight*t));const e=U.scene.layers.getLayerByName("World");if(e){Wt.prepare(bt.camera,U.scene,[e]);const n=Math.floor(.5*vi.clientWidth*t),o=Math.floor(.5*vi.clientHeight*t),i=await Wt.getWorldPointAsync(n,o);if(i){const t=bt.getPosition().distance(i);t>.5&&t<500&&(Et.setFocusDistance(i,t),console.log("[StorySplat Viewer] Updated focus distance:",t.toFixed(2)))}}}catch(t){}})();const i=t;Et.setMode(i),Ee(i),n?x(F,"fly"===i):(S(F,"fly"===i),_(F,"orbit"===i))}else Et.enable(),Et.disable(),Ct&&Ct.disable(),Ot=!0,0===xn.length&&d.waypoints&&d.waypoints.length>0&&(d.waypoints.forEach(e=>{const n=e.position?new t.Vec3(e.position.x,e.position.y,-e.position.z):new t.Vec3(0,d.playerHeight||1.6,0);xn.push(n);const o=e.rotation?ke(e.rotation):new t.Quat;bn.push(o);let i=e.fov||Sn;i<3.5&&(i*=180/Math.PI),wn.push(i)}),console.log("[StorySplat Viewer] Rebuilt waypoint arrays from config:",xn.length)),Mn(Je),dn&&pn&&(bt.setPosition(dn),bt.setRotation(pn),bt.camera&&wn.length>0&&(bt.camera.fov=_n)),x(F,!1),S(F,!1),_(F,!1),E(F);i.emit("modeChange",{mode:e})}_e?.forEach(t=>{t.addEventListener("click",()=>{if("explore"!==Lt&&"walk"!==Lt)return;const e=t.getAttribute("data-explore-mode");"walk"===e?("walk"!==Lt&&Ce("walk"),Ee("walk")):("walk"===Lt&&Ce("explore"),Et.setMode(e),Ee(e),N?x(F,"fly"===e):(S(F,"fly"===e),_(F,"orbit"===e)),E(F))})}),U.on("cameracontrols:modechange",e=>{if("orbit"!==e&&"fly"!==e||(Ee(e),"explore"!==Lt&&"walk"!==Lt||(N?x(F,"fly"===e):(S(F,"fly"===e),_(F,"orbit"===e)),E(F))),"orbit"===e){let e=!1;if(ee&&Jt){const n=bt.getPosition(),o=n.distance(Jt);if(o>.3&&o<500){const i=bt.forward,s=new t.Vec3(n.x+i.x*o,n.y+i.y*o,n.z+i.z*o);Et.syncFromCamera(s),e=!0}}if(!e)try{const t=.25,e=Math.floor(.5*vi.clientWidth*t),n=Math.floor(.5*vi.clientHeight*t);Wt.resize(Math.floor(vi.clientWidth*t),Math.floor(vi.clientHeight*t));const o=U.scene.layers.getLayerByName("World");o&&(Wt.prepare(bt.camera,U.scene,[o]),Wt.getWorldPointAsync(e,n).then(t=>{if(!lt&&t&&isFinite(t.x)&&isFinite(t.y)&&isFinite(t.z)){const e=bt.getPosition().distance(t);e>.3&&e<500&&Et.syncFromCamera(t)}}).catch(()=>{}))}catch(t){}}});const Te=(t,e)=>{F.preloader&&function(t,e,n,o){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=n||`${o||r.loading} ${Math.round(a)}%`)}(F.preloader,t,e,l(P,"loading")),i.emit("progress",{progress:t,text:e})};function Ae(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 Pe(e,n){const o=new t.Entity("splat");o.addComponent("gsplat",{asset:e,unified:!0});const i=o.gsplat;i&&It(n)&&(i.lodDistances=[...j]);const s=d.scale||{x:1,y:1,z:1},a=d.invertXScale||!1,r=d.invertYScale||!1,l={x:a?-s.x:s.x,y:r?s.y:-s.y,z:a!==r?s.z:-s.z};o.setLocalScale(l.x,l.y,l.z);const c=d.position||[0,0,0];o.setPosition(c[0],c[1],-c[2]);const p=d.rotation||[0,0,0],h=[p[0]*(180/Math.PI),p[1]*(180/Math.PI),-p[2]*(180/Math.PI)];return o.setEulerAngles(h[0],h[1],h[2]),U.root.addChild(o),o.gsplat?.material&&o.gsplat.material.setParameter("alphaClip",.01),o}function Le(t,e){if(U.assets.add(t),e.includes("lod-meta.json")){const n=U.loader.getHandler("gsplat"),o=n?.parsers?.octree;o?o.load({load:e,original:e},(e,n)=>{e?t.fire("error",e):(t.resource=n,t.loaded=!0,t.fire("load",t))},t):U.assets.load(t)}else U.assets.load(t)}function ke(e){if("_w"in e||"w"in e){const n=e._x??e.x??0,o=e._y??e.y??0,i=e._z??e.z??0,s=e._w??e.w??1;return new t.Quat(-n,-o,i,s)}if("x"in e&&"y"in e&&"z"in e){const n=new t.Quat;return n.setFromEulerAngles(e.x||0,e.y||0,e.z||0),n}return new t.Quat}const ze=nt(o.revealEffect||n.revealEffect||d.revealEffect||"none",o.revealStyle||n.revealStyle||d.revealStyle||"bloom"),Re=.75,De=1.5,Ie=50,Fe=-50,Be=1;function Ue(e,n,o,i){if(!ze)return void(i&&i());e.script||e.addComponent("script");let s=e.script?.gsplatWipeTransition;if(!s){const n=function(){if(Z)return Z;const e=t.createScript("gsplatWipeTransition");return Object.assign(e.prototype,{effectTime:0,material:null,_effectInitialized:!1,_shadersNeedApplication:!1,_edgeTintArray:[0,0,0],mode:1,duration:1.5,bandWidth:3,wipeTop:30,wipeBottom:-30,edgeTint:null,onComplete:null,initialize(){this.effectTime=0,this.material=null,this._effectInitialized=!1,this._shadersNeedApplication=!1,this._edgeTintArray=[0,0,0],this.edgeTint||(this.edgeTint=new t.Color(.2,.6,1)),this.on("enable",()=>{this.effectTime=0,this._effectInitialized?this._applyShaders():this._shadersNeedApplication=!0}),this.on("disable",()=>{this._removeShaders()}),this.entity.gsplat&&(this._effectInitialized=!0,this.enabled&&this._applyShaders())},update(t){if(this._effectInitialized){if(this._shadersNeedApplication&&(this._applyShaders(),this.material&&(this._shadersNeedApplication=!1)),this.material){if(this.effectTime+=t,Math.min(this.effectTime/this.duration,1)>=1){const t=this.entity.script&&this.entity.script.gsplatRelighting;if(t&&t.enabled){if(this.onComplete){const t=this.onComplete;this.onComplete=null,t()}return}const e=this.onComplete;return this.enabled=!1,void(e&&e())}this._updateUniforms(),this.material.update()}}else this.entity.gsplat&&(this._effectInitialized=!0,this.enabled&&this._shadersNeedApplication&&(this._applyShaders(),this.material&&(this._shadersNeedApplication=!1)))},_updateUniforms(){const t=Math.min(this.effectTime/this.duration,1);this._setUniform("uWipeProgress",t),this._setUniform("uWipeTop",this.wipeTop),this._setUniform("uWipeBottom",this.wipeBottom),this._setUniform("uBandWidth",this.bandWidth),this._setUniform("uMode",this.mode),this._setUniform("uTime",this.effectTime),this._edgeTintArray[0]=this.edgeTint.r,this._edgeTintArray[1]=this.edgeTint.g,this._edgeTintArray[2]=this.edgeTint.b,this._setUniform("uEdgeTint",this._edgeTintArray)},getShaderGLSL:()=>'\nuniform float uWipeProgress; // 0.0 = start, 1.0 = fully wiped\nuniform float uWipeTop; // Y coordinate of scene top\nuniform float uWipeBottom; // Y coordinate of scene bottom\nuniform float uBandWidth; // Transition band width in world units\nuniform float uMode; // 1.0 = reveal (show from top), -1.0 = hide (hide from top)\nuniform vec3 uEdgeTint; // Color tint at the wipe edge\nuniform float uTime; // Animation time for edge effects\n\n// Per-splat shared state\nfloat g_visibility; // 0 = hidden, 1 = visible\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 initWipe(vec3 center) {\n float wipeY = mix(uWipeTop, uWipeBottom, uWipeProgress);\n\n // Subtle per-splat noise for organic edge (kept small to avoid dead areas between two splats)\n float noise = (hash(center) - 0.5) * uBandWidth * 0.15;\n float threshold = wipeY + noise;\n float halfBand = uBandWidth * 0.5;\n\n // Compute signed distance from threshold, accounting for sweep direction.\n // For downward sweep (uWipeTop > uWipeBottom): positive = above threshold (already passed).\n // For upward sweep (uWipeTop < uWipeBottom): flip so positive = below threshold (already passed).\n float sweepDir = sign(uWipeTop - uWipeBottom);\n float dist = (center.y - threshold) * sweepDir;\n float raw = smoothstep(-halfBand, halfBand, dist);\n\n if (uMode > 0.0) {\n // Reveal: splats in the "already passed" region are visible\n g_visibility = raw;\n } else {\n // Hide: splats in the "not yet reached" region are visible\n g_visibility = 1.0 - raw;\n }\n}\n\nvoid modifySplatCenter(inout vec3 center) {\n initWipe(center);\n // No position modification for clean wipe\n}\n\nvoid modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) {\n if (g_visibility < 0.01) {\n // Fully hidden\n scale = vec3(0.0);\n return;\n }\n\n if (g_visibility >= 0.99) {\n // Fully visible - keep original\n return;\n }\n\n // Transition band: shrink splats toward the edge\n float origSize = gsplatGetSizeFromScale(scale);\n float t = g_visibility;\n\n if (t < 0.3) {\n // Near-invisible: tiny spherical dots\n float dotSize = min(t * 0.15 * origSize, origSize);\n gsplatMakeSpherical(scale, dotSize);\n } else {\n // Partial visibility: scale down proportionally\n scale *= t;\n }\n}\n\nvoid wipeColorEffect(vec3 center, inout vec4 color) {\n if (g_visibility < 0.01 || g_visibility >= 0.99) return;\n\n // Tint at the wipe edge - strongest at the middle of the band\n float edgeIntensity = sin(g_visibility * 3.14159);\n color.rgb += uEdgeTint * edgeIntensity * 0.4;\n}\n\nvoid modifySplatColor(vec3 center, inout vec4 color) {\n wipeColorEffect(center, color);\n}\n',getShaderWGSL:()=>"\nuniform uWipeProgress: f32;\nuniform uWipeTop: f32;\nuniform uWipeBottom: f32;\nuniform uBandWidth: f32;\nuniform uMode: f32;\nuniform uEdgeTint: vec3f;\nuniform uTime: f32;\n\nvar<private> g_visibility: f32;\n\nfn hash(p: vec3f) -> f32 {\n return fract(sin(dot(p, vec3f(127.1, 311.7, 74.7))) * 43758.5453);\n}\n\nfn initWipe(center: vec3f) {\n let wipeY = mix(uniform.uWipeTop, uniform.uWipeBottom, uniform.uWipeProgress);\n let noise = (hash(center) - 0.5) * uniform.uBandWidth * 0.15;\n let threshold = wipeY + noise;\n let halfBand = uniform.uBandWidth * 0.5;\n\n // Signed distance from threshold, accounting for sweep direction\n let sweepDir = sign(uniform.uWipeTop - uniform.uWipeBottom);\n let dist = (center.y - threshold) * sweepDir;\n let raw = smoothstep(-halfBand, halfBand, dist);\n\n if (uniform.uMode > 0.0) {\n g_visibility = raw;\n } else {\n g_visibility = 1.0 - raw;\n }\n}\n\nfn modifySplatCenter(center: ptr<function, vec3f>) {\n initWipe(*center);\n}\n\nfn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr<function, vec4f>, scale: ptr<function, vec3f>) {\n if (g_visibility < 0.01) {\n *scale = vec3f(0.0);\n return;\n }\n\n if (g_visibility >= 0.99) {\n return;\n }\n\n let origSize = gsplatGetSizeFromScale(*scale);\n let t = g_visibility;\n\n if (t < 0.3) {\n let dotSize = min(t * 0.15 * origSize, origSize);\n gsplatMakeSpherical(scale, dotSize);\n } else {\n *scale *= t;\n }\n}\n\nfn wipeColorEffect(center: vec3f, color: ptr<function, vec4f>) {\n if (g_visibility < 0.01 || g_visibility >= 0.99) {\n return;\n }\n\n let edgeIntensity = sin(g_visibility * 3.14159);\n (*color) = vec4f((*color).rgb + uniform.uEdgeTint * edgeIntensity * 0.4, (*color).a);\n}\n\nfn modifySplatColor(center: vec3f, color: ptr<function, vec4f>) {\n wipeColorEffect(center, color);\n}\n",_applyShaders(){const t=this.entity.gsplat;if(t)if(t.unified){if(this.material=this.app.scene.gsplat?.material??null,!this.material)return void(this._shadersNeedApplication=!0);this._applyShaderToMaterial(this.material)}else{const e=t.material;e?(this.material=e,this._applyShaderToMaterial(e)):this._shadersNeedApplication=!0}},_applyShaderToMaterial(t){const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl";let o="wgsl"===n?this.getShaderWGSL():this.getShaderGLSL();const i=this.entity.script&&this.entity.script.gsplatRelighting;if(i&&i.enabled){const t="wgsl"===n?O:V;o="wgsl"===n?t+"\n"+o.replace(/fn modifySplatColor\(center: vec3f, color: ptr<function, vec4f>\) \{\s*wipeColorEffect\(center, color\);\s*\}/,"fn modifySplatColor(center: vec3f, color: ptr<function, vec4f>) {\n applyRelighting(center, color);\n wipeColorEffect(center, color);\n}"):t+"\n"+o.replace(/void modifySplatColor\(vec3 center, inout vec4 color\) \{\s*wipeColorEffect\(center, color\);\s*\}/,"void modifySplatColor(vec3 center, inout vec4 color) {\n applyRelighting(center, color);\n wipeColorEffect(center, color);\n}"),i._shaderManagedExternally=!0,console.log("[WipeTransition] Combined shader with relighting")}t.getShaderChunks(n).set("gsplatModifyVS",o),t.update()},_removeShaders(){if(!this.material)return;const t=this.entity.script&&this.entity.script.gsplatRelighting;if(t&&t.enabled){t._shaderManagedExternally=!1,t.material=this.material;const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl",o="wgsl"===n?t.getShaderWGSL():t.getShaderGLSL();return this.material.getShaderChunks(n).set("gsplatModifyVS",o),this.material.update(),this.material=null,void console.log("[WipeTransition] Handed material back to relighting")}const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl";this.material.getShaderChunks(n).delete("gsplatModifyVS"),this.material.update(),this.material=null},_setUniform(t,e){this.material?.setParameter(t,e)},destroy(){this._removeShaders()}}),Z=e,e}();s=e.script?.create?.(n)??void 0}s?(s.enabled=!1,s.mode=n,s.duration=Re,s.bandWidth=De,s.wipeTop=o?Ie:Fe,s.wipeBottom=o?Fe:Ie,s.onComplete=i||null,s.effectTime=0,n>0?s.edgeTint.set(.2,.6,1):s.edgeTint.set(1,.4,.1),s.enabled=!0):i&&i()}function $e(e,n,o){if(!ze)return void(o&&o());e.script||e.addComponent("script");let i=e.script?.gsplatDissolveTransition;if(!i){const n=function(){if(Q)return Q;const e=t.createScript("gsplatDissolveTransition");return Object.assign(e.prototype,{effectTime:0,material:null,_effectInitialized:!1,_shadersNeedApplication:!1,_edgeTintArray:[0,0,0],mode:1,duration:1,edgeTint:null,onComplete:null,initialize(){this.effectTime=0,this.material=null,this._effectInitialized=!1,this._shadersNeedApplication=!1,this._edgeTintArray=[0,0,0],this.edgeTint||(this.edgeTint=new t.Color(.3,.5,.8)),this.on("enable",()=>{this.effectTime=0,this._effectInitialized?this._applyShaders():this._shadersNeedApplication=!0}),this.on("disable",()=>{this._removeShaders()}),this.entity.gsplat&&(this._effectInitialized=!0,this.enabled&&this._applyShaders())},update(t){if(this._effectInitialized){if(this._shadersNeedApplication&&(this._applyShaders(),this.material&&(this._shadersNeedApplication=!1)),this.material){if(this.effectTime+=t,Math.min(this.effectTime/this.duration,1)>=1){const t=this.entity.script&&this.entity.script.gsplatRelighting;if(t&&t.enabled){if(this.onComplete){const t=this.onComplete;this.onComplete=null,t()}return}const e=this.onComplete;return this.enabled=!1,void(e&&e())}this._updateUniforms(),this.material.update()}}else this.entity.gsplat&&(this._effectInitialized=!0,this.enabled&&this._shadersNeedApplication&&(this._applyShaders(),this.material&&(this._shadersNeedApplication=!1)))},_updateUniforms(){const t=Math.min(this.effectTime/this.duration,1);this._setUniform("uDissolveProgress",t),this._setUniform("uMode",this.mode),this._setUniform("uTime",this.effectTime),this._edgeTintArray[0]=this.edgeTint.r,this._edgeTintArray[1]=this.edgeTint.g,this._edgeTintArray[2]=this.edgeTint.b,this._setUniform("uEdgeTint",this._edgeTintArray)},getShaderGLSL:()=>"\nuniform float uDissolveProgress; // 0.0 = start, 1.0 = fully dissolved\nuniform float uMode; // 1.0 = dissolve in (reveal), -1.0 = dissolve out (hide)\nuniform vec3 uEdgeTint; // Color tint at dissolve edge\nuniform float uTime; // Animation time\n\n// Per-splat shared state\nfloat g_visibility; // 0 = hidden, 1 = visible\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 initDissolve(vec3 center) {\n // Each splat gets a random threshold (0-1) based on its position\n float noise = hash(center);\n // Softness controls how gradual each individual splat's transition is\n float softness = 0.08;\n\n if (uMode > 0.0) {\n // Dissolve in: splats appear as progress exceeds their noise threshold\n g_visibility = smoothstep(noise - softness, noise + softness, uDissolveProgress);\n } else {\n // Dissolve out: splats disappear as progress exceeds their noise threshold\n g_visibility = 1.0 - smoothstep(noise - softness, noise + softness, uDissolveProgress);\n }\n}\n\nvoid modifySplatCenter(inout vec3 center) {\n initDissolve(center);\n}\n\nvoid modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) {\n if (g_visibility < 0.01) {\n scale = vec3(0.0);\n return;\n }\n\n if (g_visibility >= 0.99) {\n return;\n }\n\n // Transition: shrink splats as they dissolve\n float origSize = gsplatGetSizeFromScale(scale);\n float t = g_visibility;\n\n if (t < 0.2) {\n // Nearly dissolved: tiny spherical dots\n float dotSize = min(t * 0.2 * origSize, origSize);\n gsplatMakeSpherical(scale, dotSize);\n } else {\n scale *= t;\n }\n}\n\nvoid dissolveColorEffect(vec3 center, inout vec4 color) {\n if (g_visibility < 0.01 || g_visibility >= 0.99) return;\n\n // Tint at the dissolve edge - strongest in the middle of the transition\n float edgeIntensity = sin(g_visibility * 3.14159);\n color.rgb += uEdgeTint * edgeIntensity * 0.25;\n}\n\nvoid modifySplatColor(vec3 center, inout vec4 color) {\n dissolveColorEffect(center, color);\n}\n",getShaderWGSL:()=>"\nuniform uDissolveProgress: f32;\nuniform uMode: f32;\nuniform uEdgeTint: vec3f;\nuniform uTime: f32;\n\nvar<private> g_visibility: f32;\n\nfn hash(p: vec3f) -> f32 {\n return fract(sin(dot(p, vec3f(127.1, 311.7, 74.7))) * 43758.5453);\n}\n\nfn initDissolve(center: vec3f) {\n let noise = hash(center);\n let softness: f32 = 0.08;\n\n if (uniform.uMode > 0.0) {\n g_visibility = smoothstep(noise - softness, noise + softness, uniform.uDissolveProgress);\n } else {\n g_visibility = 1.0 - smoothstep(noise - softness, noise + softness, uniform.uDissolveProgress);\n }\n}\n\nfn modifySplatCenter(center: ptr<function, vec3f>) {\n initDissolve(*center);\n}\n\nfn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr<function, vec4f>, scale: ptr<function, vec3f>) {\n if (g_visibility < 0.01) {\n *scale = vec3f(0.0);\n return;\n }\n\n if (g_visibility >= 0.99) {\n return;\n }\n\n let origSize = gsplatGetSizeFromScale(*scale);\n let t = g_visibility;\n\n if (t < 0.2) {\n let dotSize = min(t * 0.2 * origSize, origSize);\n gsplatMakeSpherical(scale, dotSize);\n } else {\n *scale *= t;\n }\n}\n\nfn dissolveColorEffect(center: vec3f, color: ptr<function, vec4f>) {\n if (g_visibility < 0.01 || g_visibility >= 0.99) {\n return;\n }\n\n let edgeIntensity = sin(g_visibility * 3.14159);\n (*color) = vec4f((*color).rgb + uniform.uEdgeTint * edgeIntensity * 0.25, (*color).a);\n}\n\nfn modifySplatColor(center: vec3f, color: ptr<function, vec4f>) {\n dissolveColorEffect(center, color);\n}\n",_applyShaders(){const t=this.entity.gsplat;if(t)if(t.unified){if(this.material=this.app.scene.gsplat?.material??null,!this.material)return void(this._shadersNeedApplication=!0);this._applyShaderToMaterial(this.material)}else{const e=t.material;e?(this.material=e,this._applyShaderToMaterial(e)):this._shadersNeedApplication=!0}},_applyShaderToMaterial(t){const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl";let o="wgsl"===n?this.getShaderWGSL():this.getShaderGLSL();const i=this.entity.script&&this.entity.script.gsplatRelighting;if(i&&i.enabled){const t="wgsl"===n?O:V;o="wgsl"===n?t+"\n"+o.replace(/fn modifySplatColor\(center: vec3f, color: ptr<function, vec4f>\) \{\s*dissolveColorEffect\(center, color\);\s*\}/,"fn modifySplatColor(center: vec3f, color: ptr<function, vec4f>) {\n applyRelighting(center, color);\n dissolveColorEffect(center, color);\n}"):t+"\n"+o.replace(/void modifySplatColor\(vec3 center, inout vec4 color\) \{\s*dissolveColorEffect\(center, color\);\s*\}/,"void modifySplatColor(vec3 center, inout vec4 color) {\n applyRelighting(center, color);\n dissolveColorEffect(center, color);\n}"),i._shaderManagedExternally=!0}t.getShaderChunks(n).set("gsplatModifyVS",o),t.update()},_removeShaders(){if(!this.material)return;const t=this.entity.script&&this.entity.script.gsplatRelighting;if(t&&t.enabled){t._shaderManagedExternally=!1,t.material=this.material;const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl",o="wgsl"===n?t.getShaderWGSL():t.getShaderGLSL();return this.material.getShaderChunks(n).set("gsplatModifyVS",o),this.material.update(),void(this.material=null)}const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl";this.material.getShaderChunks(n).delete("gsplatModifyVS"),this.material.update(),this.material=null},_setUniform(t,e){this.material?.setParameter(t,e)},destroy(){this._removeShaders()}}),Q=e,e}();i=e.script?.create?.(n)??void 0}i?(i.enabled=!1,i.mode=n,i.duration=Be,i.onComplete=o||null,i.effectTime=0,n>0?i.edgeTint.set(.3,.5,.8):i.edgeTint.set(.8,.4,.2),i.enabled=!0):o&&o()}const Ve=d.swapTransitionType||"dissolve";function Oe(t,e,n){"scanline"===Ve?function(t,e,n=!0){ze?(Ue(t,-1,n,()=>{t.enabled=!1,console.log("[WipeTransition] Old splat hidden after wipe")}),Ue(e,1,n),console.log(`[WipeTransition] Dual wipe swap started (${n?"forward":"backward"})`)):t.enabled=!1}(t,e,n):function(t,e){ze?($e(t,-1,()=>{t.enabled=!1}),$e(e,1),console.log("[DissolveTransition] Cross-dissolve swap started")):t.enabled=!1}(t,e)}function We(t,e){"scanline"===Ve?Ue(t,1,e):$e(t,1)}function Ne(t){t.enabled=!1,console.log("[SplatSwap] Splat hidden")}function Ge(t){const e=gt.get(t);if(e){const n=e.script?.gsplatRelighting;n&&st.delete(n),e.destroy(),gt.delete(t),console.log("[SplatSwap] Splat disposed:",t)}}function He(t){const e=gt.get(t);return!!e&&(e.enabled=!0,ut=t,console.log("[SplatSwap] Splat shown:",t),!0)}function Xe(){if(ut){const t=gt.get(ut);if(t&&t.enabled)return t}return et&&et.enabled?et:null}async function qe(e){if(!gt.has(e)&&e!==ut&&!lt){console.log("[SplatSwap] Preloading splat:",e);try{const n=new t.Asset("splat-preload-"+Date.now(),"gsplat",{url:e});await new Promise((o,i)=>{n.ready(()=>{if(lt)return void i(new Error("Viewer destroyed"));const s=new t.Entity("splat-preload");s.addComponent("gsplat",{asset:n});const a=d.scale||{x:1,y:1,z:1},r=d.invertXScale||!1,l=d.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=d.position||[0,0,0];s.setPosition(p[0],p[1],-p[2]);const h=d.rotation||[0,0,0],u=[h[0]*(180/Math.PI),h[1]*(180/Math.PI),-h[2]*(180/Math.PI)];s.setEulerAngles(u[0],u[1],u[2]),s.enabled=!1,U.root.addChild(s),Ao(s),gt.set(e,s),console.log("[SplatSwap] Preload complete:",e),o()}),n.on("error",t=>{console.error("[SplatSwap] Preload error:",t),i(t)}),U.assets.add(n),U.assets.load(n)})}catch(t){console.error("[SplatSwap] Error preloading:",e,t)}}}function je(t,e=!1){if("explore"!==Lt)return;const n=e?ht:t||ht;if(Et){Et.mode!==n&&(Et.setMode(n),console.log(`[SplatSwap] Switching explore sub-mode to: ${n}`),Ee(n),N?x(F,"fly"===n):(S(F,"fly"===n),_(F,"orbit"===n)),E(F))}}async function Ye(e,n=!0){if(e!==ut&&!mt&&!lt){mt=!0,console.log(`[SplatSwap] Switching to splat (${n?"fwd":"bwd"}):`,e);try{const o=Xe();if(gt.has(e)){He(e),i.emit("splatChange",{url:e,isOriginal:!1});const t=gt.get(e);o&&o!==t?Oe(o,t,n):We(t,n)}else{const s=new t.Asset("splat-swap-"+Date.now(),"gsplat",{url:e});await new Promise((a,r)=>{s.ready(()=>{if(lt)return void r(new Error("Viewer destroyed"));const l=new t.Entity("splat-swap");l.addComponent("gsplat",{asset:s});const c=d.scale||{x:1,y:1,z:1},p=d.invertXScale||!1,h=d.invertYScale||!1,u={x:p?-c.x:c.x,y:h?c.y:-c.y,z:p!==h?c.z:-c.z};l.setLocalScale(u.x,u.y,u.z);const m=d.position||[0,0,0];l.setPosition(m[0],m[1],-m[2]);const g=d.rotation||[0,0,0],f=[g[0]*(180/Math.PI),g[1]*(180/Math.PI),-g[2]*(180/Math.PI)];l.setEulerAngles(f[0],f[1],f[2]),U.root.addChild(l),Ao(l),gt.set(e,l),ut=e,o?Oe(o,l,n):We(l,n),i.emit("splatChange",{url:e,isOriginal:!1}),console.log("[SplatSwap] New splat loaded and shown:",e),a()}),s.on("error",t=>{console.error("[SplatSwap] Load error:",t),r(t)}),U.assets.add(s),U.assets.load(s)})}const s=dt.findIndex(t=>t.url===e);-1!==s&&async function(t){if(!dt||0===dt.length)return;const e=(t+1)%dt.length,n=dt[e];n&&n.url&&await qe(n.url)}(s)}catch(t){console.error("[SplatSwap] Error switching splat:",t)}finally{mt=!1}}}function Ze(){return d.sogUrl?d.sogUrl:d.splatUrl?d.splatUrl:d.fallbackUrls&&d.fallbackUrls.length>0?d.fallbackUrls[0]:""}function Qe(){const t=Ze();if(ut===t)return;const e=Xe();et&&(et.enabled=!0,e&&e!==et?(Oe(e,et,!1),gt.forEach((n,o)=>{o!==t&&n!==e&&(pt?Ne(n):Ge(o))})):(We(et,!1),gt.forEach((e,n)=>{n!==t&&(pt?Ne(e):Ge(n))}))),ut=t,xt=0,i.emit("splatChange",{url:t,isOriginal:!0}),console.log("[SplatSwap] Manually returned to original splat"),je(void 0,!0)}function Ke(t,e=0){console.log("[SplatSwap] Applying skybox:",t,"rotation:",e),vo(t,e)}let Je=0,tn=0,en=!1;const nn=d.waypoints?.reduce((t,e)=>t+(e.duration||2e3),0)||1,on=d.waypoints?.length||1,sn=Math.max(1,20*(on-1));let an=void 0!==d.autoplaySpeed?60*d.autoplaySpeed/sn:1e3/nn;const rn=d.loopMode;let ln;ln=!0===rn?"loop":!1===rn?"none":"loop"===rn||"pingpong"===rn||"none"===rn?rn:"loop";let cn=1;console.log("[StorySplat Viewer] Playback config:",{loopMode:ln,playbackSpeed:an,totalDuration:nn,autoPlay:d.autoPlay,autoplaySpeed:d.autoplaySpeed,rawLoopMode:d.loopMode});let dn=null,pn=null;const hn=.01+.1*(d.transitionSpeed||1);let un=0,mn=0;let gn=!1,fn=!1,yn=0,vn=0;const xn=[],bn=[],wn=[],Sn=d.fov||60;let _n=Sn;function Mn(n){if(!Ot||xn.length<2)return;n=Math.max(0,Math.min(1,n));const o=xn.length,s=n*(o-1),a=Math.min(Math.floor(s),o-2),r=s-a,l=xn[Math.max(a-1,0)],c=xn[a],p=xn[a+1],h=xn[Math.min(a+2,o-1)],u=r*r,m=u*r;dn=new t.Vec3(.5*(2*c.x+(-l.x+p.x)*r+(2*l.x-5*c.x+4*p.x-h.x)*u+(-l.x+3*c.x-3*p.x+h.x)*m),.5*(2*c.y+(-l.y+p.y)*r+(2*l.y-5*c.y+4*p.y-h.y)*u+(-l.y+3*c.y-3*p.y+h.y)*m),.5*(2*c.z+(-l.z+p.z)*r+(2*l.z-5*c.z+4*p.z-h.z)*u+(-l.z+3*c.z-3*p.z+h.z)*m));const g=bn[a],f=bn[a+1];pn=new t.Quat,pn.slerp(g,f,r);const y=wn[a],v=wn[a+1];_n=Ut(y,v,r);const x=Math.round(n*(o-1));if(x!==K){const n=K;K=x;const o=d.waypoints[x],s=o.cameraMode||"first-person";"orbit"===s&&(o.orbitTarget?new t.Vec3(o.orbitTarget.x,o.orbitTarget.y,-(o.orbitTarget.z||0)):new t.Vec3(xn[x].x,xn[x].y,xn[x].z)),i.emit("waypointChange",{index:x,waypoint:o,prevIndex:n,cameraMode:s}),b=x,w=n,qn.forEach((t,e)=>{const{entity:n,waypointIndex:o,config:i,slotId:s,autoplayTriggered:a}=t,r=n.sound?.slot(s);if(!r)return;const l=w===o&&b!==o;b===o&&w!==o&&i.autoplay&&!a&&(console.log(`[Audio] Autoplay waypoint audio: ${e} at waypoint ${o}`),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))}),jn(),Yn(e);const a=e.querySelector(".storysplat-hotspot-popup");a&&a.classList.remove("visible")}var b,w;i.emit("progressUpdate",{progress:Math.max(0,Math.min(1,n)),index:K}),function(){if(!dt||0===dt.length)return;const t=d.waypoints?.length||1,e=100*Je,n=Math.round(Je*Math.max(1,t-1));if(Math.abs(e-ft)<.1&&n===yt)return;ft=e,yt=n;let o=null,s=null,a=-1/0,r=-1/0;for(const t of dt)-1!==t.waypointIndex?n>=t.waypointIndex&&t.waypointIndex>a&&(a=t.waypointIndex,o=t):-1!==t.percentage&&e>=t.percentage&&t.percentage>r&&(r=t.percentage,s=t);const l=s||o,c=l&&"__ORIGINAL__"===l.url,p=Ze(),h=l?c?p:l.url:p;if(h&&h!==ut){const t=e>=xt;if(xt=e,h===p&&et&&!ut)ut=p;else if(h===p&&et){const e=Xe();et.enabled=!0,ut=p,e&&e!==et?(Oe(e,et,t),gt.forEach((t,n)=>{n!==p&&t!==e&&(pt?Ne(t):Ge(n))})):(We(et,t),gt.forEach((t,e)=>{e!==p&&(pt?Ne(t):Ge(e))})),i.emit("splatChange",{url:p,isOriginal:!0}),console.log("[SplatSwap] Returned to primary splat"),je(void 0,!0)}else Ye(h,t),l&&je(l.defaultExploreMode,!1);l&&l.skyboxUrl&&!c?Ke(l.skyboxUrl,l.skyboxRotation||0):c&&mo&&Ke(mo,go)}}()}function En(t,e=!1){const n=Math.max(0,Math.min(1,t));tn=n,e?Tn(n):(Je=n,Mn(Je))}d.waypoints&&d.waypoints.length>0&&(d.waypoints.forEach(e=>{const n=e.position?new t.Vec3(e.position.x,e.position.y,-e.position.z):new t.Vec3(0,d.playerHeight||1.6,0);xn.push(n);const o=e.rotation?ke(e.rotation):new t.Quat;bn.push(o);let i=e.fov||Sn;i<3.5&&(i*=180/Math.PI),wn.push(i)}),xn.length>0&&(dn=xn[0].clone(),pn=bn[0].clone(),_n=wn[0])),U.on("update",function(){if(!Ot)return;if(!en){const t=tn-Je;Math.abs(t)>1e-4&&(Je+=t*hn,Mn(Je))}if(!dn||!pn)return;const e=bt.getPosition(),n=new t.Vec3;n.lerp(e,dn,hn),bt.setPosition(n.x,n.y,n.z);const o=bt.camera;if(o&&wn.length>0){const t=Ut(o.fov,_n,hn);o.fov=t}gn||(un*=.95,mn*=.95,Math.abs(un)<.01&&(un=0),Math.abs(mn)<.01&&(mn=0));const i=new t.Quat;i.setFromEulerAngles(mn,un,0);const s=new t.Quat;s.mul2(pn,i);const a=bt.getRotation(),r=new t.Quat;r.slerp(a,s,hn),bt.setRotation(r)});const Cn=500*(d.transitionSpeed||1);function Tn(t,e=Cn){const n=Je,o=performance.now();en=!0;const i=()=>{const s=performance.now()-o,a=Math.min(s/e,1);Je=n+(t-n)*(a<.5?2*a*a:(4-2*a)*a-1),Mn(Je),a<1?requestAnimationFrame(i):en=!1};requestAnimationFrame(i)}function An(t){if(!d.waypoints||t<0||t>=d.waypoints.length)return;if(!Ot)return;const e=t/Math.max(1,d.waypoints.length-1);tn=e,Tn(e)}function Pn(){if(!d.waypoints||0===d.waypoints.length)return;const t=d.scrollButtonMode||"waypoint";if("percentage"===t||"continuous"===t||"incremental"===t){const t=d.scrollAmount||10;let e=Je+t/100;e>1&&(e="loop"===ln?0:1),tn=e,Tn(e)}else{let t=K+1;t>=d.waypoints.length&&(t="loop"===ln?0:d.waypoints.length-1),An(t)}}function Ln(){if(!d.waypoints||0===d.waypoints.length)return;const t=d.scrollButtonMode||"waypoint";if("percentage"===t||"continuous"===t||"incremental"===t){const t=d.scrollAmount||10;let e=Je-t/100;e<0&&(e="loop"===ln?1:0),tn=e,Tn(e)}else{let t=K-1;t<0&&(t="loop"===ln?d.waypoints.length-1:0),An(t)}}let kn=0,zn=null;function Rn(t){if(!tt)return;0===kn&&(kn=t);const e=(t-kn)/1e3;kn=t;const n=an*e*cn;if(Je+=n,tn+=n,Je>=1)switch(console.log("[StorySplat Viewer] End of tour reached, loopMode:",ln),ln){case"loop":console.log("[StorySplat Viewer] Looping - restarting at beginning"),Je=0,tn=0;break;case"pingpong":console.log("[StorySplat Viewer] Pingpong - reversing direction"),Je=1,tn=1,cn=-1;break;case"none":return console.log("[StorySplat Viewer] No loop - stopping playback"),Je=1,tn=1,In(),void i.emit("playbackComplete");default:return console.log("[StorySplat Viewer] Unknown loopMode, stopping:",ln),Je=1,tn=1,In(),void i.emit("playbackComplete")}else if(Je<=0)if("pingpong"===ln)console.log("[StorySplat Viewer] Pingpong - reversing to forward"),Je=0,tn=0,cn=1;else Je=0,tn=0;Mn(Je),zn=requestAnimationFrame(Rn)}function Dn(){if(ct)return ct.play(),void i.emit("playbackStart");tt||!d.waypoints||d.waypoints.length<2||(tt=!0,kn=0,cn=1,i.emit("playbackStart"),zn=requestAnimationFrame(Rn))}function In(){if(ct)return ct.pause(),void i.emit("playbackStop");tt=!1,zn&&(cancelAnimationFrame(zn),zn=null),i.emit("playbackStop")}const Fn=U.graphicsDevice.canvas;Fn.addEventListener("wheel",t=>{if(!Ot)return;t.preventDefault();const e=d.scrollSpeed||.1,n=d.scrollAmount||100,o=d.waypoints?.length||2,i=Math.max(20,20*(o-1)),s=100*(Math.abs(t.deltaY)/100)*e*(n/100)/i,a=t.deltaY>0?s:-s;tn=Math.max(0,Math.min(1,tn+a))},{passive:!1}),Fn.addEventListener("pointerdown",t=>{Ot&&!fn&&(gn=!0,yn=t.clientX,vn=t.clientY)},{capture:!0}),Fn.addEventListener("pointermove",t=>{if(!Ot||!gn||fn)return;const e=t.clientX-yn,n=t.clientY-vn;yn=t.clientX,vn=t.clientY;un+=.3*-e,mn+=.3*-n,mn=Math.max(-60,Math.min(60,mn))},{capture:!0}),Fn.addEventListener("pointerup",()=>{gn=!1},{capture:!0}),Fn.addEventListener("pointerleave",()=>{gn=!1},{capture:!0});const Bn=[],Un=[],$n=F.portalPopup;let Vn=null;const On=()=>{$n&&($n.classList.remove("visible"),Vn=null)};if($n){const t=$n.querySelector(".storysplat-portal-popup-confirm"),e=$n.querySelector(".storysplat-portal-popup-cancel");t?.addEventListener("click",()=>{if(Vn){const t=Vn;On(),fi(t)}}),e?.addEventListener("click",()=>{On()})}function Wn(e){const n=parseInt(e.slice(1,3),16)/255,o=parseInt(e.slice(3,5),16)/255,i=parseInt(e.slice(5,7),16)/255;return new t.Color(n,o,i)}const Nn=[],Gn=[];let Hn=!1;const Xn=new Map,qn=new Map;function jn(){Bn.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 Yn(t){const e=t.querySelector(".storysplat-hotspot-popup");if(!e)return;e.querySelectorAll("video").forEach(t=>{t.pause(),t.currentTime=0}),e.querySelectorAll("iframe").forEach(t=>{t.src=""});const n=e.__modelViewerCleanup;n&&(n(),delete e.__modelViewerCleanup)}const Zn=new Map,Qn=new Map,Kn={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 Jn(e,n){return new Promise((o,i)=>{if(Qn.has(e))return void o(Qn.get(e));const s=new t.Asset(e,"texture",{url:n});s.on("load",()=>{if(lt)return console.log(`[Particle] Ignoring texture load - viewer was destroyed: ${e}`),void i(new Error("Viewer destroyed"));const t=s.resource;Qn.set(e,t),o(t)}),s.on("error",t=>{console.error(`[Particle] Failed to load texture: ${e}`,t),i(t)}),U.assets.add(s),U.assets.load(s)})}function to(e){const n=new t.Entity(e.name||"particle-system"),o=(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=e.emitterPosition?o(e.emitterPosition,0):{x:e.position?.x??0,y:e.position?.y??0,z:e.position?.z??0},a=o(e.gravity,0),r=i(e.color1),l=i(e.color2),c=i(e.colorDead),d=o(e.direction1,0),p=o(e.direction2,0),h=e.maxLifeTime??3;let u=t.EMITTERSHAPE_BOX,m=new t.Vec3(.1,.1,.1),g=new t.Vec3(0,0,0);if("sphere"===e.emitterType||"sphere"===e.emitterShape){u=t.EMITTERSHAPE_SPHERE;const n=e.emitterRadius||.1;m=new t.Vec3(n,n,n)}else if("box"===e.emitterShape&&e.emitBoxMin&&e.emitBoxMax){u=t.EMITTERSHAPE_BOX;const n=o(e.emitBoxMin,0),i=o(e.emitBoxMax,0);m=new t.Vec3(Math.abs(i.x-n.x),Math.abs(i.y-n.y),Math.abs(i.z-n.z)),g=new t.Vec3((n.x+i.x)/2,(n.y+i.y)/2,-(n.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||.01,e.emitterExtents?.y||e.emitterRadius||.01,e.emitterExtents?.z||e.emitterRadius||.01);let f=t.BLEND_ADDITIVE;const y=e.blendMode||"";"BLENDMODE_STANDARD"===y||"normal"===y||"alpha"===y?f=t.BLEND_NORMAL:"BLENDMODE_MULTIPLY"===y||"multiply"===y?f=t.BLEND_MULTIPLICATIVE:"BLENDMODE_ONEONE"===y||"BLENDMODE_ADD"===y?f=t.BLEND_ADDITIVE:"BLENDMODE_MULTIPLYADD"===y&&(f=t.BLEND_ADDITIVEALPHA);const v=a.x||0,x=a.y||0,b=-(a.z||0),w=v*h,S=x*h,_=b*h,M=180/Math.PI,E=(e.minAngularSpeed??0)*M,C=(e.maxAngularSpeed??e.angularSpeed??0)*M,T=(e.minInitialRotation??0)*M,A=(e.maxInitialRotation??0)*M,P=.1*(e.minSize??.1),L=.1*(e.maxSize??.5),k=e.minScaleX??1,z=e.maxScaleX??1,R=e.minScaleY??1,D=e.maxScaleY??1,I=e.minEmitPower??1,F=e.maxEmitPower??2,B=d.x,U=d.y,$=-d.z,V=p.x,O=p.y,W=-p.z,N=u===t.EMITTERSHAPE_SPHERE,G=e.emitRate||e.rate||50,H=1/G,X=Math.ceil(G*h*2);n.addComponent("particlesystem",{numParticles:e.numParticles||Math.max(X,100),lifetime:h,rate:H,emitterShape:u,emitterExtents:m,emitterRadius:e.emitterRadius||.1,startAngle:T,startAngle2:A,...N?{radialSpeedGraph:new t.Curve([0,I]),radialSpeedGraph2:new t.Curve([0,F])}:{localVelocityGraph:new t.CurveSet([[0,B*I],[0,U*I],[0,$*I]]),localVelocityGraph2:new t.CurveSet([[0,V*F],[0,O*F],[0,W*F]])},velocityGraph:new t.CurveSet([[0,0,1,w],[0,0,1,S],[0,0,1,_]]),scaleGraph:new t.Curve([0,P*Math.min(k,R)]),scaleGraph2:new t.Curve([0,L*Math.max(z,D)]),rotationSpeedGraph:new t.Curve([0,E]),...C!==E?{rotationSpeedGraph2:new t.Curve([0,C])}:{},colorGraph:new t.CurveSet([[0,r.r,.95,l.r,1,c.r],[0,r.g,.95,l.g,1,c.g],[0,r.b,.95,l.b,1,c.b]]),alphaGraph:new t.Curve([0,r.a??1,.95,l.a??1,1,c.a??0]),blendType:f,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}),n.particlesystem&&(n.particlesystem.localSpace=e.localSpace??!1),n.setPosition(s.x+g.x,s.y+g.y,-s.z+g.z);const q=e.renderingGroupId??3;return n.particlesystem&&void 0!==q&&(n.particlesystem.drawOrder=q),console.log(`[Particle] Entity configured at position: (${s.x+g.x}, ${s.y+g.y}, ${-s.z+g.z})`),console.log(`[Particle] Emitter shape: ${e.emitterShape||"box"}, extents: ${m.x}, ${m.y}, ${m.z}`),console.log(`[Particle] Gravity: (${v}, ${x}, ${b}), Lifetime: ${h}s`),console.log(`[Particle] Colors: start=${JSON.stringify(r)}, mid=${JSON.stringify(l)}, dead=${JSON.stringify(c)}`),console.log(`[Particle] Angular speed: ${E.toFixed(1)} - ${C.toFixed(1)} deg/s, Initial rotation: ${T.toFixed(1)} - ${A.toFixed(1)} deg`),console.log(`[Particle] Scale: ${P}*${k} - ${L}*${D}, EmitPower: ${I}-${F}, RenderingGroupId: ${q}`),console.log(`[Particle] Rate: ${G} particles/sec → PC rate=${H.toFixed(4)}s/particle, numParticles=${e.numParticles||Math.max(X,100)}`),n}const eo=new Map;function no(t){const{type:e,component:n,node:o,animations:i}=t;console.log("[CustomMesh] Playing animation, type:",e,"node:",o?.name,"animations:",i?.length);try{if("pc-anim"===e){if(console.log("[CustomMesh] Using simple pc-anim approach (like HTML export)"),!n)return;n.playing=!0,t.isPlaying=!0,console.log("[CustomMesh] Animation started - playing:",n.playing)}else if("animation"===e){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"===e){console.log("[CustomMesh] Starting GLB skeleton animation playback");const{modelEntity:e,animations:n}=t;if(!e)return;if(n&&n.length>0)try{if(e.anim||e.addComponent("anim",{activate:!0,speed:1}),e.anim){const o=e.anim;n.forEach((t,e)=>{const n=t.resource||t,i=n._name||n.name||t._name||t.name||`Anim_${e}`;console.log("[CustomMesh] Assigning animation track:",i,"type:",typeof n);try{o.assignAnimation?.(i,n)}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&&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 oo(t){const{type:e,component:n}=t;if(n||"glb-skeleton"===e)try{"pc-anim"===e&&n?(n.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&&(U.off("update",t.updateHandler),t.updateHandler=null,console.log("[CustomMesh] GLB manual animation paused"))):n&&(n.playing=!1,n.speed=0,"animation"===e&&"function"==typeof n.pause&&n.pause())}catch(t){console.error("[CustomMesh] Error pausing animation:",t)}}function io(e,n){if(console.log("[CustomMesh] Loading mesh",n,":",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 o=new t.Entity("custom-mesh-"+n),i=e.position||{x:0,y:0,z:0};if(o.setPosition(i._x??i.x??0,i._y??i.y??0,-(i._z??i.z??0)),e.rotation){const t=e.rotation;o.setRotation(ri(t._x??t.x??0,t._y??t.y??0,t._z??t.z??0))}if(e.scale){const t=e.scale;o.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-"+n,"container",{url:s});U.assets.add(a);const r=e.id||`mesh-${n}`,l={entity:o,config:e,modelAsset:a,isAnimPlaying:!1,audioPlaying:!1};return eo.set(r,l),a.ready(n=>{try{if(!eo.has(r))return void console.log("[CustomMesh] Entity destroyed before model loaded, skipping:",e.name);if(console.log("[CustomMesh] Model loaded:",e.name),!n||!n.resource)return void console.error("[CustomMesh] Invalid asset resource for mesh:",e.name);const i=n.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)})}o.addChild(s),o.modelEntity=s,a(s);let c=0;function d(e){const n=e.render;if(n&&n.meshInstances)for(const t of n.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&&d(e)})}if(s.forEach(()=>{c++}),console.log("[CustomMesh] Enabled all children for:",e.name,"- Total nodes:",c),d(s),"animated"===e.opacityMode&&e.opacityAnimation){const h=e.opacityAnimation.startOpacity??1;co(o,h),console.log("[CustomMesh] Applied initial animated opacity:",h,"to",e.name)}else void 0!==e.opacity&&e.opacity<1&&(co(o,e.opacity),console.log("[CustomMesh] Applied static opacity:",e.opacity,"to",e.name));if(e.billboard){const u=o.getEulerAngles().clone();o._billboardActive=!e.billboardRange,U.on("update",()=>{if(!o.enabled)return;if(!o._billboardActive)return void o.setEulerAngles(u.x,u.y,u.z);const t=bt.getPosition(),e=o.getPosition(),n=t.x-e.x,i=t.z-e.z,s=Math.atan2(n,i)*(180/Math.PI),a=o.getEulerAngles();o.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 m=[],g=s.anim;if(g&&(console.log("[CustomMesh] Found modelEntity.anim component - using simple approach"),m.push({type:"pc-anim",component:g,modelEntity:s})),0===m.length){function f(e,n=0){e.anim&&!m.find(t=>t.component===e.anim)&&(m.push({type:"anim",component:e.anim,node:e}),console.log("[CustomMesh] Found existing ANIM component on:",e.name)),e.animation&&(m.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&&f(e,n+1)})}f(s)}if(0===m.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{m.push({type:"glb-skeleton",modelEntity:s,asset:n,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(v){console.error("[CustomMesh] Error setting up GLB animations:",v)}}if(m.length>0){l.allAnimComponents=m,l.animComponent=m[0].component,console.log("[CustomMesh] Total animation components for",e.name,":",m.length,"- Types:",m.map(t=>t.type).join(", "));const x=e.interaction?.animationAutoPlay;x&&(m.forEach(t=>{no(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,n,o){const i=n.interaction;if(!i)return;const s=n.id||n.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}}}),o.audioSlotId=a;const r=new t.Asset(`mesh-audio-asset-${s}`,"audio",{url:i.audioUrl});U.assets.add(r),r.ready(()=>{const t=e.sound?.slot(a);t&&(t.asset=r.id),console.log("[CustomMesh] Audio loaded for mesh:",n.name,"Spatial:",i.audioSpatial)}),r.on("error",t=>{console.error("[CustomMesh] Failed to load mesh audio:",n.name,t)}),U.assets.load(r)}(o,e,l),e.interaction&&function(e,n,o){if(!function(t){const e=t.interaction;return!!e&&!!(e.triggerUIPopup||e.playAudio||e.playModelAnimation||e.triggerDirectLink)}(n))return void console.log("[CustomMesh] Skipping interaction setup - no active interactions for:",n.name);const i=n.interaction?.activationMode||"click",s=U.graphicsDevice.canvas,a=(n,o)=>{const i=s.getBoundingClientRect(),a=n-i.left,r=o-i.top,l=bt.camera.screenToWorld(a,r,bt.camera.nearClip),c=bt.camera.screenToWorld(a,r,bt.camera.farClip),d=(new t.Vec3).sub2(c,l).normalize(),p=e.modelEntity;if(p&&so(p,l,d))return!0;if(so(e,l,d))return!0;const h=e.getPosition(),u=(new t.Vec3).sub2(h,l).dot(d);if(u>0){const n=(new t.Vec3).add2(l,d.clone().mulScalar(u)).distance(h),o=e.getLocalScale();if(n<1.5*Math.max(o.x,o.y,o.z))return!0}return!1},r=n.interaction?.popupTriggerMode||i,l=n.interaction?.audioTriggerMode||i,c=n.interaction?.animationTriggerMode||i,d=n.interaction?.directLinkTriggerMode||i;let p=!1;const h=()=>{if(s.style.cursor="pointer",Ai=!0,ue=!0,"hover"===r&&n.interaction?.triggerUIPopup&&ro(n),"hover"===l&&n.interaction?.playAudio&&o.audioSlotId){const t=e.sound?.slot(o.audioSlotId);t&&!t.isPlaying&&(t.play(),o.audioPlaying=!0,console.log("[CustomMesh] Playing audio on hover for:",n.name))}if("hover"===c&&n.interaction?.playModelAnimation){const t=o.allAnimComponents;t&&t.length>0&&!o.isAnimPlaying&&(t.forEach(t=>no(t)),o.isAnimPlaying=!0,console.log("[CustomMesh] Playing animation on hover for:",n.name))}"hover"===d&&n.interaction?.triggerDirectLink&&lo(n)},u=()=>{if(s.style.cursor="",Ai=!1,ue=!1,"hover"===r&&n.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&&n.interaction?.playAudio&&o.audioSlotId){const t=e.sound?.slot(o.audioSlotId);t&&t.isPlaying&&(t.stop(),o.audioPlaying=!1,console.log("[CustomMesh] Stopped audio on hover out for:",n.name))}if("hover"===c&&n.interaction?.playModelAnimation){const t=o.allAnimComponents;t&&t.length>0&&o.isAnimPlaying&&(t.forEach(t=>oo(t)),o.isAnimPlaying=!1,console.log("[CustomMesh] Paused animation on hover out for:",n.name))}},m=()=>{if(console.log("[CustomMesh] Clicked mesh:",n.name),"click"===r&&n.interaction?.triggerUIPopup&&ro(n),"click"===c&&n.interaction?.playModelAnimation){const t=o.allAnimComponents;t&&t.length>0&&(o.isAnimPlaying?(t.forEach(t=>oo(t)),o.isAnimPlaying=!1,console.log("[CustomMesh] Paused",t.length,"animations on click for:",n.name)):(t.forEach(t=>no(t)),o.isAnimPlaying=!0,console.log("[CustomMesh] Playing",t.length,"animations on click for:",n.name)))}if("click"===l&&n.interaction?.playAudio&&o.audioSlotId){const t=e.sound?.slot(o.audioSlotId);t&&(t.isPlaying?(t.pause(),o.audioPlaying=!1,console.log("[CustomMesh] Paused audio on click for:",n.name)):(t.play(),o.audioPlaying=!0,console.log("[CustomMesh] Playing audio on click for:",n.name)))}"click"===d&&n.interaction?.triggerDirectLink&&lo(n)},g=t=>{a(t.clientX,t.clientY)&&m()},f=t=>{const e=a(t.clientX,t.clientY);e&&!p?(p=!0,h()):!e&&p&&(p=!1,u())},y=()=>{p&&(p=!1,u())};s.addEventListener("click",g),s.addEventListener("mousemove",f),s.addEventListener("mouseleave",y),e.meshClickHandler=g,e.meshHoverHandler=f,e.meshLeaveHandler=y}(o,e,l),console.log("[CustomMesh] Mesh fully initialized:",e.name)}catch(b){console.error("[CustomMesh] Error processing loaded mesh:",e.name,b)}}),a.on("error",t=>{console.error("[CustomMesh] Failed to load mesh:",e.name,t),U.assets.remove(a)}),U.assets.load(a),e.visibilityRange?(o.visibilityRange=e.visibilityRange,o.enabled=!1!==e.enabled):o.enabled=!1!==e.enabled,o.opacityConfig={mode:e.opacityMode||"static",value:void 0!==e.opacity?e.opacity:1},U.root.addChild(o),o}function so(e,n,o){const i=e.render;if(i&&i.meshInstances)for(const t of i.meshInstances)if(t.aabb){if(ao(n,o,t.aabb))return!0}const s=e.model;if(s&&s.meshInstances)for(const t of s.meshInstances)if(t.aabb){if(ao(n,o,t.aabb))return!0}const a=e.children;if(a)for(const e of a)if(e instanceof t.Entity&&so(e,n,o))return!0;return!1}function ao(t,e,n){const o=n.getMin?n.getMin():n.min,i=n.getMax?n.getMax():n.max;if(!o||!i)return!1;let s=-1/0,a=1/0;const r=["x","y","z"],l=(t,e,n)=>{const o=t[e];if("number"==typeof o)return o;const i=t[`_${e}`];return"number"==typeof i?i:t.data?.[n]??0};for(let n=0;n<3;n++){const c=r[n],d=t[c],p=e[c],h=l(o,c,n),u=l(i,c,n);if(Math.abs(p)<1e-8){if(d<h||d>u)return!1}else{let t=(h-d)/p,e=(u-d)/p;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 ro(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"};F.showHotspotPopup?F.showHotspotPopup(e):function(t){const e=document.querySelector(".storysplat-mesh-popup");e&&e.remove();const n=document.createElement("div");n.className="storysplat-mesh-popup",n.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 o=document.createElement("h2");if(o.textContent=t.name||"Custom Mesh",o.style.cssText="margin-top: 0; margin-bottom: 16px;",n.appendChild(o),t.interaction?.popupContent){const e=document.createElement("p");e.textContent=t.interaction.popupContent,e.style.cssText="margin: 0; line-height: 1.6;",n.appendChild(e)}const i=document.createElement("button");i.textContent=`× ${l(P,"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=()=>n.remove(),n.appendChild(i),document.body.appendChild(n)}(t)}function lo(t){t.interaction?.triggerDirectLink&&t.interaction.directLinkUrl&&window.open(t.interaction.directLinkUrl,"_blank")}function co(e,n){!function e(o){o.render&&o.render.meshInstances&&o.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=n,e.material.blendType=t.BLEND_PREMULTIPLIED,e.material.depthTest=!0,e.material.depthWrite=!0,e.material.alphaTest=.01,e.material.update()}}),o.children.forEach(n=>{n instanceof t.Entity&&e(n)})}(e)}function po(){const t=U.graphicsDevice.canvas;eo.forEach(e=>{const n=e.entity,o=n.meshClickHandler;o&&t.removeEventListener("click",o),n.destroy()}),eo.clear()}i.on("progressUpdate",()=>{!function(){const t=100*Je,e=d.waypoints?.length||1,n=Math.round(Je*Math.max(1,e-1));eo.forEach(e=>{const{entity:o,config:i}=e,s=o.visibilityRange;if(s){let e=!0;"waypoint"===s.type?e=n>=s.start&&n<=s.end:"percentage"===s.type&&(e=t>=s.start&&t<=s.end),o.enabled=e}if("animated"===i.opacityMode&&i.opacityAnimation){const e=i.opacityAnimation;if(t>=e.startPercent&&t<=e.endPercent){const n=(t-e.startPercent)/(e.endPercent-e.startPercent);co(o,e.startOpacity+(e.endOpacity-e.startOpacity)*n)}}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=n>=e.start&&n<=e.end),o._billboardActive=s}else i.billboard&&(o._billboardActive=!0)})}()});let ho=null,uo=null;const mo=d.skybox?.url||d.skyboxUrl||null,go=d.skybox?.rotation??d.skyboxRotation??0;function fo(){const e=d.skybox?.url||d.skyboxUrl;if(!e)return void console.log("[StorySplat Viewer] No skybox configured");const n=d.skybox?.rotation??d.skyboxRotation??0,o=d.skybox?.intensity??1,i=d.skybox?.enableIBL??!0;console.log("[StorySplat Viewer] Creating skybox:",e,"rotation:",n,"rad =",n*(180/Math.PI),"deg","IBL:",i),yo();const s=new Image;s.crossOrigin="anonymous",s.onload=()=>{if(!lt)try{const e=new t.Texture(U.graphicsDevice,{width:s.width,height:s.height,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,projection:t.TEXTUREPROJECTION_EQUIRECT});e.setSource(s);const a=Math.min(s.width/4,2048),r=new t.Texture(U.graphicsDevice,{width:a,height:a,cubemap:!0,mipmaps:!1,format:t.PIXELFORMAT_RGBA8,minFilter:t.FILTER_LINEAR,magFilter:t.FILTER_LINEAR});t.reprojectTexture(e,r,{numSamples:1024});const l=a,c=new t.Texture(U.graphicsDevice,{width:l,height:l,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});if(t.EnvLighting.generateAtlas(r,{target:c}),U.scene.envAtlas=c,U.scene.skyboxMip=0,U.scene.skyboxIntensity=o,0!==n){const e=new t.Quat;e.setFromEulerAngles(0,-n*(180/Math.PI),0),U.scene.skyboxRotation=e}i&&(U.scene.ambientLight=new t.Color(.3*o,.3*o,.35*o),console.log("[StorySplat Viewer] IBL ambient lighting applied:",o)),console.log("[StorySplat Viewer] Native skybox applied successfully")}catch(e){console.warn("[StorySplat Viewer] Native skybox pipeline failed, using sphere fallback:",e),function(e,n,o,i,s){const a=new t.Entity("skybox-fallback"),r=new t.StandardMaterial;r.diffuse=new t.Color(0,0,0),r.emissive=new t.Color(1,1,1),r.emissiveIntensity=1,r.specular=new t.Color(0,0,0),r.cull=t.CULLFACE_NONE,r.depthTest=!1,r.depthWrite=!1,r.update();const l=new t.Texture(U.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});l.setSource(n),r.emissiveMap=l,r.emissiveIntensity=i,r.update(),a.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1}),a.render.material=r,a.render.meshInstances.length>0&&(a.render.meshInstances[0].drawOrder=-1e4,a.render.meshInstances[0].pick=!1);a.setLocalScale(-500,500,500),0!==o&&a.setEulerAngles(0,-o*(180/Math.PI),0);uo=()=>{const t=bt.getPosition();a.setPosition(t.x,t.y,t.z)},U.on("update",uo),U.root.addChild(a),ho=a,s&&(U.scene.ambientLight=new t.Color(.3*i,.3*i,.35*i));console.log("[StorySplat Viewer] Skybox fallback sphere applied")}(0,s,n,o,i)}},s.onerror=t=>{console.error("[StorySplat Viewer] Failed to load skybox texture:",e,t)},s.src=e}function yo(){U.scene.envAtlas=null,ho&&(ho.destroy(),ho=null),uo&&(U.off("update",uo),uo=null)}function vo(t,e){yo(),t&&(d.skybox={url:t,rotation:e??0},d.skyboxUrl=t,d.skyboxRotation=e??0,fo())}const xo=[];function bo(e){const n=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return n?new t.Color(parseInt(n[1],16)/255,parseInt(n[2],16)/255,parseInt(n[3],16)/255):new t.Color(1,1,1)}function wo(e){const n=new t.Entity(e.name||"Point Light");n.addComponent("light",{type:"point",color:bo(e.color||"#ffffff"),intensity:e.intensity??1,range:e.range||10,castShadows:e.castShadows||!1});const o=e.position;return o&&n.setPosition(o._x??o.x??0,o._y??o.y??0,-(o._z??o.z??0)),n.enabled=!1!==e.enabled,n}function So(e){const n=new t.Entity(e.name||"Directional Light");n.addComponent("light",{type:"directional",color:bo(e.color||"#ffffff"),intensity:e.intensity??1,castShadows:e.castShadows||!1});const o=e.position;o&&n.setPosition(o._x??o.x??0,o._y??o.y??0,-(o._z??o.z??0));const i=e.rotation;if(i)n.setRotation(ri(i._x??i.x??0,i._y??i.y??0,i._z??i.z??0));else if(e.direction){const o=e.direction,i=new t.Vec3(o._x??o.x??0,o._y??o.y??-1,-(o._z??o.z??0)).normalize();n.lookAt(n.getPosition().x+i.x,n.getPosition().y+i.y,n.getPosition().z+i.z)}else n.setEulerAngles(45,0,0);return n.enabled=!1!==e.enabled,n}function _o(e){const n=new t.Entity(e.name||"Hemispheric Light");n.addComponent("light",{type:"directional",color:bo(e.color||"#ffffff"),intensity:e.intensity??1,castShadows:!1});const o=e.position;if(o&&n.setPosition(o._x??o.x??0,o._y??o.y??0,-(o._z??o.z??0)),n.setEulerAngles(-90,0,0),n.enabled=!1!==e.enabled,e.groundColor){const n=bo(e.groundColor),o=bo(e.color||"#ffffff");U.scene.ambientLight=new t.Color((o.r+.3*n.r)/1.3,(o.g+.3*n.g)/1.3,(o.b+.3*n.b)/1.3)}return n}function Mo(e){const n=bo(e.color||"#404040"),o=e.intensity??.4;return U.scene.ambientLight=new t.Color(n.r*o,n.g*o,n.b*o),console.log("[StorySplat Viewer] Set ambient light:",e.name||"Ambient Light"),null}function Eo(e){const n=new t.Entity(e.name||"Spot Light"),o=180/Math.PI,i=(e.angle??45*Math.PI/180)*o;e.exponent;const s=e.innerConeAngle??e.innerAngle??.8*i,a=e.outerConeAngle??e.outerAngle??i;n.addComponent("light",{type:"spot",color:bo(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&&n.setPosition(r._x??r.x??0,r._y??r.y??0,-(r._z??r.z??0));const l=e.rotation;if(l)n.setRotation(ri(l._x??l.x??0,l._y??l.y??0,l._z??l.z??0));else if(e.direction){const o=e.direction,i=new t.Vec3(o._x??o.x??0,o._y??o.y??-1,-(o._z??o.z??0)).normalize();n.lookAt(n.getPosition().x+i.x,n.getPosition().y+i.y,n.getPosition().z+i.z)}else n.setEulerAngles(90,0,0);return n.enabled=!1!==e.enabled,n}const Co=[];function To(){if(!et)return;const t=d.splatRelighting,e=xo.length>0;if(console.log("[StorySplat Viewer] initSplatRelighting:",{relightConfig:t,hasLights:e,lightCount:xo.length,splatEntity:!!et}),!e)return void console.log("[StorySplat Viewer] No lights, skipping splat relighting");et.script||et.addComponent("script");const n=X(),o=et.script;if(it=o?.create?.(n)??null,it){const e=0,n=t?.ambientColor||"#ffffff";it.setAmbientColor(n),it.ambientR*=e,it.ambientG*=e,it.ambientB*=e;let o=0;!0===t?.enabled&&(o=t?.allowViewerToggle?t?.viewerDefaultOn?1:0:1),it.setRelightFade(o),it.enabled=!0,at=!0,st.add(it),console.log("[StorySplat Viewer] Splat relighting initialized, startFade:",o)}}function Ao(t){if(!at||!it)return;if(t.script?.gsplatRelighting)return;t.script||t.addComponent("script");const e=X(),n=t.script,o=n?.create?.(e)??null;o&&(o.ambientR=it.ambientR,o.ambientG=it.ambientG,o.ambientB=it.ambientB,o.relightFade=it.relightFade,o.enabled=!0,st.add(o),console.log("[Relighting] Added relighting script to swap entity:",t.name))}let Po=!1;let Lo=null,ko=null;function zo(t){for(const e of xo){if(!e.light)continue;"directional"===e.light.type&&(e.light.castShadows=t,t&&(e.light.shadowResolution=2048,e.light.shadowBias=.05,e.light.normalOffsetBias=.05,e.light.shadowDistance=40))}eo.forEach(e=>{Ro(e.entity,t)})}function Ro(e,n){e.render&&(e.render.castShadows=n);for(const o of e.children)o instanceof t.Entity&&Ro(o,n)}function Do(){Lo&&(Lo.destroy(),Lo=null),ko=null}function Io(){const e=d.splatRelighting;if(e?.shadowsEnabled){!function(e,n,o){if(Lo)return Lo.setLocalPosition(0,e,0),Lo.setLocalScale(n,1,n),void(ko&&(ko.opacity=o,ko.update()));ko=new t.StandardMaterial,ko.shadowCatcher=!0,ko.blendType=t.BLEND_MULTIPLICATIVE,ko.depthWrite=!1,ko.useSkybox=!1,ko.diffuse.set(0,0,0),ko.specular.set(0,0,0),ko.opacity=o,ko.update(),Lo=new t.Entity("ShadowCatcher"),Lo.addComponent("render",{type:"plane",material:ko,castShadows:!1,receiveShadows:!0}),Lo.setLocalPosition(0,e,0),Lo.setLocalScale(n,1,n);const i=Lo.render?.meshInstances;if(i)for(const t of i)t.drawBucket=250;U.root.addChild(Lo),console.log("[StorySplat Viewer] Shadow catcher created at Y:",e,"scale:",n)}(e.shadowGroundY??0,e.shadowPlaneScale??50,e.shadowIntensity??.4),zo(!0)}else Do(),zo(!1)}const Fo=[[1,.2,.1],[.1,.6,1],[.2,1,.3],[1,.8,.1],[.8,.2,1],[1,.4,.7],[.1,1,.9]],Bo=[],Uo=[];let $o=!1;const Vo=9.8,Oo=.6,Wo=.15;function No(e){if(0===Uo.length)for(const[e,n,o]of Fo){const i=new t.StandardMaterial;i.diffuse.set(0,0,0),i.specular.set(0,0,0),i.emissive.set(e,n,o),i.emissiveIntensity=4,i.useLighting=!1,i.update(),Uo.push(i)}return Uo[e%Uo.length]}const Go=[[.85,.85,.88],[.83,.69,.22],[.72,.45,.2],[.56,.57,.58],[.66,.66,.68]],Ho=[];function Xo(e){if(0===Ho.length)for(const[e,n,o]of Go){const i=new t.StandardMaterial;i.diffuse.set(e,n,o),i.specular.set(1,1,1),i.metalness=.9,i.gloss=.8,i.useMetalness=!0,i.update(),Ho.push(i)}return Ho[e%Ho.length]}function qo(){return Ct?Ct.collisionMeshEntities:Et._collisionEntities??[]}function jo(e,n){let o=null;for(const t of qo()){const i=t.getPosition(),s=t.getLocalScale(),a=t._collisionBounds;let r,l,c,d,p,h;if(a?(r=a.center.x,l=a.center.y,c=a.center.z,d=a.halfExtents.x,p=a.halfExtents.y,h=a.halfExtents.z):(r=i.x,l=i.y,c=i.z,d=s.x/2,p=s.y/2,h=s.z/2),Math.abs(e.x-r)<d+n&&Math.abs(e.z-c)<h+n){const t=l+p;(null===o||t>o)&&(o=t)}}const i=Ct?.voxelCollisionInstance;if(i&&et){const s=new t.Mat4;s.copy(et.getWorldTransform()).invert();const a=new t.Vec3;s.transformPoint(e,a);const r=i.findGround(a.x,a.z,a.y+2*n);if(null!==r){const t=et.getWorldTransform().data,e=t[1]*a.x+t[5]*r+t[9]*a.z+t[13];(null===o||e>o)&&(o=e)}}else if(i){const t=i.findGroundWorld(e.x,e.z,e.y,2*n);null!==t&&(null===o||t>o)&&(o=t)}return o}function Yo(e,n,o){for(const t of qo()){const i=t.getPosition(),s=t.getLocalScale(),a=t._collisionMeshType;if("floor"===a||"plane"===a)continue;const r=t._collisionBounds;let l,c,d,p,h,u;r?(l=r.center.x,c=r.center.y,d=r.center.z,p=r.halfExtents.x,h=r.halfExtents.y,u=r.halfExtents.z):(l=i.x,c=i.y,d=i.z,p=s.x/2,h=s.y/2,u=s.z/2);const m=e.x-l,g=e.y-c,f=e.z-d,y=p+o-Math.abs(m),v=h+o-Math.abs(g),x=u+o-Math.abs(f);y>0&&v>0&&x>0&&(y<=v&&y<=x?(e.x+=Math.sign(m)*y,n.x=-n.x*Oo):x<=v?(e.z+=Math.sign(f)*x,n.z=-n.z*Oo):(e.y+=Math.sign(g)*v,n.y=g>0?Math.abs(n.y)*Oo:-Math.abs(n.y)*Oo))}const i=Ct?.voxelCollisionInstance;if(i){let s,a,r,l,c,d;if(et){const n=new t.Mat4;n.copy(et.getWorldTransform()).invert();const o=new t.Vec3;n.transformPoint(e,o),s=o.x,a=o.y,r=o.z}else s=e.x,a=-e.y,r=-e.z;const p=i.querySphere(s,a,r,o);if(p){if(et){const t=et.getWorldTransform().data;l=t[0]*p.x+t[4]*p.y+t[8]*p.z,c=t[1]*p.x+t[5]*p.y+t[9]*p.z,d=t[2]*p.x+t[6]*p.y+t[10]*p.z}else l=p.x,c=-p.y,d=-p.z;e.x+=l,e.y+=c,e.z+=d;const t=Math.sqrt(l*l+c*c+d*d);if(t>1e-6){const e=l/t,o=c/t,i=d/t,s=n.x*e+n.y*o+n.z*i;s<0&&(n.x-=(1+Oo)*s*e,n.y-=(1+Oo)*s*o,n.z-=(1+Oo)*s*i)}}}}function Zo(t){const e=xo.indexOf(t.entity);e>=0&&xo.splice(e,1),t.entity.destroy()}function Qo(){if(!$o){if($o=!0,!at&&et){d.splatRelighting={...d.splatRelighting,enabled:!0},To();for(const t of st)t.setRelightFade(1)}console.log("[Playground] Started — press F to spawn light balls, relightingEnabled:",at,"splatEntity:",!!et)}}function Ko(){if($o){$o=!1;for(const t of Bo)Zo(t);Bo.length=0,console.log("[Playground] Stopped")}}window.__storysplat_playground_handler&&window.removeEventListener("keydown",window.__storysplat_playground_handler);const Jo=e=>{e.shiftKey&&"KeyP"===e.code?(e.preventDefault(),$o?Ko():Qo()):"KeyF"===e.code&&$o?(console.log("[Playground] F pressed — spawning light ball"),function(){Bo.length>=16&&Zo(Bo.shift());const e=Math.floor(Math.random()*Fo.length),[n,o,i]=Fo[e],s=bt.getPosition(),a=bt.forward,r=new t.Vec3(s.x+2*a.x,s.y+2*a.y,s.z+2*a.z);console.log("[Playground] Spawning ball #"+(Bo.length+1),"cam:",s.x.toFixed(2),s.y.toFixed(2),s.z.toFixed(2),"fwd:",a.x.toFixed(2),a.y.toFixed(2),a.z.toFixed(2),"spawn:",r.x.toFixed(2),r.y.toFixed(2),r.z.toFixed(2),"color:",[n.toFixed(1),o.toFixed(1),i.toFixed(1)],"collisionEntities:",qo().length,"lightEntities:",xo.length,"relightingEnabled:",at);const l=new t.Entity("playground-ball");l.addComponent("render",{type:"sphere",material:No(e),castShadows:!!d.splatRelighting?.shadowsEnabled}),l.setLocalScale(.3,.3,.3),l.addComponent("light",{type:"point",color:new t.Color(n,o,i),intensity:2,range:8,castShadows:!1}),l.setPosition(r),U.root.addChild(l),xo.push(l);const c=new t.Vec3(8*a.x+2*(Math.random()-.5),8*a.y+3,8*a.z+2*(Math.random()-.5));Bo.push({entity:l,velocity:c,age:0,lifetime:12,baseIntensity:2,radius:Wo})}()):"KeyG"===e.code&&$o&&function(){d.splatRelighting?.shadowsEnabled||(console.log("[Playground] Auto-enabling shadows for shadow ball testing"),d.splatRelighting={...d.splatRelighting,shadowsEnabled:!0},Io()),Bo.length>=16&&Zo(Bo.shift());const e=Math.floor(Math.random()*Go.length),n=bt.getPosition(),o=bt.forward,i=new t.Vec3(n.x+2*o.x,n.y+2*o.y,n.z+2*o.z);console.log("[Playground] G pressed — spawning shadow ball at",i.x.toFixed(2),i.y.toFixed(2),i.z.toFixed(2));const s=new t.Entity("playground-shadow-ball");s.addComponent("render",{type:"sphere",material:Xo(e),castShadows:!0}),s.setLocalScale(.3,.3,.3),s.setPosition(i),U.root.addChild(s);const a=new t.Vec3(8*o.x+2*(Math.random()-.5),8*o.y+3,8*o.z+2*(Math.random()-.5));Bo.push({entity:s,velocity:a,age:0,lifetime:12,baseIntensity:0,radius:Wo})}()};function ti(t){switch(t){case"linear":return"linear";case"inverse":return"inverse";default:return"exponential"}}window.__storysplat_playground_handler=Jo,window.addEventListener("keydown",Jo),console.log("[Playground] Toggle registered — Shift+P to start/stop, F = light ball, G = shadow ball");const ei=new Map;const ni=new Set;function oi(){Hn||(Gn.forEach(t=>{Xn.set(t,t.volume),t.volume=0}),qn.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);e&&(e._storedVolume=e.volume,e.volume=0)}),ei.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}),Hn=!0,console.log("[Audio] All audio muted"))}function ii(){Hn&&(Gn.forEach(t=>{const e=Xn.get(t);t.volume=void 0!==e?e:1}),qn.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);if(e){const t=e._storedVolume;e.volume=void 0!==t?t:1}}),ei.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}),Hn=!1,console.log("[Audio] All audio unmuted"))}const si=[];function ai(e,n=!1,o=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=.005,n?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=o,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 o=new vt(U,e,{autoPlay:!0,onReady:()=>{if(lt)return console.log(`[Hotspot] Ignoring GIF load - viewer was destroyed: ${e}`),void o.destroy();o.texture&&(o.texture.premultiplyAlpha=!0,n?(s.diffuseMap=o.texture,s.opacityMap=o.texture):(s.emissiveMap=o.texture,s.opacityMap=o.texture),s.opacityMapChannel="a",s.update(),console.log(`[Hotspot] GIF texture loaded: ${e}, useLighting=${n}`),i&&i())},onError:n=>{console.error(`[Hotspot] Failed to load GIF: ${e}`,n),s.opacity=.3,s.emissive=new t.Color(1,0,0),s.update(),i&&i()}});si.push(o)}else{const o=new Image;o.crossOrigin="anonymous",o.onload=()=>{if(lt)return void console.log(`[Hotspot] Ignoring texture load - viewer was destroyed: ${e}`);const a=new t.Texture(U.graphicsDevice,{width:o.width,height:o.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.premultiplyAlpha=!0,a.setSource(o),n?(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=${n}`),i&&i()},o.onerror=n=>{console.error(`[Hotspot] Failed to load texture: ${e}`,n),s.opacity=.3,s.emissive=new t.Color(1,0,0),s.update(),i&&i()},o.src=e}return s}function ri(e,n,o){const i=n/2,s=e/2,a=o/2,r=Math.cos(i),l=Math.sin(i),c=Math.cos(s),d=Math.sin(s),p=Math.cos(a),h=Math.sin(a),u=r*c*p+l*d*h,m=r*d*p+l*c*h,g=l*c*p-r*d*h,f=r*c*h-l*d*p;return new t.Quat(-m,-g,f,u)}function li(e,n,o,i){const s=(new t.Quat).setFromEulerAngles(90,0,0),a=ri(n,o,i);e.setRotation((new t.Quat).mul2(a,s))}function ci(e,n){const o=new t.Entity(`hotspot-${e.id||n}`),i=e.position||{_x:0,_y:0,_z:0};o.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=a._x??a.x??1,l=a._y??a.y??1,c=a._z??a.z??1,d=e.rotation||{_x:0,_y:0,_z:0},p=d._x??d.x??0,h=d._y??d.y??0,u=d._z??d.z??0;if(o.setRotation(ri(p,h,u)),"sphere"===e.type){o.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1});const n=new t.StandardMaterial,i=Wn(e.color||"#ffffff");n.diffuse=i,n.emissive=i.clone(),n.emissive.mulScalar(.5);const s=e.opacity??1;s>=.95?(n.opacity=1,n.blendType=t.BLEND_NONE,n.depthTest=!0,n.depthWrite=!0):(n.opacity=s,n.blendType=t.BLEND_ADDITIVEALPHA,n.depthTest=!0,n.depthWrite=!0),n.update(),o.render.material=n,o.setLocalScale(.2*r,.2*l,.2*c)}else if("image"===e.type&&e.imageUrl){o.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),o.targetOpacity=t,o.textureLoaded=!1,o.hiddenUntilTextureLoaded=!0,o.enabled=!1;const n=!0===e.useLighting,s=ai(e.imageUrl,n,t,()=>{o.textureLoaded=!0,o.visibilityRange&&!o.shouldBeVisible||(o.enabled=!0),o.hiddenUntilTextureLoaded=!1});o.render.material=s,o.setLocalScale(r,c,l),li(o,p,h,u),o.hotspotMaterial=s,console.log(`[Hotspot] Created image hotspot: ${e.title}, pos=(${(i._x??i.x??0).toFixed(2)}, ${(i._y??i.y??0).toFixed(2)}, ${(i._z??i.z??0).toFixed(2)}), rot=(${p.toFixed(3)}, ${h.toFixed(3)}, ${u.toFixed(3)}), scale=(${r.toFixed(3)}, ${l.toFixed(3)}, ${c.toFixed(3)}), opacity=${t}`)}else if("video"===e.type&&e.videoUrl){o.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,d=(t=>{const e=t.toLowerCase();return e.endsWith(".webm")||e.includes("format=webm")||e.includes("video/webm")})(s)&&!1!==e.webmHasAlpha,m=(n,o,i=!1)=>{const s=document.createElement("video");s.src=n,s.loop=!1!==e.videoLoop,s.crossOrigin="anonymous",s.playsInline=!0,s.preload="metadata",s.muted=!!o||!1!==e.videoMuted,"autoplay"===e.mediaTriggerMode&&(s.autoplay=!0,s.muted=!0);const a=new t.Texture(U.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}},g=m(s,!1,d),f=g.video,y=g.texture;if(e.videoBackupUrl){const t=e.videoBackupUrl;f.onerror=()=>{console.log(`[Hotspot] Main video failed, trying backup: ${t}`),f.src=t,f.load()}}const v=new t.StandardMaterial;v.useLighting=!1,v.emissiveMap=y,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,d&&(v.opacityMap=y,v.blendType=t.BLEND_PREMULTIPLIED,v.alphaTest=.01,console.log(`[Hotspot] WebM video with alpha enabled: ${e.title}`));let x=null,b=null;if(a){const n=m(a,!0,!1);x=n.video,b=n.texture,v.opacityMap=b,v.opacityMapChannel="r",v.blendType=t.BLEND_PREMULTIPLIED,v.alphaTest=.01,f.addEventListener("play",()=>{x&&x.paused&&(x.currentTime=f.currentTime,x.play().catch(console.warn))}),f.addEventListener("pause",()=>{x&&!x.paused&&x.pause()}),f.addEventListener("seeked",()=>{x&&(x.currentTime=f.currentTime)}),console.log(`[Hotspot] iOS alpha mask video enabled: ${e.title}, alphaUrl=${a.substring(0,50)}...`)}v.update(),U.on("update",()=>{f.readyState===f.HAVE_ENOUGH_DATA&&y.upload(),x&&b&&x.readyState===x.HAVE_ENOUGH_DATA&&b.upload()});const w={x:r,y:l,z:c};if(f.addEventListener("loadedmetadata",()=>{const t=f.videoWidth,e=f.videoHeight;if(t>0&&e>0){const n=t/e;1===w.x&&1===w.y&&o.setLocalScale(n*w.y,w.z,w.y)}}),o.render.material=v,o.setLocalScale(r,c,l),li(o,p,h,u),o.videoElement=f,o.alphaVideoElement=x,o.hotspotMaterial=v,o.mediaTriggerMode=e.mediaTriggerMode||"click",o.proximityDistance=e.proximityDistance||5,o.isVideoPlaying=!1,"click"!==e.mediaTriggerMode&&e.mediaTriggerMode||(f.addEventListener("loadeddata",()=>{f.currentTime=0,y.upload(),x&&b&&(x.currentTime=0,b.upload()),console.log(`[Hotspot] First frame loaded for: ${e.title}`)},{once:!0}),f.load(),x&&x.load()),!0!==e.videoMuted){const t=function(t,e,n){if(!n.videoSpatialAudio&&!1!==n.videoMuted)return null;try{const o=new(window.AudioContext||window.webkitAudioContext);Nn.push(o);const i=o.createMediaElementSource(e),s=o.createPanner();s.panningModel="HRTF",s.distanceModel=n.videoDistanceModel||"linear",s.refDistance=void 0!==n.videoRefDistance?n.videoRefDistance:1,s.maxDistance=void 0!==n.videoMaxDistance?n.videoMaxDistance:100,s.rolloffFactor=void 0!==n.videoRolloffFactor?n.videoRolloffFactor:1;const a=t.getPosition();return s.setPosition(a.x,a.y,a.z),i.connect(s),s.connect(o.destination),U.on("update",()=>{if(!t||!t.getPosition)return;const e=t.getPosition();if(s.setPosition(e.x,e.y,e.z),bt&&bt.getPosition){const t=bt.getPosition(),e=bt.forward,n=bt.up;o.listener.positionX?(o.listener.positionX.value=t.x,o.listener.positionY.value=t.y,o.listener.positionZ.value=t.z,o.listener.forwardX.value=e.x,o.listener.forwardY.value=e.y,o.listener.forwardZ.value=e.z,o.listener.upX.value=n.x,o.listener.upY.value=n.y,o.listener.upZ.value=n.z):(o.listener.setPosition(t.x,t.y,t.z),o.listener.setOrientation(e.x,e.y,e.z,n.x,n.y,n.z))}}),console.log(`[Audio] Video spatial audio setup for hotspot: ${n.title}, refDist=${s.refDistance}, maxDist=${s.maxDistance}`),{audioCtx:o,source:i,panner:s}}catch(t){return console.warn("[Audio] Failed to setup video spatial audio:",t),null}}(o,f,e);t&&(o.videoSpatialAudio=t)}if("click"===e.mediaTriggerMode||!e.mediaTriggerMode||"autoplay"===e.mediaTriggerMode&&!1===e.videoMuted){const i=new t.Entity("video-overlay-"+(e.id||n));i.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1}),i.setLocalPosition(0,.02,0),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(U.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,o.addChild(i);const d=()=>{"autoplay"===e.mediaTriggerMode&&!1===e.videoMuted?i.enabled=f.muted:i.enabled=f.paused};f.addEventListener("play",d),f.addEventListener("pause",d),f.addEventListener("volumechange",d),i.enabled=!0,o.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=${!!o.videoSpatialAudio}`)}else if("gif"===e.type&&e.gifUrl){o.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1});let n=1;"animated"===e.opacityMode&&e.opacityAnimation?n=void 0!==e.opacityAnimation.startOpacity?e.opacityAnimation.startOpacity:1:void 0!==e.opacity&&(n=e.opacity),o.targetOpacity=n,o.textureLoaded=!1,o.hiddenUntilTextureLoaded=!0,o.enabled=!1;const i=!0===e.useLighting,s=new t.StandardMaterial;s.blendType=t.BLEND_PREMULTIPLIED,s.opacity=n,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=new vt(U,e.gifUrl,{autoPlay:!0,onReady:()=>{if(lt)a.destroy();else if(a.texture){a.texture.premultiplyAlpha=!0,i||(s.emissiveMap=a.texture),s.diffuseMap=a.texture,s.opacityMap=a.texture,s.opacityMapChannel="a",s.update(),o.render.material=s;const t=(a.texture.width||256)/(a.texture.height||256);1===r&&1===l?o.setLocalScale(t,c,1):o.setLocalScale(r,c,l),li(o,p,h,u),o.textureLoaded=!0,o.gifTexture=a.texture,o.hotspotMaterial=s,o.animatedGifTexture=a,o.visibilityRange&&!o.shouldBeVisible||(o.enabled=!0),o.hiddenUntilTextureLoaded=!1,console.log(`[Hotspot] Created GIF hotspot: ${e.title}, opacity=${n}, useLighting=${i}`)}},onError:n=>{console.error("[Hotspot] Failed to load GIF:",e.gifUrl,n),o.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1});const i=new t.StandardMaterial;i.diffuse=Wn(e.color||"#FF00FF"),i.opacity=.8,i.blendType=t.BLEND_NORMAL,i.update(),o.render.material=i,o.setLocalScale(.2*r,.2*l,.2*c),o.enabled=!0,o.hiddenUntilTextureLoaded=!1}});si.push(a)}else{o.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1});const n=new t.StandardMaterial,i=Wn(e.color||"#CC5833");n.diffuse=i,n.emissive=i.clone(),n.emissive.mulScalar(.5);const s=e.opacity??1;n.opacity=s,n.blendType=t.BLEND_ADDITIVEALPHA,n.depthTest=!0,n.depthWrite=!0,n.update(),o.render.material=n,o.setLocalScale(.2*r,.2*l,.2*c)}o.addComponent("collision",{type:"sphere"===e.type?"sphere":"box",radius:.1,halfExtents:new t.Vec3(.5,.5,.05)}),o.hotspotData=e;const m=function(t,e){if(!e.audioUrl)return null;const n=document.createElement("audio");if(n.src=e.audioUrl,n.loop=e.audioLoop||!1,n.volume=void 0!==e.audioVolume?e.audioVolume:1,n.crossOrigin="anonymous",Gn.push(n),e.audioSpatial){const o=new(window.AudioContext||window.webkitAudioContext);Nn.push(o);const i=o.createMediaElementSource(n),s=o.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(o.destination);const r=()=>{if(!t||!t.getPosition)return;const e=t.getPosition();if(s.setPosition(e.x,e.y,e.z),bt&&bt.getPosition){const t=bt.getPosition(),e=bt.forward,n=bt.up;o.listener.positionX?(o.listener.positionX.value=t.x,o.listener.positionY.value=t.y,o.listener.positionZ.value=t.z,o.listener.forwardX.value=e.x,o.listener.forwardY.value=e.y,o.listener.forwardZ.value=e.z,o.listener.upX.value=n.x,o.listener.upY.value=n.y,o.listener.upZ.value=n.z):(o.listener.setPosition(t.x,t.y,t.z),o.listener.setOrientation(e.x,e.y,e.z,n.x,n.y,n.z))}};return U.on("update",r),console.log(`[Audio] Spatial audio setup for hotspot: ${e.title}, refDist=${s.refDistance}, maxDist=${s.maxDistance}`),{audio:n,audioCtx:o,source:i,panner:s,updateAudioPosition:r}}return console.log(`[Audio] Non-spatial audio setup for hotspot: ${e.title}`),{audio:n}}(o,e);if(m&&(o.audioElements=m,console.log(`[StorySplat Viewer] Audio setup for hotspot: ${e.title||"Untitled"}`)),e.billboard){const n=o.render?.meshInstances?.[0]?.material;n&&(n.cull=t.CULLFACE_FRONT,n.update());const i=o.getEulerAngles().clone(),s=void 0!==e.billboardRangeStart||void 0!==e.billboardRangeEnd;o._billboardActive=!s,o._billboardOriginalRotation=i,U.on("update",()=>{o._billboardActive&&(o.lookAt(bt.getPosition()),o.rotateLocal(90,0,0))})}return e.visibilityRange&&(o.visibilityRange=e.visibilityRange,o.enabled=!1),U.root.addChild(o),Bn.push(o),console.log(`[StorySplat Viewer] Created hotspot: ${e.title||"Untitled"}`),o}function di(){const e=100*Je,n=d.waypoints?.length||1,o=Math.round(Je*Math.max(1,n-1)),i=bt.getPosition();Bn.forEach(n=>{const s=n.hotspotData;if(!s)return;let a=!0;if(s.alwaysVisible)a=!0;else{const t=n.visibilityRange;t&&("waypoint"===t.type?a=o>=t.start&&o<=t.end:"percentage"===t.type&&(a=e>=t.start&&e<=t.end))}if(n.shouldBeVisible=a,s.billboard&&(void 0!==s.billboardRangeStart||void 0!==s.billboardRangeEnd)){const o=s.billboardRangeStart??0,i=s.billboardRangeEnd??100,a=e>=o&&e<=i,r=n._billboardActive;if(n._billboardActive=a,a!==r){const e=n.render?.meshInstances?.[0]?.material;e&&(e.cull=a?t.CULLFACE_FRONT:t.CULLFACE_NONE,e.update())}if(!a&&n._billboardOriginalRotation){const t=n._billboardOriginalRotation;n.setEulerAngles(t.x,t.y,t.z)}}if(n.hiddenUntilTextureLoaded?n.enabled=!1:n.enabled=a,n.videoElement&&"video"===s.type){const t=n.mediaTriggerMode||"click";if("proximity"===t){const t=n.getPosition(),e=i.distance(t),o=n.proximityDistance||5;e<=o&&!n.isVideoPlaying?(pi(n,s),console.log(`[Hotspot] Proximity play: ${s.title}, distance=${e.toFixed(2)}`)):e>o&&n.isVideoPlaying&&(hi(n),console.log(`[Hotspot] Proximity pause: ${s.title}, distance=${e.toFixed(2)}`))}"scroll"===t&&(a&&!n.isVideoPlaying?(pi(n,s),console.log(`[Hotspot] Scroll play: ${s.title}, scroll=${e.toFixed(1)}%`)):!a&&n.isVideoPlaying&&(hi(n),console.log(`[Hotspot] Scroll pause: ${s.title}, scroll=${e.toFixed(1)}%`)))}if("animated"===s.opacityMode&&s.opacityAnimation){if("image"===s.type&&!n.textureLoaded)return;const t=s.opacityAnimation,o=t.startPercent??0,i=t.endPercent??100,a=void 0!==t.startOpacity?t.startOpacity:1,r=void 0!==t.endOpacity?t.endOpacity:1;let l;if(e<=o)l=a;else if(e>=i)l=r;else{l=a+(r-a)*((e-o)/(i-o))}if(l=Math.max(0,Math.min(1,l)),n.hotspotMaterial)n.hotspotMaterial.opacity=l,n.hotspotMaterial.update();else if(n.render&&n.render.material){const t=n.render.material;t.opacity=l,t.update?.()}}})}function pi(t,e){const n=t.videoElement,o=t.alphaVideoElement;if(n){if("autoplay"!==t.mediaTriggerMode&&(n.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))}n.play().catch(t=>console.warn("Video play failed:",t)),o&&o.play().catch(t=>console.warn("Alpha video play failed:",t)),t.isVideoPlaying=!0}}function hi(t){const e=t.videoElement,n=t.alphaVideoElement;e&&(e.pause(),n&&n.pause(),t.isVideoPlaying=!1)}function ui(e,n){const o=new t.Entity(`portal-${n}`),i=e.position||{_x:0,_y:0,_z:0};o.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,d=e.rotation||{_x:0,_y:0,_z:0},p=d._x??d.x??0,h=d._y??d.y??0,u=d._z??d.z??0;if(o.setRotation(ri(p,h,u)),"sphere"===e.type){o.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1});const n=new t.StandardMaterial,i=Wn(e.color||"#9C27B0");n.diffuse=i,n.emissive=i.clone(),n.emissive.mulScalar(.5);const s=e.opacity??.8;s>=.95?(n.opacity=1,n.blendType=t.BLEND_NONE,n.depthTest=!0,n.depthWrite=!0):(n.opacity=s,n.blendType=t.BLEND_ADDITIVEALPHA,n.depthTest=!0,n.depthWrite=!0),n.update(),o.render.material=n,o.setLocalScale(.2*r,.2*l,.2*c)}else if("image"===e.type&&e.imageUrl){o.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1});const t=!0===e.useLighting,n=e.opacity??1;o.textureLoaded=!1,o.hiddenUntilTextureLoaded=!0,o.enabled=!1;const i=ai(e.imageUrl,t,n,()=>{o.textureLoaded=!0,o.visibilityRange&&!o.shouldBeVisible||(o.enabled=!0),o.hiddenUntilTextureLoaded=!1});o.render.material=i,o.setLocalScale(r,c,l),li(o,p,h,u),o.portalMaterial=i,console.log(`[Portal] Created image portal: ${e.title||e.targetSceneName||"Untitled"}`)}else if("video"===e.type&&e.videoUrl){o.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1});const n=document.createElement("video");n.src=e.videoUrl,n.loop=!0,n.muted=!0,n.crossOrigin="anonymous",n.playsInline=!0,n.autoplay=!0;const i=new t.Texture(U.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(n);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(),o.render.material=s,o.setLocalScale(r,c,l),li(o,p,h,u),U.on("update",()=>{n.readyState>=n.HAVE_CURRENT_DATA&&i.setSource(n)}),n.play().catch(t=>console.log("[Portal] Video autoplay blocked:",t)),o.videoElement=n,o.portalMaterial=s,console.log(`[Portal] Created video portal: ${e.title||e.targetSceneName||"Untitled"}`)}else if("gif"===e.type&&e.gifUrl){o.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1});const t=!0===e.useLighting,n=e.opacity??1,i=ai(e.gifUrl,t,n,()=>{o.textureLoaded=!0,o.visibilityRange&&!o.shouldBeVisible||(o.enabled=!0),o.hiddenUntilTextureLoaded=!1});o.render.material=i,o.setLocalScale(r,c,l),li(o,p,h,u),o.textureLoaded=!1,o.hiddenUntilTextureLoaded=!0,o.enabled=!1,o.portalMaterial=i,console.log(`[Portal] Created GIF portal: ${e.title||e.targetSceneName||"Untitled"}`)}else{o.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1});const n=new t.StandardMaterial,i=Wn(e.color||"#9C27B0");n.diffuse=i,n.emissive=i.clone(),n.emissive.mulScalar(.5);const s=e.opacity??.8;n.opacity=s,n.blendType=t.BLEND_ADDITIVEALPHA,n.depthTest=!0,n.depthWrite=!0,n.update(),o.render.material=n,o.setLocalScale(.2*r,.2*l,.2*c)}if(o.addComponent("collision",{type:"sphere"===e.type?"sphere":"box",radius:.1,halfExtents:new t.Vec3(.5,.5,.05)}),o.portalData=e,e.billboard){const e=o.render?.meshInstances?.[0]?.material;e&&(e.cull=t.CULLFACE_FRONT,e.update()),U.on("update",()=>{o.enabled&&(o.lookAt(bt.getPosition()),o.rotateLocal(90,0,0))})}return e.visibilityRange&&(o.visibilityRange=e.visibilityRange,o.enabled=!1),U.root.addChild(o),Un.push(o),console.log(`[StorySplat Viewer] Created portal: ${e.title||e.targetSceneName||"Untitled"} -> ${e.targetSceneId}`),o}function mi(){const t=100*Je,e=d.waypoints?.length||1,n=Math.round(Je*Math.max(1,e-1)),o=bt.getPosition();Un.forEach(e=>{const i=e.portalData;if(!i)return;let s=!0;const a=e.visibilityRange;if(a&&("waypoint"===a.type?s=n>=a.start&&n<=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(),n=o.distance(t),s=i.proximityDistance||2;n<=s&&!e.proximityTriggered?(e.proximityTriggered=!0,console.log(`[Portal] Proximity triggered: ${i.title||i.targetSceneName}, navigating to scene ${i.targetSceneId}`),fi(i)):n>s&&(e.proximityTriggered=!1)}})}function gi(e,n){const o=bt.camera.screenToWorld(e,n,bt.camera.nearClip),i=bt.camera.screenToWorld(e,n,bt.camera.farClip);let s=null;Un.forEach(e=>{if(!e.enabled)return;const n=e.getPosition(),a=(new t.Vec3).sub2(i,o).normalize(),r=(new t.Vec3).sub2(n,o).dot(a);if(r<0)return;const l=(new t.Vec3).add2(o,a.clone().mulScalar(r)).distance(n),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 fi(t){if(Vt(),!t.targetSceneId)return void console.warn("[Portal] No target scene ID specified");console.log(`[StorySplat Viewer] Portal activated: navigating to scene ${t.targetSceneId}`);const n=i.listenerCount("portalActivated")>0;if(i.emit("portalActivated",{portalId:t.id,targetSceneId:t.targetSceneId,targetSceneName:t.targetSceneName}),n)console.log("[Portal] External portal handler detected, deferring navigation");else{!function(t,e){const n=t.querySelector(".storysplat-portal-loading");n&&n.remove();try{"static"===window.getComputedStyle(t).position&&(t.style.position="relative")}catch{}const o=document.createElement("div");o.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(P,"loadingScene").replace("{name}",e),o.appendChild(i),o.appendChild(s),Object.assign(o.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 ${m}`,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(o)}(e,t.targetSceneName||t.title||"scene");try{const n=`https://discover.storysplat.com/api/scene/${t.targetSceneId}`;console.log(`[Portal] Fetching scene from: ${n}`);const o=await fetch(n);if(!o.ok)throw new Error(`Failed to fetch scene: ${o.status} ${o.statusText}`);const i=await o.json(),s=i.data||i;s.name&&function(t,e){const n=t.querySelector(".storysplat-portal-loading-text");n&&(n.textContent=l(P,"loadingScene").replace("{name}",e))}(e,s.name);!function(){console.log("[Portal] Cleaning up current scene for navigation..."),lt=!0,In(),Gn.forEach(t=>{t.pause(),t.src=""}),Gn.length=0,Xn.clear(),Nn.forEach(t=>{t.close().catch(()=>{})}),Nn.length=0,qn.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);e&&e.stop(),t.entity.destroy()}),qn.clear(),ei.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);e&&e.stop(),t.entity.destroy()}),ei.clear(),Bn.forEach(t=>{t.videoElement&&(t.videoElement.pause(),t.videoElement.src=""),t.alphaVideoElement&&(t.alphaVideoElement.pause(),t.alphaVideoElement.src="")}),si.forEach(t=>t.destroy()),si.length=0,po(),Bn.forEach(t=>{t.destroy()}),Bn.length=0,Un.forEach(t=>{t.destroy()}),Un.length=0,et&&(et.destroy(),et=null);it=null,st.clear(),at=!1,Ko(),window.removeEventListener("keydown",Jo),qt.destroy(),Yt.destroy(),Xt.destroy(),vi.removeEventListener("mousemove",xi),Do(),xo.forEach(t=>{t.destroy()}),xo.length=0,Zn.forEach(t=>{t.destroy()}),Zn.clear(),ho&&(ho.destroy(),ho=null);ct&&(ct.destroy(),ct=null);Ct&&(Ct.destroy(),Ct=null);const t=U.__htmlMeshManager;t&&t.destroy();const n=U.__customScriptSystem;n&&n.dispose();window.removeEventListener("resize",Di),F.preloader&&F.preloader.remove();F.scrollControls&&F.scrollControls.remove();F.fullscreenButton&&F.fullscreenButton.remove();F.helpButton&&F.helpButton.remove();F.helpPanel&&F.helpPanel.remove();F.waypointInfo&&F.waypointInfo.remove();F.watermark&&F.watermark.remove();const o=document.getElementById("storysplat-viewer-styles");o&&o.remove();e.classList.remove("storysplat-viewer-container"),U.destroy(),console.log("[Portal] Cleanup complete")}(),await new Promise(t=>setTimeout(t,100)),B&&B.parentNode&&B.remove();await Ft(e,s,{lazyLoad:!1});return yi(e),void console.log(`[Portal] Successfully navigated to scene: ${t.targetSceneId}`)}catch(t){console.error("[Portal] Navigation failed:",t),yi(e);const n=document.createElement("div");n.className="storysplat-portal-error",n.textContent=`Failed to load scene: ${t.message}`,Object.assign(n.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(n),setTimeout(()=>n.remove(),3e3)}}}function yi(t){const e=t.querySelector(".storysplat-portal-loading");e&&e.remove()}i.on("progressUpdate",()=>{di()}),setTimeout(()=>{di()},100),i.on("progressUpdate",()=>{mi()}),setTimeout(()=>{mi()},100);const vi=U.graphicsDevice.canvas,xi=t=>{const e=vi.getBoundingClientRect();Qt=t.clientX-e.left,Kt=t.clientY-e.top};N||vi.addEventListener("mousemove",xi);let bi=!1,wi=!1,Si=0,_i=0;vi.addEventListener("pointerdown",t=>{0===t.button&&(wi=!0,bi=!1,Si=t.clientX,_i=t.clientY)}),vi.addEventListener("pointermove",t=>{if(!wi)return;const e=t.clientX-Si,n=t.clientY-_i;e*e+n*n>25&&(bi=!0)});const Mi=()=>{wi=!1};function Ei(e,n){const o=bt.camera.screenToWorld(e,n,bt.camera.nearClip),i=bt.camera.screenToWorld(e,n,bt.camera.farClip);let s=null;Bn.forEach(e=>{if(!e.enabled)return;const n=e.getPosition(),a=(new t.Vec3).sub2(i,o).normalize(),r=(new t.Vec3).sub2(n,o).dot(a);if(r<0)return;const l=(new t.Vec3).add2(o,a.clone().mulScalar(r)).distance(n),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}vi.addEventListener("pointerup",Mi),vi.addEventListener("pointercancel",Mi);let Ci=null,Ti=!1,Ai=!1;const Pi=e.querySelector(".storysplat-hotspot-popup"),Li=e.querySelector(".storysplat-hotspot-overlay");function ki(t,e){Me||(Me=!0,E(F)),S(F,!1),"walk"!==Lt?"explore"===Lt&&async function(t,e){if("explore"!==Lt)return;console.log("[StorySplat Viewer] Double-click focus at:",t,e);const n=Ei(t,e);if(n){const t=n.entity.getPosition();return console.log("[StorySplat Viewer] Focusing on hotspot at:",t.x,t.y,t.z),void("fly"===Et.mode?Et.flyTo(t):Et.focus(t,!1))}try{const n=.25;Wt.resize(Math.floor(vi.clientWidth*n),Math.floor(vi.clientHeight*n));const o=U.scene.layers.getLayerByName("World");if(!o)return void console.warn("[StorySplat Viewer] World layer not found");Wt.prepare(bt.camera,U.scene,[o]);const i=Math.floor(t*n),s=Math.floor(e*n),a=await Wt.getWorldPointAsync(i,s);if(a)if(isFinite(a.x)&&isFinite(a.y)&&isFinite(a.z)){const t=bt.getPosition().distance(a);if(t>.3&&t<500)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"===Et.mode?Et.flyTo(a):Et.focus(a,!1));console.log("[StorySplat Viewer] Discarding pick result — distance out of range:",t.toFixed(2))}else console.log("[StorySplat Viewer] Discarding non-finite pick result:",a.x,a.y,a.z);console.log("[StorySplat Viewer] No valid pick result at click point")}catch(t){console.warn("[StorySplat Viewer] Picking failed:",t)}}(t,e):$t(!Bt)}Pi&&(Pi.addEventListener("mouseenter",()=>{Ti=!0}),Pi.addEventListener("mouseleave",()=>{Ti=!1,Ci&&"hover"===Ci.activationMode&&(Pi.classList.remove("visible"),Li&&Li.classList.remove("visible"),Ci=null)})),vi.addEventListener("mousemove",t=>{const n=vi.getBoundingClientRect(),o=t.clientX-n.left,i=t.clientY-n.top,s=gi(o,i);if(s&&s.portal){const t=s.portal.activationMode||"click";return vi.style.cursor="click"===t?"pointer":"default",void(ue=!0)}const a=Ei(o,i);if(a&&a.hotspot){const t=a.hotspot,n=t.activationMode||"click";if("click"===n||"hover"===n||"video"===t.type?(vi.style.cursor="pointer",ue=!0):(vi.style.cursor="default",ue=!1),"hover"===t.activationMode&&Ci!==t){Ci=t;!(c&&p===t.id)&&(t.information||t.photoUrl||t.iframeUrl||t.externalLinkUrl||t.modelUrl)&&v(e,t,P)}}else if(Ai||(vi.style.cursor="default",ue=!1),Ci&&"hover"===Ci.activationMode&&!Ti){const t=e.querySelector(".storysplat-hotspot-popup"),n=e.querySelector(".storysplat-hotspot-overlay");t&&t.classList.remove("visible"),n&&n.classList.remove("visible"),Ci=null}}),vi.addEventListener("click",n=>{if(bi)return void(bi=!1);const o=vi.getBoundingClientRect(),s=n.clientX-o.left,a=n.clientY-o.top,r=gi(s,a);if(null!==r&&r.portal){const t=r.portal;if(console.log("[StorySplat Viewer] Portal clicked:",t.title||t.targetSceneName),c)return void i.emit("portalClick",{portal:t});if("click"===(t.activationMode||"click")){!1!==t.confirmNavigation&&(t.title||t.targetSceneName||t.targetSceneId)&&$n?(t=>{if(!$n)return;const e=$n.querySelector(".storysplat-portal-popup-title");e&&(t.title?e.textContent=t.title:t.targetSceneName?e.textContent=`${l(P,"switchScenes").replace("?","")} ${t.targetSceneName}?`:e.textContent=l(P,"switchScenes")),Vn=t,$n.classList.add("visible")})(t):fi(t)}return}const h=Ei(s,a);if(null!==h){const n=h.entity,o=h.hotspot;if(console.log("[StorySplat Viewer] Hotspot clicked:",o.title),i.emit("hotspotClick",{hotspot:o}),n.audioElements&&n.audioElements.audio){const t=n.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:",o.title)):(e.pause(),console.log("[Audio] Hotspot audio paused:",o.title))}if(n.videoElement&&("click"===n.mediaTriggerMode||"autoplay"===n.mediaTriggerMode)){const t=n.videoElement;n.alphaVideoElement,"autoplay"===n.mediaTriggerMode&&!t.paused&&t.muted?(t.muted=(o.videoMuted,!1),console.log("[Hotspot] Video unmuted by click")):t.paused?(pi(n,o),console.log("[Hotspot] Video started")):(hi(n),console.log("[Hotspot] Video paused"))}const s=o.activationMode||"click",a=o.title||o.information||o.photoUrl||o.iframeUrl||o.externalLinkUrl||o.modelUrl;"click"===s&&a&&(ge?xe?be():function(e){if(!ge)return;ye||(ye=new t.Entity("arContentPlane"),ye.addComponent("render",{type:"plane"}),ye.setLocalScale(.8,1,.45),U.root.addChild(ye));const n=512,o=288,i=document.createElement("canvas");i.width=n,i.height=o;const s=i.getContext("2d"),a=e.backgroundColor||"rgba(20, 20, 20, 0.95)";s.fillStyle=a,s.fillRect(0,0,n,o),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,n,o,i,s){const a=e.split(" ");let r="",l=o;for(const e of a){const o=r+e+" ";t.measureText(o).width>i&&r?(t.fillText(r.trim(),n,l),r=e+" ",l+=s):r=o}return t.fillText(r.trim(),n,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",ve||(ve=new t.Texture(U.graphicsDevice,{width:n,height:o,format:t.PIXELFORMAT_RGBA8,mipmaps:!1,minFilter:t.FILTER_LINEAR,magFilter:t.FILTER_LINEAR})),ve.setSource(i);const c=new t.StandardMaterial;c.diffuse=new t.Color(0,0,0),c.diffuseMap=ve,c.emissive=new t.Color(1,1,1),c.emissiveMap=ve,c.useLighting=!1,c.blendType=t.BLEND_NORMAL,c.cull=t.CULLFACE_NONE,c.depthWrite=!0,c.update(),ye.render&&ye.render.meshInstances[0]&&(ye.render.meshInstances[0].material=c),ye.enabled=!0,xe=!0,console.log("[StorySplat Viewer] AR content displayed for hotspot:",e.title)}(o):c&&p===o.id||v(e,o,P));const r=o.teleportWaypoint??o.teleportToWaypoint,l=o.teleportPercent??o.teleportToPercent,u=o.teleportMode||"animate";let m=null;if(void 0!==r&&-1!==r){const t=d.waypoints?.length||1,e=Math.max(0,Math.min(r,t-1));m=t>1?e/(t-1):0,console.log("[Hotspot] Teleporting to waypoint:",e,"(progress:",m,", mode:",u,")")}else void 0!==l&&-1!==l&&(m=Math.max(0,Math.min(l/100,1)),console.log("[Hotspot] Teleporting to percent:",l,"(progress:",m,", mode:",u,")"));null!==m&&("instant"===u?(Je=m,tn=m,Mn(Je)):(tn=m,Tn(m,800)))}}),vi.addEventListener("dblclick",t=>{const e=vi.getBoundingClientRect();ki(t.clientX-e.left,t.clientY-e.top)});let zi=0;vi.addEventListener("touchend",t=>{if(1!==t.changedTouches.length)return;const e=Date.now();if(e-zi<300){const e=t.changedTouches[0],n=vi.getBoundingClientRect();ki(e.clientX-n.left,e.clientY-n.top),zi=0}else zi=e}),document.addEventListener("keydown",t=>{if(Bt&&["KeyW","KeyA","KeyS","KeyD","ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Space"].includes(t.code)&&Vt(),t.shiftKey&&t.ctrlKey&&"KeyD"===t.code&&Ct){t.preventDefault();const e=!Ct.collisionDebugVisible;Ct.setCollisionDebug(e),console.log(`[StorySplat Viewer] Collision debug: ${e?"ON":"OFF"} (${Ct.collisionMeshEntities.length} meshes)`)}}),Te(.2,"Initializing...");const Ri=n.frameSequence&&n.frameSequence.frameUrls&&n.frameSequence.frameUrls.length>0?async function(){if(!n.frameSequence||!n.frameSequence.frameUrls||0===n.frameSequence.frameUrls.length)throw new Error("No frame sequence URLs provided");console.log("[StorySplat Viewer] Loading 4DGS frame sequence:",n.frameSequence.frameUrls.length,"frames"),Te(.3,"Loading 4DGS frames..."),ct=new Pt(U,{frameUrls:n.frameSequence.frameUrls,fps:n.frameSequence.fps||24,loop:!1!==n.frameSequence.loop,preloadCount:n.frameSequence.preloadCount||5,autoplay:n.frameSequence.autoplay||!1},{onFrameChange:(t,e)=>{i.emit("frameChange",t,e)},onLoadProgress:(t,e)=>{Te(.3+t/e*.6,`Loading frames... ${t}/${e}`)},onError:t=>{console.error("[StorySplat Viewer] Frame sequence error:",t),i.emit("error",new Error(t))}}),ct.on("complete",()=>{i.emit("frameComplete")}),U.on("update",t=>{ct&&!lt&&ct.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:d.lodMetaUrl||"(none)",sogUrl:d.sogUrl||"(none)",splatUrl:d.splatUrl||"(none)",fallbackUrls:d.fallbackUrls?.length||0}),d.lodMetaUrl&&(a.push(d.lodMetaUrl),console.log("[SPLAT] ✓ LOD streaming URL added (highest priority):",d.lodMetaUrl)),d.sogUrl&&(a.push(d.sogUrl),console.log("[SPLAT] ✓ SOG URL added (second priority):",d.sogUrl)),d.splatUrl&&(a.push(d.splatUrl),console.log("[SPLAT] ✓ Original splat URL added (third priority):",d.splatUrl)),d.fallbackUrls&&(a.push(...d.fallbackUrls),console.log("[SPLAT] ✓ Fallback URLs added:",d.fallbackUrls)),console.log("[SPLAT] Final URL priority order:",a),console.log("[SPLAT] Will try URLs in order: LOD streaming > SOG > PLY/Other"),Te(.3,l(P,"loading"));for(const r of a)if(r)try{const a=r.split(".").pop()?.toLowerCase()||"splat",c="gsplat",p=r.includes("lod-meta.json"),h=r.includes(".sog")||p;console.log("[SPLAT] Attempting to load URL:",r),console.log("[SPLAT] Format detection:",{extension:a,isLodStreaming:p,isSogFormat:h,assetType:c});const u=new t.Asset("splat-"+Date.now(),c,{url:r});u.on("progress",(t,n)=>{if(n>0){Ae(r)&&(e=t);const o=.3+t/n*.6,i=Math.round(t/n*100);i%25!=0&&100!==i||console.log(`[SPLAT] Loading progress: ${i}% (${(t/1024/1024).toFixed(2)}MB / ${(n/1024/1024).toFixed(2)}MB)`),Te(o,`${l(P,"loading")} ${i}%`)}}),s=r,await new Promise((t,e)=>{let s=!1;const a=h?t=>{if(s)return;const n=t.reason?.message||String(t.reason);(n.includes("shape")||n.includes("upgradeMeta"))&&(console.warn("[SPLAT] ⚠️ Parse error detected (likely deprecated v1 format):",n),s=!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..."),e(new Error(`SOG parse error: ${n}`)))}:null;h&&a&&(console.log("[SPLAT] Registered error handler for SOG format parsing"),window.addEventListener("unhandledrejection",a));const l=()=>{h&&a&&window.removeEventListener("unhandledrejection",a)};u.ready(()=>{if(lt)return console.log("[SPLAT] Ignoring load - viewer was destroyed during loading"),l(),void e(new Error("Viewer destroyed"));console.log("[SPLAT] ✓ Asset loaded and resources ready");try{if(et=Pe(u,r),Ct){Ct.setSplatEntity(et);const t=Ct.voxelCollisionInstance;t&&Et.setVoxelCollision(t,et)}It(r)?(console.log("[SPLAT] ✓ LOD streaming enabled for this splat"),console.log("[SPLAT] LOD distances configured:",{distances:j,description:"Quality levels switch at these camera distances",preset:H})):h?console.log("[SPLAT] ✓ Single SOG file loaded (no LOD streaming)"):console.log("[SPLAT] Standard format loaded (PLY/SPLAT)");const e=o.revealEffect||n.revealEffect||d.revealEffect||"none",i=o.revealStyle||n.revealStyle||d.revealStyle||"bloom",a=nt(e,i);if(a){et.addComponent("script");const t="radial"===i?Y():J();ot=et.script?.create?.(t)??null,ot&&(ot.enabled=!1,ot.center.set(0,0,0),ot.speed=a.speed,ot.acceleration=a.acceleration,ot.delay=a.delay,ot.oscillationIntensity=a.oscillationIntensity,ot.dotTint.set(a.dotTint.r,a.dotTint.g,a.dotTint.b),ot.waveTint.set(a.waveTint.r,a.waveTint.g,a.waveTint.b),ot.endRadius=a.endRadius,console.log("[StorySplat Viewer] Reveal effect configured (waiting to start):",i,e))}else console.log("[StorySplat Viewer] Reveal effect disabled");p?(l(),t()):setTimeout(()=>{s||(l(),t())},100)}catch(t){console.error("[StorySplat Viewer] Error during gsplat setup:",t),l(),e(t)}}),u.on("error",t=>{const n=t;console.error("[SPLAT] ✗ Asset load error:",{url:r,assetType:c,isSogFormat:h,isLodFormat:p,error:t,message:n?.message||"Unknown error",status:n?.status||n?.statusCode||"N/A"}),l(),e(t)}),Le(u,r)});const m=Ae(s);return i.emit("loaded",{bandwidthUsed:m?e:0,isStorySplatHosted:m}),console.log("[SPLAT] ✓✓✓ SPLAT LOADED SUCCESSFULLY ✓✓✓"),void console.log("[SPLAT] Load summary:",{url:s,format:p?"LOD streaming (lod-meta.json)":h?"SOG (compressed)":"Standard (PLY/SPLAT)",lodEnabled:p,lodPreset:p?H:"N/A",bandwidthUsed:`${(e/1024/1024).toFixed(2)}MB`,isStorySplatHosted:m,bandwidthCounted:m?"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"))};Ri().then(()=>{if(Te(1,"Ready!"),c||(d.hotspots&&0!==d.hotspots.length?(console.log(`[StorySplat Viewer] Creating ${d.hotspots.length} hotspots...`),d.hotspots.forEach((t,e)=>{ci(t,e)})):console.log("[StorySplat Viewer] No hotspots to create"),function(){if(!d.portals||0===d.portals.length)return void console.log("[StorySplat Viewer] No portals to create");const t=d.portals.filter(t=>!t.menuOnly);console.log(`[StorySplat Viewer] Creating ${t.length} portals (${d.portals.length-t.length} menu-only)...`),t.forEach((t,e)=>{ui(t,e)})}(),d.waypoints&&0!==d.waypoints.length&&(d.waypoints.forEach((e,n)=>{e.interactions&&Array.isArray(e.interactions)&&e.interactions.forEach(o=>{if("audio"===o.type&&o.data){const i=o.data;if(!i.url)return void console.warn(`[Audio] Waypoint ${n} audio interaction has no URL, skipping`);const s=String(o.id||`audio-${n}`),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:ti(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});U.assets.add(c),c.ready(()=>{const t=a.sound?.slot(s);t&&(t.asset=c.id);const e=qn.get(s);e&&(e.assetReady=!0),console.log(`[Audio] Waypoint audio loaded: ${s}, spatialSound=${i.spatialSound}, maxDistance=${i.maxDistance}`)}),U.assets.load(c),U.root.addChild(a),qn.set(s,{entity:a,waypointIndex:n,config:i,slotId:s,playing:!1,autoplayTriggered:!1,assetReady:!1}),console.log(`[Audio] Waypoint audio created: ${s} at waypoint ${n}, spatial=${i.spatialSound}`)}})}),qn.size>0&&console.log(`[StorySplat Viewer] Setup ${qn.size} waypoint audio sources`)),function(){const e=d.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 n=e.id||`emitter-${Date.now()}-${Math.random().toString(36).substr(2,9)}`,o=e.position||{x:0,y:0,z:0},i=new t.Entity(`audio-emitter-${n}`);i.setPosition(o.x,o.y,-o.z);const s=ti(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(n,{volume:e.volume??.5,loop:!1!==e.loop,autoPlay:!1,overlap:!1});const a=new t.Asset(`audio-emitter-${n}`,"audio",{url:e.url});a.on("load",()=>{if(lt)return;const t=i.sound?.slot(n);t&&(t.asset=a.id);const o=ei.get(n);o&&(o.assetReady=!0,!1!==e.autoplay&&t&&(console.log(`[Audio] Autoplay emitter: ${e.name||n}`),t.play(),o.playing=!0)),console.log(`[Audio] Emitter loaded: ${e.name||n}, spatial=${!1!==e.spatialSound}, maxDistance=${e.maxDistance||100}`)}),U.assets.add(a),U.assets.load(a),U.root.addChild(i),ei.set(n,{entity:i,config:e,slotId:n,playing:!1,assetReady:!1}),console.log(`[Audio] Emitter created: ${e.name||n} at (${o.x}, ${o.y}, ${o.z})`)}),ei.size>0&&console.log(`[StorySplat Viewer] Setup ${ei.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:",d.particles),console.log("[Particle] Type:",typeof d.particles),console.log("[Particle] Is Array:",Array.isArray(d.particles)),!d.particles||0===d.particles.length)return console.log("[Particle] ⚠️ No particle systems to create (particles array is empty or undefined)"),void console.log("═══════════════════════════════════════");console.log(`[Particle] 📋 Found ${d.particles.length} particle system(s) to create`);for(let t=0;t<d.particles.length;t++){const e=d.particles[t];console.log(`[Particle] --- Particle System ${t+1}/${d.particles.length} ---`),console.log("[Particle] Raw config:",JSON.stringify(e,null,2));try{const n=e.particleTexture||"flare";let o,i;"custom"===n&&e.customTextureUrl?(o=e.customTextureUrl,i=`custom_${e.id||e.name||t}`,console.log(`[Particle] Custom texture: ${o.substring(0,60)}...`)):(o=Kn[n]||Kn.flare,i=n,console.log(`[Particle] Texture: ${n} -> ${o.substring(0,60)}...`)),console.log("[Particle] Loading texture...");const s=await Jn(i,o);console.log("[Particle] ✅ Texture loaded:",s.name),console.log("[Particle] Creating entity...");const a=to(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!"),U.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-${Zn.size}`).replace(/[^a-zA-Z0-9]/g,"_");Zn.set(l,a),console.log(`[Particle] ✅ SUCCESS: Created "${e.name||l}"`)}catch(t){const n=t instanceof Error?t:{message:String(t),stack:""};console.error(`[Particle] ❌ FAILED to create particle system: ${e.name}`),console.error(`[Particle] Error message: ${n.message||"No message"}`),console.error(`[Particle] Error stack: ${n.stack||"No stack"}`),console.error("[Particle] Error object:",t)}}console.log("═══════════════════════════════════════"),console.log(`[Particle] 🎉 COMPLETE: ${Zn.size}/${d.particles.length} particle systems created`),console.log("[Particle] Active particle systems:",Array.from(Zn.keys())),console.log("═══════════════════════════════════════")}(),async function(){d.customMeshes&&0!==d.customMeshes.length?(console.log(`[StorySplat Viewer] Creating ${d.customMeshes.length} custom meshes...`),console.log("[StorySplat Viewer] All custom mesh configs:",d.customMeshes),setTimeout(()=>{let t=0,e=0;d.customMeshes.forEach((n,o)=>{if(console.log(`[CustomMesh] Processing mesh ${o}:`,{name:n.name,enabled:n.enabled,hasModelUrl:!!n.modelUrl,modelUrl:n.modelUrl?.substring(0,100)+"..."}),!1!==n.enabled)try{io(n,o)?t++:(e++,console.warn(`[CustomMesh] Mesh ${n.name} returned null (likely missing modelUrl)`))}catch(t){console.error("[CustomMesh] Error loading mesh",n.name,":",t),e++}else console.log("[CustomMesh] Skipping disabled mesh:",n.name,"(enabled =",n.enabled,")"),e++}),console.log(`[CustomMesh] Summary: ${t} loaded, ${e} skipped`)},100),console.log(`[StorySplat Viewer] ${d.customMeshes.length} custom meshes queued for loading`)):console.log("[StorySplat Viewer] No custom meshes to create")}(),fo(),d.lights&&0!==d.lights.length?(console.log(`[StorySplat Viewer] Creating ${d.lights.length} custom lights...`),d.lights.forEach((t,e)=>{let n=null;switch(t.type){case"point":n=wo(t);break;case"directional":n=So(t);break;case"hemispheric":n=_o(t);break;case"ambient":Mo(t);break;case"spot":n=Eo(t);break;default:return void console.warn("[StorySplat Viewer] Unknown light type:",t.type)}n&&(U.root.addChild(n),xo.push(n),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")),To(),Io(),dt.length>0&&dt[0].url&&qe(dt[0].url),ot&&(ot.enabled=!0,console.log("[StorySplat Viewer] Reveal effect started immediately")),!ot&&d.lodMetaUrl){const t=5e3,e=performance.now();let n=!1;const o=()=>{if(n)return;const i=U.scene.gsplat;null!=i?.material||performance.now()-e>t?(n=!0,requestAnimationFrame(()=>{requestAnimationFrame(()=>{F.preloader&&g(F.preloader)})})):requestAnimationFrame(o)};requestAnimationFrame(o)}else setTimeout(()=>{F.preloader&&g(F.preloader)},200);if(function(){if(!d.includeXR)return;if(!U.xr)return void console.warn("[StorySplat Viewer] WebXR not supported in this browser");const e=U.xr,n=d.xrMode||"both";"vr"!==n&&"both"!==n||(e.isAvailable(t.XRTYPE_VR)&&(F.vrButton?.classList.add("available"),console.log("[StorySplat Viewer] VR is available")),e.on("available:"+t.XRTYPE_VR,t=>{t?F.vrButton?.classList.add("available"):F.vrButton?.classList.remove("available")})),"ar"!==n&&"both"!==n||(e.isAvailable(t.XRTYPE_AR)&&(F.arButton?.classList.add("available"),console.log("[StorySplat Viewer] AR is available")),e.on("available:"+t.XRTYPE_AR,t=>{t?F.arButton?.classList.add("available"):F.arButton?.classList.remove("available")})),e.on("start",()=>{ge=!0,console.log("[StorySplat Viewer] XR session started"),"vr"===fe?(F.vrButton?.classList.add("active"),F.vrButton.textContent=l(P,"exitVr")):"ar"===fe&&(F.arButton?.classList.add("active"),F.arButton.textContent=l(P,"exitAr")),Et.disable(),Ct&&Ct.disable(),i.emit("xrStart",{type:fe})}),e.on("end",()=>{ge=!1,console.log("[StorySplat Viewer] XR session ended"),be(),F.vrButton?.classList.remove("active"),F.arButton?.classList.remove("active"),F.vrButton&&(F.vrButton.textContent=l(P,"vr")),F.arButton&&(F.arButton.textContent=l(P,"ar")),fe=null,"explore"===Lt?Et.enable():"walk"===Lt&&Ct&&Ct.enable(),i.emit("xrEnd",{})}),F.vrButton&&F.vrButton.addEventListener("click",()=>{ge&&"vr"===fe?e.end():!ge&&e.isAvailable(t.XRTYPE_VR)&&(fe="vr",bt.camera.startXr(t.XRTYPE_VR,t.XRSPACE_LOCALFLOOR,{callback:t=>{t&&(console.error("[StorySplat Viewer] Failed to start VR:",t),fe=null)}}))}),F.arButton&&F.arButton.addEventListener("click",()=>{ge&&"ar"===fe?e.end():!ge&&e.isAvailable(t.XRTYPE_AR)&&(fe="ar",bt.camera.startXr(t.XRTYPE_AR,t.XRSPACE_LOCALFLOOR,{callback:t=>{t&&(console.error("[StorySplat Viewer] Failed to start AR:",t),fe=null)}}))})}(),d.htmlMeshes&&d.htmlMeshes.length>0){console.log("[StorySplat Viewer] Setting up HTML meshes:",d.htmlMeshes.length);const t=St(U,d.htmlMeshes);U.__htmlMeshManager=t,i.on("progressUpdate",()=>{const e=100*Je,n=d.waypoints?.length||1,o=Math.round(Je*Math.max(1,n-1));t.updateVisibility(e,o)})}try{const t=new URLSearchParams(window.location.search),e=t.get("waypoint"),n=t.get("autoplay");if(null!==e&&d.waypoints&&d.waypoints.length>0){const t=parseInt(e,10);!isNaN(t)&&t>=0&&t<d.waypoints.length&&(console.log("[StorySplat Viewer] URL param: navigating to waypoint",t),An(t))}"true"!==n||o.autoPlay||d.autoPlay||(console.log("[StorySplat Viewer] URL param: starting autoplay"),Dn())}catch(t){console.warn("[StorySplat Viewer] Could not parse URL parameters:",t)}if(i.emit("ready"),console.log("[StorySplat Viewer] Ready"),c||Ce(D),h){if(y(F,{nextWaypoint:Pn,prevWaypoint:Ln,play:Dn,pause:In,isPlaying:()=>tt,getCurrentWaypointIndex:()=>K,getWaypointCount:()=>d.waypoints?.length||0,getWaypoints:()=>d.waypoints||[],setCameraMode:Ce,on:(t,e)=>i.on(t,e)},D,P,{container:e,hideProgressText:T.hideProgressText}),function(t,e){if(!t.sceneMenuContainer)return;const n=t.sceneMenuContainer.querySelectorAll(".storysplat-scene-menu-item"),o=t.sceneMenuContainer.querySelector(".storysplat-scene-menu-toggle"),i=t.sceneMenuContainer.querySelector(".storysplat-scene-menu-dropdown");n.forEach(t=>{t.addEventListener("click",()=>{const n=t.getAttribute("data-portal-id")||"";e(n),o?.classList.remove("open"),i?.classList.remove("open")})})}(F,t=>{const e=d.portals?.find(e=>e.id===t);e&&fi(e)}),F.muteButton&&F.muteButton.addEventListener("click",()=>{const t=(Hn?ii():oi(),Hn),e=F.muteButton.querySelector(".storysplat-unmuted-icon"),n=F.muteButton.querySelector(".storysplat-muted-icon");e&&(e.style.display=t?"none":""),n&&(n.style.display=t?"":"none"),F.muteButton.setAttribute("aria-label",l(P,t?"unmute":"mute"))}),F.relightingButton&&it){const t=d.splatRelighting?.viewerDefaultOn??!1;console.log("[StorySplat Viewer] Relighting toggle button wired up, startOn:",t);let e=t,n=t?1:0;F.relightingButton.classList.toggle("active",t);let o=null;F.relightingButton.addEventListener("click",()=>{if(ot&&ot.enabled)return void console.log("[StorySplat Viewer] Relighting toggle blocked — reveal still active");e=!e,n=e?1:0,console.log("[StorySplat Viewer] Relighting toggle:",e,"scripts:",st.size),F.relightingButton.classList.toggle("active",e),null!==o&&cancelAnimationFrame(o);const t=()=>{if(0===st.size)return;const e=it?it.relightFade:n,i=n-e;if(Math.abs(i)<.01){for(const t of st)t.setRelightFade(n);return void(o=null)}const s=e+.15*i;for(const t of st)t.setRelightFade(s);o=requestAnimationFrame(t)};o=requestAnimationFrame(t)})}const t=e.querySelector(".storysplat-hotspot-popup-close");t&&t.addEventListener("click",()=>{jn(),Yn(e)}),F.waypointListContainer&&d.waypoints&&d.waypoints.length>0&&(!function(t,e){if(!t.waypointListContainer)return;const n=t.waypointListContainer.querySelectorAll(".storysplat-waypoint-item"),o=t.waypointListContainer.querySelector(".storysplat-waypoint-list-toggle"),i=t.waypointListContainer.querySelector(".storysplat-waypoint-list-dropdown");n.forEach(t=>{t.addEventListener("click",()=>{const n=parseInt(t.getAttribute("data-waypoint-index")||"0",10);e(n),o?.classList.remove("open"),i?.classList.remove("open")})})}(F,t=>{console.log("[StorySplat Viewer] Waypoint list: jumping to waypoint",t),"tour"!==Lt&&Ce("tour"),An(t)}),i.on("waypointChange",({index:t})=>{C(F,t)}),C(F,0)),i.emit("progressUpdate",{progress:Je,index:K}),d.waypoints&&d.waypoints.length>0&&i.emit("waypointChange",{index:0,waypoint:d.waypoints[0],prevIndex:-1})}(o.autoPlay||d.autoPlay)&&Dn()}).catch(t=>{console.error("[StorySplat Viewer] Failed to initialize:",t),F.preloader&&g(F.preloader),i.emit("error",t)});const Di=()=>{U.resizeCanvas()};window.addEventListener("resize",Di);const Ii=new Map,Fi=new Map,Bi=new Map,Ui=new Map,$i=new Map,Vi=new Map;c&&(Bn.forEach(t=>{const e=t.hotspotData?.id;e&&Ii.set(e,t)}),xo.forEach((t,e)=>{const n=d.lights?.[e],o=n?.id||n?.name||`light-${e}`;Fi.set(o,t)}),Zn.forEach((t,e)=>{Bi.set(e,t)}),Un.forEach(t=>{const e=t.portalData?.id;e&&$i.set(e,t)}));const Oi={app:U,canvas:B,goToWaypoint:t=>{if("explore"===Lt)throw new Error('[StorySplat Viewer] goToWaypoint() requires tour mode. Call setCameraMode("tour") first.');An(t)},nextWaypoint:()=>{if("explore"===Lt)throw new Error('[StorySplat Viewer] nextWaypoint() requires tour mode. Call setCameraMode("tour") first.');Pn()},prevWaypoint:()=>{if("explore"===Lt)throw new Error('[StorySplat Viewer] prevWaypoint() requires tour mode. Call setCameraMode("tour") first.');Ln()},getCurrentWaypointIndex:()=>K,getWaypointCount:()=>d.waypoints?.length||0,setPosition:(t,e,n)=>{if("explore"!==Lt)throw new Error('[StorySplat Viewer] setPosition() requires explore mode. Call setCameraMode("explore") first.');if(tt)throw new Error("[StorySplat Viewer] setPosition() cannot be used during autoplay. Call pause() or stop() first.");Et.disable(),bt.setPosition(t,e,n),Et.syncFromCamera(),Et.enable(),requestAnimationFrame(()=>{bt.setPosition(t,e,n),Et.syncFromCamera()})},setRotation:(t,e,n)=>{if("explore"!==Lt)throw new Error('[StorySplat Viewer] setRotation() requires explore mode. Call setCameraMode("explore") first.');if(tt)throw new Error("[StorySplat Viewer] setRotation() cannot be used during autoplay. Call pause() or stop() first.");Et.disable(),bt.setEulerAngles(t,e,n),Et.syncFromCamera(),Et.enable(),requestAnimationFrame(()=>{bt.setEulerAngles(t,e,n),Et.syncFromCamera()})},getPosition:()=>{const t=bt.getPosition();return{x:t.x,y:t.y,z:t.z}},getRotation:()=>{const t=bt.getEulerAngles();return{x:t.x,y:t.y,z:t.z}},play:()=>{if("explore"===Lt)throw new Error('[StorySplat Viewer] play() requires tour mode. Call setCameraMode("tour") first.');Dn()},pause:()=>{if("explore"===Lt)throw new Error('[StorySplat Viewer] pause() requires tour mode. Call setCameraMode("tour") first.');In()},stop:()=>{if("explore"===Lt)throw new Error('[StorySplat Viewer] stop() requires tour mode. Call setCameraMode("tour") first.');!function(){if(ct)return ct.stop(),void i.emit("playbackStop");In(),En(0)}()},isPlaying:()=>tt,setFrame:t=>ct?.setFrame(t),getCurrentFrame:()=>ct?.getCurrentFrame()??0,getTotalFrames:()=>ct?.getTotalFrames()??0,setFps:t=>ct?.setFps(t),getFps:()=>ct?.getFps()??24,getFrameProgress:()=>ct?.getProgress()??0,setFrameProgress:t=>ct?.setProgress(t),goToOriginalSplat:Qe,goToSplat:async t=>{const e=Ze();if(t===e)return void Qe();const n=dt.find(e=>e.url===t);n||gt.has(t)||await qe(t);const o=ut||e;if(o===e&&et)Ne(et);else if(o){const t=gt.get(o);t&&(t.enabled=!1)}if(!He(t))throw new Error(`[StorySplat Viewer] Failed to switch to splat: ${t}`);n&&je(n.defaultExploreMode,!1),i.emit("splatChange",{url:t,isOriginal:!1})},getCurrentSplatUrl:function(){return ut||Ze()},isShowingOriginalSplat:function(){return ut===Ze()||null===ut},getAdditionalSplats:()=>dt.map(t=>({url:t.url,name:t.name,waypointIndex:t.waypointIndex,percentage:t.percentage})),destroy:()=>{lt=!0,In(),ct&&(ct.destroy(),ct=null),window.removeEventListener("resize",Di),e.removeAttribute("data-storysplat-editor"),e.querySelector(".storysplat-error-popup")?.remove(),F.preloader&&F.preloader.remove(),F.scrollControls&&F.scrollControls.remove(),F.fullscreenButton&&F.fullscreenButton.remove(),F.helpButton&&F.helpButton.remove(),F.helpPanel&&F.helpPanel.remove(),F.waypointInfo&&F.waypointInfo.remove(),F.watermark&&F.watermark.remove(),F.wasdHint&&F.wasdHint.remove(),F.orbitHint&&F.orbitHint.remove(),F.doubleTapHint&&F.doubleTapHint.remove(),F.lookZone&&F.lookZone.remove(),F.joystick&&F.joystick.remove(),F.muteButton&&F.muteButton.remove(),F.relightingButton&&F.relightingButton.remove(),F.waypointListContainer&&F.waypointListContainer.remove(),F.sceneMenuContainer&&F.sceneMenuContainer.remove(),F.fpsCounter&&F.fpsCounter.remove(),F.hotspotPopup&&F.hotspotPopup.remove(),F.portalPopup&&F.portalPopup.remove(),F.vrButton&&F.vrButton.remove(),F.arButton&&F.arButton.remove(),F.modeContainer&&F.modeContainer.remove(),F.exploreControls&&F.exploreControls.remove();const t=document.getElementById("storysplat-viewer-styles");t&&t.remove(),e.classList.remove("storysplat-viewer-container"),si.forEach(t=>t.destroy()),si.length=0,po(),Et.setCollisionEntities([]),Ct&&Ct.destroy();const n=U.__customScriptSystem;n&&n.dispose();const o=U.__htmlMeshManager;o&&o.destroy(),qt.destroy(),Yt.destroy(),Xt.destroy(),vi.removeEventListener("mousemove",xi),ye&&(ye.destroy(),ye=null),ve&&(ve.destroy(),ve=null),Nt.destroy(),U.destroy(),B.remove()},resize:Di,navigateToScene:async t=>{const e={targetSceneId:t,activationMode:"click"};await fi(e)},setCameraMode:t=>Ce(t),getCameraMode:()=>Lt,setExploreMode:t=>{if("explore"!==Lt&&"walk"!==Lt)throw new Error('[StorySplat Viewer] setExploreMode() requires explore mode. Call setCameraMode("explore") first.');if("walk"===t)"walk"!==Lt&&Ce("walk"),Ee("walk");else{"walk"===Lt&&Ce("explore"),Et.setMode(t),Ee(t);Rt()?x(F,"fly"===t):(S(F,"fly"===t),_(F,"orbit"===t)),E(F)}},setProgress:t=>{if("explore"===Lt)throw new Error('[StorySplat Viewer] setProgress() requires tour mode. Call setCameraMode("tour") first.');En(t)},getProgress:()=>Je,muteAll:()=>oi(),unmuteAll:()=>ii(),isMuted:()=>Hn,getHotspots:()=>Bn.map(t=>{const e=t.hotspotData,n=t.getPosition();return{id:e?.id||"",title:e?.title||e?.information?.substring(0,30)||"",type:e?.type||"sphere",position:{x:n.x,y:n.y,z:n.z}}}),triggerHotspot:t=>{const n=Bn.find(e=>e.hotspotData?.id===t);if(!n)throw new Error(`[StorySplat Viewer] Hotspot not found: ${t}`);const o=n.hotspotData;o&&v(e,o,P)},closeHotspot:()=>{const t=e.querySelector(".storysplat-hotspot-popup"),n=e.querySelector(".storysplat-hotspot-overlay");t&&t.classList.remove("visible"),n&&n.classList.remove("visible"),jn(),Yn(e)},setButtonLabels:t=>{P={...P,...t};const n=t=>l(P,t),o=(t,o)=>{const i=e.querySelector(`.storysplat-mode-btn[data-mode="${t}"]`);i&&(i.textContent=n(o))};o("tour","tour"),o("explore","explore");const i=e.querySelector('.storysplat-explore-btn[data-explore-mode="orbit"]');i&&(i.textContent=n("orbit"));const s=e.querySelector('.storysplat-explore-btn[data-explore-mode="fly"]');s&&(s.textContent=n("fly"));const a=e.querySelector('.storysplat-explore-btn[data-explore-mode="walk"]');a&&(a.textContent=n("walk"));const r=e.querySelector(".storysplat-btn-prev");r&&(r.textContent=n("previous"));const c=e.querySelector(".storysplat-btn-next");c&&(c.textContent=n("next"));const d=e.querySelector(".storysplat-waypoint-list-toggle");if(d){const t=d.querySelector("svg");d.textContent="",d.append(n("waypoints")),t&&d.appendChild(t),d.setAttribute("aria-label",n("waypoints"))}const p=e.querySelector(".storysplat-vr-btn");p&&!p.classList.contains("active")&&(p.textContent=n("vr"),p.setAttribute("aria-label",n("vr")));const h=e.querySelector(".storysplat-ar-btn");h&&!h.classList.contains("active")&&(h.textContent=n("ar"),h.setAttribute("aria-label",n("ar")));const u=e.querySelector(".storysplat-fullscreen-btn");u&&u.setAttribute("aria-label",n("fullscreen"));const m=e.querySelector(".storysplat-hotspot-popup-close");m&&(m.textContent=n("close"));const g=e.querySelector(".storysplat-portal-popup-confirm");g&&(g.textContent=n("yes"));const f=e.querySelector(".storysplat-portal-popup-cancel");f&&(f.textContent=n("cancel"));const y=e.querySelector(".storysplat-help-panel");if(y){y.textContent="";const t=(t,e,n)=>{const o=document.createElement(t);if(n){const t=document.createElement("strong");t.textContent=e,o.appendChild(t)}else o.textContent=e;return y.appendChild(o),o},e=()=>y.appendChild(document.createElement("br"));t("h3",n("helpTitle")),t("p",n("helpCameraModes"),!0),t("p",`• ${n("tour")} - ${n("helpTourDesc")}`),t("p",`• ${n("explore")} - ${n("helpExploreDesc")}`),t("p",`• ${n("walk")} - ${n("helpWalkDesc")}`),e(),t("p",`${n("tour")} Mode:`,!0),t("p","• Scroll - Move along path"),t("p","• Drag - Look around"),e(),t("p",`${n("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",`${n("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 v=e.querySelector(".storysplat-help-btn");v&&v.setAttribute("title",n("helpTitle"))},on:(t,e)=>i.on(t,e),off:(t,e)=>i.off(t,e)};if(c){const e=Oi;return e.setCameraMode=t=>Ce(t),e.getCameraMode=()=>Lt,e.getCameraControls=()=>Et,e.suppressInput=()=>{fn=!0,gn=!1,Et.disable()},e.resumeInput=()=>{fn=!1,"explore"===Lt&&Et.enable()},e.setReticleEnabled=t=>{he=t,t||(Xt.enabled=!1,ee=!1,me=0)},e.setEditingHotspotId=t=>{p=t},e.getApp=()=>U,e.getSplatEntity=()=>et,e.getAllHotspotEntities=()=>Ii,e.getAllLightEntities=()=>Fi,e.getAllPortalEntities=()=>$i,e.getAllCustomMeshEntities=()=>Ui,e.getAllParticleEntities=()=>Bi,e.getAllCollisionMeshEntities=()=>Vi,e.getAllHtmlMeshEntities=()=>{const t=U.__htmlMeshManager,e=new Map;return t&&t.getAllMeshes().forEach((t,n)=>{e.set(n,t.entity)}),e},e.getCamera=()=>bt,e.setProgress=t=>En(t),e.getProgress=()=>Je,e.addHotspot=t=>{const e=t.id||`hotspot-${Date.now()}`,n=ci(t,Bn.length);return Ii.set(e,n),e},e.removeHotspot=t=>{const e=Ii.get(t);if(e){e.destroy(),Ii.delete(t);const n=Bn.indexOf(e);n>=0&&Bn.splice(n,1)}},e.updateHotspot=(t,e)=>{const n=Ii.get(t);if(!n)return;e.position&&n.setPosition(e.position.x,e.position.y,-e.position.z);const o=n.hotspotData||{};n.hotspotData={...o,...e}},e.addLight=e=>{const n=e.id||e.name||`light-${Date.now()}`;let o=null;switch(e.type){case"point":default:o=wo(e);break;case"directional":o=So(e);break;case"hemispheric":o=_o(e);break;case"ambient":Mo(e);break;case"spot":o=Eo(e)}if(o){o.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1}),o.setLocalScale(.25,.25,.25);const e=new t.StandardMaterial;e.diffuse=new t.Color(1,.85,0,1),e.emissive=new t.Color(.5,.42,0,1),e.update(),o.render.meshInstances[0].material=e,U.root.addChild(o),xo.push(o),Fi.set(n,o)}return n},e.removeLight=t=>{const e=Fi.get(t);if(e){e.destroy(),Fi.delete(t);const n=xo.indexOf(e);n>=0&&xo.splice(n,1)}},e.updateLight=(t,e)=>{const n=Fi.get(t);n&&(e.position&&n.setPosition(e.position.x??0,e.position.y??0,-(e.position.z??0)),n.light&&(void 0!==e.intensity&&(n.light.intensity=e.intensity),void 0!==e.range&&(n.light.range=e.range),void 0!==e.castShadows&&(n.light.castShadows=e.castShadows),e.color&&(n.light.color=bo(e.color))))},e.addCustomMesh=t=>{const e=t.id||t.name||`mesh-${Date.now()}`,n=io(t,eo.size);return n&&Ui.set(e,n),e},e.removeCustomMesh=t=>{const e=Ui.get(t);e&&(eo.forEach((t,n)=>{t.entity===e&&(t.modelAsset&&(U.assets.remove(t.modelAsset),t.modelAsset.unload()),eo.delete(n))}),e.destroy(),Ui.delete(t))},e.addParticleSystem=e=>{const n=e.id||e.name||`particle-${Date.now()}`,o=to(e),i=e.particleTexture||"flare";let s,a;"custom"===i&&e.customTextureUrl?(s=e.customTextureUrl,a=`custom_${n}`):(s=Kn[i]||Kn.flare,a=i),Jn(a,s).then(t=>{o.particlesystem&&(o.particlesystem.colorMap=t)}).catch(t=>{console.warn("[Editor] Failed to load particle texture:",t)});const r=new t.Entity("particle-picker");r.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1}),r.setLocalScale(.15,.15,.15);const l=new t.StandardMaterial;l.diffuse=new t.Color(1,.5,0,1),l.opacity=.3,l.blendType=t.BLEND_NORMAL,l.depthWrite=!1,l.update(),r.render.meshInstances[0].material=l,o.addChild(r),U.root.addChild(o);const c=n.replace(/[^a-zA-Z0-9]/g,"_");return Zn.set(c,o),Bi.set(n,o),n},e.removeParticleSystem=t=>{const e=t.replace(/[^a-zA-Z0-9]/g,"_"),n=Bi.get(t)||Zn.get(e);n&&(n.destroy(),Bi.delete(t),Zn.delete(e))},e.addPortal=t=>{const e=t.id||`portal-${Date.now()}`,n=ui(t,Un.length);return $i.set(e,n),e},e.removePortal=t=>{const e=$i.get(t);if(e){const n=e.videoElement;n&&(n.pause(),n.src=""),e.destroy(),$i.delete(t);const o=Un.indexOf(e);o>=0&&Un.splice(o,1)}},e.updatePortal=(t,e)=>{const n=$i.get(t);if(!n)return;if(e.position&&n.setPosition(e.position.x??0,e.position.y??0,-(e.position.z??0)),e.rotation){const t=(e.rotation.x??0)*(180/Math.PI),o=(e.rotation.y??0)*(180/Math.PI),i=-(e.rotation.z??0)*(180/Math.PI);n.setEulerAngles(t,o,i)}e.scale&&("number"==typeof e.scale?n.setLocalScale(e.scale,e.scale,e.scale):n.setLocalScale(e.scale.x??1,e.scale.y??1,e.scale.z??1));const o=n.portalData||{};n.portalData={...o,...e}},e.addHtmlMesh=t=>{const e=t.id||`html-mesh-${Date.now()}`;let n=U.__htmlMeshManager;return n||(n=St(U,[]),U.__htmlMeshManager=n),n.createMesh({...t,id:e,html:t.html||t.htmlContent||""}),e},e.removeHtmlMesh=t=>{const e=U.__htmlMeshManager;e&&e.destroyMesh(t)},e.addCollisionMesh=e=>{const n=e.id||`collision-${Date.now()}`,o=new t.Entity(`collision-mesh-${n}`),i=e.meshType||"cube",s=e.position,a=Array.isArray(s)?s[0]:s.x,r=Array.isArray(s)?s[1]:s.y,l=Array.isArray(s)?s[2]:s.z;if(o.setPosition(a??0,r??0,-(l??0)),e.rotation){const n=e.rotation,s=(Array.isArray(n)?n[0]:n.x)??0,a=((Array.isArray(n)?n[1]:n.y)??0)/2,r=s/2,l=((Array.isArray(n)?n[2]:n.z)??0)/2,c=Math.cos(a),d=Math.sin(a),p=Math.cos(r),h=Math.sin(r),u=Math.cos(l),m=Math.sin(l),g=c*p*u+d*h*m,f=c*h*u+d*p*m,y=d*p*u-c*h*m,v=c*p*m-d*h*u,x=new t.Quat(-f,-y,v,g);if("plane"===i){const e=(new t.Quat).setFromEulerAngles(90,0,0);o.setRotation((new t.Quat).mul2(x,e))}else o.setRotation(x)}const c=e.scaling,d=(c?Array.isArray(c)?c[0]:c.x:1)??1,p=(c?Array.isArray(c)?c[1]:c.y:1)??1,h=(c?Array.isArray(c)?c[2]:c.z:1)??1;if("plane"===i){const t=.01;o.setLocalScale(3*d,h*t,3*p)}else"cube"===i||"sphere"===i?o.setLocalScale(3*d,3*p,3*h):"floor"===i?o.setLocalScale(100*d,1*p,100*h):o.setLocalScale(d,p,h);if("custom"!==i){const e="cube"===i?"box":"floor"===i?"plane":i;o.addComponent("render",{type:e,castShadows:!1,receiveShadows:!1});const n=new t.StandardMaterial;n.diffuse=new t.Color(0,.8,0),n.opacity=1,n.blendType=t.BLEND_NONE,n.depthWrite=!0,n.cull=t.CULLFACE_NONE,n.useLighting=!1,n.update(),o.render&&o.render.meshInstances.forEach(t=>{t.material=n})}return o.addComponent("collision",{type:"sphere"===i?"sphere":"box"}),o.enabled=!1!==e.visible,o._collisionMeshId=n,U.root.addChild(o),Vi.set(n,o),n},e.removeCollisionMesh=t=>{const e=U.root.children;for(let n=e.length-1;n>=0;n--)if(e[n]._collisionMeshId===t){e[n].destroy();break}Vi.delete(t)},e.addAudioEmitter=e=>{const n=e.id||`emitter-${Date.now()}`,o=e.position||{x:0,y:0,z:0},i=new t.Entity(`audio-emitter-${n}`);if(i.setPosition(o.x,o.y,-o.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(n,{volume:e.volume??.5,loop:!1!==e.loop,autoPlay:!1,overlap:!1}),e.url){const o=new t.Asset(`audio-emitter-${n}`,"audio",{url:e.url});o.on("load",()=>{if(lt)return;const t=i.sound?.slot(n);t&&(t.asset=o.id,!1!==e.autoplay&&t.play())}),U.assets.add(o),U.assets.load(o)}i.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1}),i.setLocalScale(.3,.3,.3);const s=new t.StandardMaterial;return s.diffuse=new t.Color(.2,.6,1,1),s.emissive=new t.Color(.1,.3,.5,1),s.update(),i.render.meshInstances[0].material=s,U.root.addChild(i),ei.set(n,{entity:i,config:e,slotId:n,playing:!1,assetReady:!1}),n},e.removeAudioEmitter=t=>{const e=ei.get(t);if(e){const n=e.entity.sound?.slot(e.slotId);n&&n.stop(),e.entity.destroy(),ei.delete(t)}},e.updateAudioEmitter=(t,e)=>{const n=ei.get(t);n&&(e.position&&n.entity.setPosition(e.position.x,e.position.y,-e.position.z),void 0!==e.volume&&n.entity.sound&&(n.entity.sound.volume=e.volume),n.config={...n.config,...e})},e.setSplatRelighting=t=>{if(d.splatRelighting={...d.splatRelighting,...t},!1!==t.enabled){if(!it&&xo.length>0&&et&&To(),it){if(void 0!==t.ambientColor||void 0!==t.ambientIntensity){const e=t.ambientColor??d.splatRelighting?.ambientColor??"#ffffff",n=t.ambientIntensity??d.splatRelighting?.ambientIntensity??0;for(const t of st)t.setAmbientColor(e),t.ambientR*=n,t.ambientG*=n,t.ambientB*=n}if(!0===t.enabled&&!at){for(const t of st)t.enabled=!0,t.setRelightFade(1);at=!0}}void 0===t.shadowsEnabled&&void 0===t.shadowIntensity&&void 0===t.shadowGroundY&&void 0===t.shadowPlaneScale||Io()}else{for(const t of st)t.enabled=!1;at=!1}},e.setSkybox=(t,e)=>{vo(t,e)},e.setSkyboxRotation=e=>{const n=new t.Quat;n.setFromEulerAngles(0,-e*(180/Math.PI),0),U.scene.skyboxRotation=n,ho&&ho.setEulerAngles(0,-e*(180/Math.PI),0),d.skybox&&(d.skybox.rotation=e),d.skyboxRotation=e},e.setBackgroundColor=e=>{const n=new t.Color(e.r/255,e.g/255,e.b/255);bt.camera&&(bt.camera.clearColor=n)},e.setFOV=t=>{bt.camera&&(bt.camera.fov=t)},e.loadSplatUrl=(e,n)=>async function(e,n){const o=[];if(n&&o.push(n),e&&e!==n&&o.push(e),0!==o.length){console.log("[SPLAT] loadSplatByUrl: URLs to try:",o),et&&(console.log("[SPLAT] loadSplatByUrl: Destroying existing splat entity"),et.destroy(),et=null);for(const e of o)try{console.log("[SPLAT] loadSplatByUrl: Trying URL:",e);const n=new t.Asset("splat-"+Date.now(),"gsplat",{url:e});return await new Promise((t,o)=>{n.ready(()=>{if(lt)t();else try{if(et=Pe(n,e),Ct){Ct.setSplatEntity(et);const t=Ct.voxelCollisionInstance;t&&Et.setVoxelCollision(t,et)}console.log("[SPLAT] loadSplatByUrl: New splat entity added to scene"),t()}catch(t){console.error("[SPLAT] loadSplatByUrl: Error during gsplat setup:",t),o(t)}}),n.on("error",t=>{console.error("[SPLAT] loadSplatByUrl: Asset load error:",{url:e,error:t}),o(t)}),Le(n,e)}),void i.emit("loaded",{bandwidthUsed:0,isStorySplatHosted:Ae(e)})}catch(t){console.warn("[SPLAT] loadSplatByUrl: Failed, trying next fallback:",e,t)}throw new Error("[SPLAT] loadSplatByUrl: Failed to load from any URL")}console.warn("[SPLAT] loadSplatByUrl called with no URL")}(e,n),e.addWaypoint=t=>{d.waypoints||(d.waypoints=[]),d.waypoints.push(t)},e.removeWaypoint=t=>{d.waypoints&&t>=0&&t<d.waypoints.length&&d.waypoints.splice(t,1)},e.updateWaypoint=(t,e)=>{d.waypoints&&t>=0&&t<d.waypoints.length&&Object.assign(d.waypoints[t],e)},e.rebuildTourPath=e=>{if(e&&(d.waypoints=e),xn.length=0,bn.length=0,wn.length=0,d.waypoints&&d.waypoints.length>0&&d.waypoints.forEach(e=>{const n=e.position?new t.Vec3(e.position.x,e.position.y,-e.position.z):new t.Vec3(0,d.playerHeight||1.6,0);xn.push(n);const o=e.rotation?ke(e.rotation):new t.Quat;bn.push(o);let i=e.fov||Sn;i<3.5&&(i*=180/Math.PI),wn.push(i)}),xn.length>=2){const t=Ot;Ot=!0,Mn(Je),Ot=t}else xn.length>0&&(dn=xn[0].clone(),pn=bn[0].clone(),_n=wn[0]);console.log("[Editor] Tour path rebuilt with",xn.length,"waypoints")},e.setSplatScale=(t,e,n)=>{const o=e??d.invertXScale??!1,i=n??d.invertYScale??!1;if(d.invertXScale=o,d.invertYScale=i,d.scale={x:t,y:t,z:t},!et)return;const s=o?-t:t,a=i?t:-t,r=o!==i?t:-t;et.setLocalScale(s,a,r)},e.setSplatPosition=(t,e,n)=>{et&&et.setPosition(t,e,-n)},e.setSplatRotation=(t,e,n)=>{et&&et.setEulerAngles(t*(180/Math.PI),e*(180/Math.PI),-n*(180/Math.PI))},e.setAutoplaySpeed=t=>{const e=d.waypoints?.length||1,n=Math.max(1,20*(e-1));an=60*t/n},e.setLoopMode=t=>{ln=t},e.setScrollSpeed=t=>{d.scrollSpeed=t},e.startPlayground=()=>{Qo()},e.stopPlayground=()=>{Ko()},e.isPlaygroundActive=()=>$o,e.initVoxelCollision=async t=>{if(!Ct){const t=null!=d.walkSpeed?d.walkSpeed:Mt;Ct=new $(bt,U,{moveSpeed:t,sprintMultiplier:2,lookSensitivity:1/(d.cameraRotationSensitivity||4e3)*10,playerHeight:d.playerHeight||1.6,gravity:20,jumpVelocity:8,collisionRadius:.3,stepHeight:.3}),z.includes("walk")||z.push("walk"),console.log("[StorySplat Viewer] CharacterController created lazily for voxel collision")}et&&Ct.setSplatEntity(et),await Ct.initVoxelCollision(t);const e=Ct.voxelCollisionInstance;e&&Et.setVoxelCollision(e,et),console.log("[StorySplat Viewer] Voxel collision loaded via editor API")},e.setVoxelDebug=t=>{Ct&&Ct.setVoxelDebug(t)},e.getVoxelDebugVisible=()=>Ct?.voxelDebugVisible??!1,e.clearVoxelDebug=()=>{Ct?.clearVoxelDebug()},e}if(d.customScript&&""!==d.customScript.trim()){console.log("[StorySplat Viewer] Initializing custom script system...");const t=At(Oi,U,B,d.customScript,()=>Je,()=>K);t&&(U.__customScriptSystem=t,console.log("[StorySplat Viewer] Custom script system initialized"))}if(o.analytics){const t=o.analytics.baseUrl??"https://discover.storysplat.com",e=kt(Oi,t,o.analytics.sceneId,o.analytics.ownerId),n=Oi.destroy;Oi.destroy=()=>{e.destroy(),n()}}return Oi}async function Bt(t,e,n){console.log("[StorySplat Viewer] Fetching scene from:",e);const o=await fetch(e);if(!o.ok)throw new Error(`Failed to fetch scene: ${o.statusText}`);const i=await o.json();return console.log("[StorySplat Viewer] Scene data loaded:",i),Ft(t,i,n)}function Ut(t,e,n){return t+(e-t)*n}async function $t(t,e,n,o,i){try{const s=await fetch(`${t}/api/track-embed`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sceneId:e,ownerId:n,type:o,..."bandwidth"===o&&i?{bytes:i}:{}})});s.ok?console.log(`[StorySplat] Tracked ${o}${"bandwidth"===o?` (${(i/1024/1024).toFixed(2)}MB)`:""}`):console.warn(`[StorySplat] Failed to track ${o}:`,s.status)}catch(t){console.warn(`[StorySplat] Error tracking ${o}:`,t)}}class Vt extends Error{constructor(t){super(`Scene not found: ${t}`),this.name="SceneNotFoundError"}}class Ot extends Error{constructor(t,e){super(t),this.name="SceneApiError",this.statusCode=e}}const Wt=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 Nt(t,e,n={}){const o=n.baseUrl||Wt;console.log(`[StorySplat Viewer] Fetching scene: ${e}`);const i=`${o}/api/scene/${encodeURIComponent(e)}`,s={"Content-Type":"application/json"};n.apiKey&&(s.Authorization=`Bearer ${n.apiKey}`);const a=await fetch(i,{method:"GET",headers:s});if(!a.ok){if(404===a.status)throw new Vt(e);const t=await a.text();let n;try{n=JSON.parse(t).error||`API error: ${a.status}`}catch{n=`API error: ${a.status}`}throw new Ot(n,a.status)}const r=await a.json();if(!r.success||!r.data)throw new Ot("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:d,apiKey:p,...h}=n,u=Ft(t,c,{...h,analytics:{sceneId:e,ownerId:l,baseUrl:o}});$t(o,e,l,"view");let m=!1;return u.on("loaded",t=>{m||(m=!0,t&&t.bandwidthUsed>0&&t.isStorySplatHosted?$t(o,e,l,"bandwidth",t.bandwidthUsed):console.log("[StorySplat] Bandwidth not tracked (self-hosted or no data)"))}),u}async function Gt(t,e={}){const n=`${e.baseUrl||Wt}/api/scene/${encodeURIComponent(t)}/meta`,o={"Content-Type":"application/json"};e.apiKey&&(o.Authorization=`Bearer ${e.apiKey}`);const i=await fetch(n,{method:"GET",headers:o});if(!i.ok){if(404===i.status)throw new Vt(t);throw new Ot(`API error: ${i.status}`,i.status)}const s=await i.json();return{...s,userName:s.userName||"Unknown",userSlug:s.userSlug||"unknown"}}class Ht{constructor(e,n,o={}){this.translateGizmo=null,this.rotateGizmo=null,this.scaleGizmo=null,this.activeGizmo=null,this.attachedEntity=null,this._mode="none",this._interacting=!1,this.app=e,this.camera=n,this.gizmoLayer=t.Gizmo.createLayer(e),this.translateGizmo=new t.TranslateGizmo(n,this.gizmoLayer),this.rotateGizmo=new t.RotateGizmo(n,this.gizmoLayer),this.scaleGizmo=new t.ScaleGizmo(n,this.gizmoLayer),this.setSnap(o.snap??!1,o.snapIncrement??1),this.setCoordSpace(o.coordSpace??"world"),this.setupEvents(this.translateGizmo),this.setupEvents(this.rotateGizmo),this.setupEvents(this.scaleGizmo)}get isInteracting(){return this._interacting}setupEvents(t){t.on("pointer:down",(t,e,n)=>{n&&(this._interacting=!0,this.onPointerDown?.())}),t.on("pointer:up",()=>{this._interacting&&(this._interacting=!1,this.onPointerUp?.())}),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()})})}getLayer(){return this.gizmoLayer}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])}get hasAttachment(){return null!==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,this.onPointerDown=t.pointerDown,this.onPointerUp=t.pointerUp}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 Xt{constructor(e,n,o={}){this.selectedEntities=new Set,this.highlightedEntity=null,this.originalMaterials=new Map,this.app=e,this.camera=n,this.config={highlightColor:o.highlightColor??new t.Color(.2,.5,1,1),multiSelect:o.multiSelect??!1},this.picker=new t.Picker(e,1,1,!0)}async pickAtScreenPosition(t,e){const n=this.app.graphicsDevice.canvas,o=this.camera.camera;if(!o||!n)return null;const i=.25,s=Math.max(1,Math.floor(n.clientWidth*i)),a=Math.max(1,Math.floor(n.clientHeight*i));this.picker.resize(s,a);const r=this.app.scene.layers.getLayerByName("World");if(!r)return null;this.picker.prepare(o,this.app.scene,[r]);const l=Math.floor(t*i),c=Math.floor(e*i),d=this.picker.getSelection(l,c);if(d&&d.length>0){const t=d[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 n=this.app.graphicsDevice.canvas,o=this.camera.camera;if(!o||!n)return null;const i=.25,s=Math.max(1,Math.floor(n.clientWidth*i)),a=Math.max(1,Math.floor(n.clientHeight*i));this.picker.resize(s,a);const r=this.app.scene.layers.getLayerByName("World");if(!r)return null;this.picker.prepare(o,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 n=this.getSelectedEntity();e||(this.selectedEntities.forEach(t=>this.removeHighlight(t)),this.selectedEntities.clear()),t&&(this.selectedEntities.add(t),this.applyHighlight(t)),this.onSelectionChange?.({entity:t,previousEntity:n})}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 n=new Map;e.render.meshInstances.forEach((e,o)=>{if(e.material){n.set(o,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,n)}removeHighlight(t){const e=this.originalMaterials.get(t);e&&t.render&&(t.render.meshInstances.forEach((t,n)=>{const o=e.get(n);o&&(t.material=o)}),this.originalMaterials.delete(t))}onSelect(t){this.onSelectionChange=t}async handlePointerDown(t,e=!1){let n,o;if(t instanceof MouseEvent)n=t.clientX,o=t.clientY;else{const e=t.touches[0];n=e.clientX,o=e.clientY}const i=await this.pickAtScreenPosition(n,o);return i?this.select(i,e):e||this.deselectAll(),i}destroy(){this.deselectAll(),this.selectedEntities.clear(),this.originalMaterials.clear()}}class qt{constructor(e,n,o,i={}){this.fpvEntity=null,this._enabled=!1,this.orbitTarget=new t.Vec3,this.orbitDistance=10,this.orbitYaw=0,this.orbitPitch=-30,this.isDragging=!1,this.isPanning=!1,this.lastMouseX=0,this.lastMouseY=0,this._onMouseDown=null,this._onMouseMove=null,this._onMouseUp=null,this._onWheel=null,this._onContextMenu=null,this._onFPVUpdate=null,this.app=e,this.tourCamera=n,this.cameraControls=o,this.config=i,this.editCamera=new t.Entity("editCamera");const s=n.camera;if(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}),i.gizmoLayer&&this.editCamera.camera){const t=this.editCamera.camera.layers;t.includes(i.gizmoLayer.id)||(this.editCamera.camera.layers=[...t,i.gizmoLayer.id])}const a=n.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._onFPVUpdate=()=>{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)}},this.app.on("update",this._onFPVUpdate)}updateOrbitCamera(){const e=this.orbitYaw*t.math.DEG_TO_RAD,n=this.orbitPitch*t.math.DEG_TO_RAD,o=this.orbitTarget.x+this.orbitDistance*Math.cos(n)*Math.sin(e),i=this.orbitTarget.y+this.orbitDistance*Math.sin(n),s=this.orbitTarget.z+this.orbitDistance*Math.cos(n)*Math.cos(e);this.editCamera.setPosition(o,i,s),this.editCamera.lookAt(this.orbitTarget)}attachInputHandlers(){const e=this.app.graphicsDevice.canvas;this._onMouseDown=t=>{0!==t.button||t.shiftKey?1===t.button||0===t.button&&t.shiftKey?(this.isDragging=!0,this.isPanning=!0,t.preventDefault()):2===t.button&&(this.isDragging=!0,this.isPanning=!0):(this.isDragging=!0,this.isPanning=!1),this.lastMouseX=t.clientX,this.lastMouseY=t.clientY},this._onMouseMove=e=>{if(!this.isDragging)return;const n=e.clientX-this.lastMouseX,o=e.clientY-this.lastMouseY;if(this.lastMouseX=e.clientX,this.lastMouseY=e.clientY,this.isPanning){const t=.002*this.orbitDistance,e=this.editCamera.right,i=this.editCamera.up;this.orbitTarget.x-=e.x*n*t-i.x*o*t,this.orbitTarget.y-=e.y*n*t-i.y*o*t,this.orbitTarget.z-=e.z*n*t-i.z*o*t}else{const e=this.config.rotateSensitivity||.3;this.orbitYaw-=n*e,this.orbitPitch+=o*e,this.orbitPitch=t.math.clamp(this.orbitPitch,-89,89)}this.updateOrbitCamera()},this._onMouseUp=()=>{this.isDragging=!1,this.isPanning=!1},this._onWheel=t=>{t.preventDefault();const e=.1*this.orbitDistance;this.orbitDistance+=t.deltaY>0?e:-e,this.orbitDistance=Math.max(.5,this.orbitDistance),this.updateOrbitCamera()},this._onContextMenu=t=>{t.preventDefault()},e.addEventListener("mousedown",this._onMouseDown),e.addEventListener("mousemove",this._onMouseMove),e.addEventListener("mouseup",this._onMouseUp),e.addEventListener("wheel",this._onWheel,{passive:!1}),e.addEventListener("contextmenu",this._onContextMenu)}detachInputHandlers(){const t=this.app.graphicsDevice.canvas;this._onMouseDown&&t.removeEventListener("mousedown",this._onMouseDown),this._onMouseMove&&t.removeEventListener("mousemove",this._onMouseMove),this._onMouseUp&&t.removeEventListener("mouseup",this._onMouseUp),this._onWheel&&t.removeEventListener("wheel",this._onWheel),this._onContextMenu&&t.removeEventListener("contextmenu",this._onContextMenu)}enable(){this._enabled=!0,this.tourCamera.enabled=!1,this.editCamera.enabled=!0,this.cameraControls.disable();const e=this.tourCamera.getPosition(),n=this.tourCamera.forward;this.orbitTarget.set(e.x+5*n.x,e.y+5*n.y,e.z+5*n.z),this.orbitDistance=5;const o=e.x-this.orbitTarget.x,i=e.y-this.orbitTarget.y,s=e.z-this.orbitTarget.z;this.orbitYaw=Math.atan2(o,s)*t.math.RAD_TO_DEG,this.orbitPitch=Math.atan2(i,Math.sqrt(o*o+s*s))*t.math.RAD_TO_DEG,this.updateOrbitCamera(),this.attachInputHandlers(),this.fpvEntity&&(this.fpvEntity.enabled=!0)}disable(){this._enabled=!1,this.editCamera.enabled=!1,this.tourCamera.enabled=!0,this.detachInputHandlers(),this.fpvEntity&&(this.fpvEntity.enabled=!1)}get enabled(){return this._enabled}getCamera(){return this.editCamera}focusOn(t){this.orbitTarget.copy(t),this.orbitDistance=this.config.orbitDistance||10,this.updateOrbitCamera()}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),this.tourCamera.camera&&(this.tourCamera.camera.fov=t)}destroy(){this.detachInputHandlers(),this.disable(),this._onFPVUpdate&&(this.app.off("update",this._onFPVUpdate),this._onFPVUpdate=null),this.editCamera.destroy(),this.fpvEntity&&(this.fpvEntity.destroy(),this.fpvEntity=null)}}var jt;!function(t){t[t.selected=1]="selected",t[t.hidden=2]="hidden",t[t.deleted=4]="deleted"}(jt||(jt={}));const Yt=512,Zt=1536,Qt=[0,4,8,12,1,5,9,13,2,6,10,14];class Kt{constructor(e){this.device=e,this.count=1,this.version=0,this.texture=new t.Texture(e,{name:"splatTransformPalette",width:Zt,height:1,format:t.PIXELFORMAT_RGBA32F,mipmaps:!1,minFilter:t.FILTER_NEAREST,magFilter:t.FILTER_NEAREST,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE}),this.data=new Float32Array(6144),this._writeIdentity(0),this._upload()}alloc(t){const e=this.count;this.count+=t;const n=Math.ceil(this.count/Yt);n>this.texture.height&&this._resize(n);for(let n=0;n<t;n++)this._writeIdentity(e+n);return e}free(t){this.count=Math.max(1,this.count-t)}setTransform(t,e){const n=t%Yt*3,o=Math.floor(t/Yt)*Zt*4,i=e.data;for(let t=0;t<12;t++){const e=Math.floor(t/4),s=t%4;this.data[o+4*(n+e)+s]=i[Qt[t]]}}getTransform(e,n){const o=n??new t.Mat4,i=e%Yt*3,s=Math.floor(e/Yt)*Zt*4,a=o.data;a[3]=0,a[7]=0,a[11]=0,a[15]=1;for(let t=0;t<12;t++){const e=Math.floor(t/4),n=t%4;a[Qt[t]]=this.data[s+4*(i+e)+n]}return o}upload(){this._upload()}destroy(){this.texture.destroy()}_writeIdentity(t){const e=t%Yt*3,n=Math.floor(t/Yt)*Zt*4,o=n+4*e,i=n+4*(e+1),s=n+4*(e+2);for(let t=0;t<4;t++)this.data[o+t]=0,this.data[i+t]=0,this.data[s+t]=0;this.data[o+0]=1,this.data[i+1]=1,this.data[s+2]=1}_upload(){const t=this.texture.lock();t.set(this.data.subarray(0,t.length)),this.texture.unlock()}_resize(e){const n=this.data,o=this.texture.height,i=Math.max(e,2*o);this.texture.destroy(),this.texture=new t.Texture(this.device,{name:"splatTransformPalette",width:Zt,height:i,format:t.PIXELFORMAT_RGBA32F,mipmaps:!1,minFilter:t.FILTER_NEAREST,magFilter:t.FILTER_NEAREST,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE}),this.data=new Float32Array(Zt*i*4),this.data.set(n),this.version++}}const Jt=[{name:"x",type:"float",storage:"x"},{name:"y",type:"float",storage:"y"},{name:"z",type:"float",storage:"z"},{name:"nx",type:"float",storage:"nx"},{name:"ny",type:"float",storage:"ny"},{name:"nz",type:"float",storage:"nz"},{name:"f_dc_0",type:"float",storage:"f_dc_0"},{name:"f_dc_1",type:"float",storage:"f_dc_1"},{name:"f_dc_2",type:"float",storage:"f_dc_2"},{name:"opacity",type:"float",storage:"opacity"},{name:"scale_0",type:"float",storage:"scale_0"},{name:"scale_1",type:"float",storage:"scale_1"},{name:"scale_2",type:"float",storage:"scale_2"},{name:"rot_0",type:"float",storage:"rot_0"},{name:"rot_1",type:"float",storage:"rot_1"},{name:"rot_2",type:"float",storage:"rot_2"},{name:"rot_3",type:"float",storage:"rot_3"}],te=[];for(let t=0;t<45;t++)te.push(`f_rest_${t}`);const ee=[0,9,24,45];class ne{static async serialize(e,n){const o=e.entity.gsplat;if(!o)throw new Error("No gsplat component");let i=this._getGsplatData(o);if(!i)throw new Error("No gsplat data available");if("function"==typeof i.decompress){console.log("[SplatSerializer] Decompressing gsplat data...");const t=i.decompress();i=t instanceof Promise?await t:t,console.log("[SplatSerializer] Decompression complete")}let s=0;for(let t=0;t<e.numSplats;t++)e.state[t]&jt.deleted||s++;const a=Jt.filter(t=>{try{return null!==i.getProp(t.storage)}catch{return!1}});let r=[];for(const t of te)try{null!==i.getProp(t)&&r.push(t)}catch{}if(void 0!==n&&n>=0&&n<=3){const t=ee[n];r=r.filter((e,n)=>n<t)}if(0===a.length)throw new Error(`SplatSerializer: no readable properties found on gsplatData. Data type: ${i?.constructor?.name??typeof i}`);console.log(`[SplatSerializer] Writing ${s} splats (${a.length} props, ${r.length} SH)`);const l=["ply","format binary_little_endian 1.0",`element vertex ${s}`,...[...a.map(t=>`property ${t.type} ${t.name}`),...r.map(t=>`property float ${t}`)],"end_header",""].join("\n"),c=(new TextEncoder).encode(l),d=4*(a.length+r.length),p=c.length+s*d,h=new ArrayBuffer(p),u=new Uint8Array(h);u.set(c,0);const m=new ArrayBuffer(s*d),g=new Float32Array(m),f=a.map(t=>i.getProp(t.storage)),y=r.map(t=>i.getProp(t)),v=a.findIndex(t=>"x"===t.name),x=a.findIndex(t=>"y"===t.name),b=a.findIndex(t=>"z"===t.name),w=a.findIndex(t=>"rot_0"===t.name),S=a.findIndex(t=>"rot_1"===t.name),_=a.findIndex(t=>"rot_2"===t.name),M=a.findIndex(t=>"rot_3"===t.name),E=a.findIndex(t=>"scale_0"===t.name),C=a.findIndex(t=>"scale_1"===t.name),T=a.findIndex(t=>"scale_2"===t.name),A=new Map,P=n=>{if(0===n)return null;let o=A.get(n);if(!o){const i=e.transformPalette.getTransform(n),s=new t.Vec3,a=new t.Quat;i.getScale(s),a.setFromMat4(i),o={mat:i,rot:a,scale:s},A.set(n,o)}return o},L=new t.Vec3,k=new t.Quat;let z=0;for(let t=0;t<e.numSplats;t++){if(e.state[t]&jt.deleted)continue;const n=P(e.transform[t]);if(n){const e=f[v]?.[t]??0,o=f[x]?.[t]??0,i=f[b]?.[t]??0;L.set(e,o,i),n.mat.transformPoint(L,L),w>=0&&(k.set(f[S]?.[t]??0,f[_]?.[t]??0,f[M]?.[t]??0,f[w]?.[t]??1),k.mul2(n.rot,k));for(let e=0;e<f.length;e++){let o=f[e]?.[t]??0;e===v?o=L.x:e===x?o=L.y:e===b?o=L.z:e===w?o=k.w:e===S?o=k.x:e===_?o=k.y:e===M?o=k.z:e===E?o+=Math.log(Math.abs(n.scale.x)):e===C?o+=Math.log(Math.abs(n.scale.y)):e===T&&(o+=Math.log(Math.abs(n.scale.z))),g[z++]=o}}else for(let e=0;e<f.length;e++)g[z++]=f[e]?.[t]??0;for(let e=0;e<y.length;e++)g[z++]=y[e]?.[t]??0}return u.set(new Uint8Array(m),c.length),console.log(`[SplatSerializer] Done — ${(p/1024/1024).toFixed(1)}MB`),u}static _getGsplatData(t){const e=t.instance??t._instance,n=t._placement,o=e??n;if(o?.resource?.gsplatData)return o.resource.gsplatData;if(e?.splat?.gsplatData)return e.splat.gsplatData;const i=t.asset?.resource??t._resource??o?.resource;return i?.gsplatData?i.gsplatData:null}}class oe{constructor(e,n){this._stats={total:0,selected:0,hidden:0,deleted:0,shBands:0},this._material=null,this.tintClr=new t.Color(1,1,1),this.temperature=0,this.brightness=0,this.blackPoint=0,this.whitePoint=1,this.saturation=1,this.transparency=1,this._originalModifyVS=null,this.entity=e,this._app=n,this.device=n.graphicsDevice,this.transformPalette=new Kt(this.device);const o=e.gsplat;if(!o)throw new Error("SplatEditAttachment: entity has no gsplat component");const i=o.instance,s=o._placement;if(!i&&!s)throw new Error("SplatEditAttachment: gsplat instance/placement not ready");const a=o.asset?.resource??i?.resource??s?.resource,r=a?.textureDimensions??a?.streams?.textureDimensions??this._inferTextureDimensions(i??s);this.texWidth=r.x,this.texHeight=r.y,this.numSplats=this._getNumSplats(i??s,a),this.state=new Uint8Array(this.texWidth*this.texHeight),this.transform=new Uint16Array(this.texWidth*this.texHeight),this.stateTexture=new t.Texture(this.device,{name:"splatEditState",width:this.texWidth,height:this.texHeight,format:t.PIXELFORMAT_R8U,mipmaps:!1,minFilter:t.FILTER_NEAREST,magFilter:t.FILTER_NEAREST,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE}),this.transformTexture=new t.Texture(this.device,{name:"splatEditTransform",width:this.texWidth,height:this.texHeight,format:t.PIXELFORMAT_R16U,mipmaps:!1,minFilter:t.FILTER_NEAREST,magFilter:t.FILTER_NEAREST,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE}),this._uploadState(),this._uploadTransform(),this._applyShaders(),this._stats.total=this.numSplats,this._stats.shBands=this._detectSHBands()}get stats(){return{...this._stats}}updateState(){this._uploadState(),this._recountStats()}updateTransform(){this._uploadTransform()}getMaterial(){return this._material}updateMaterialParams(){if(!this._material)return;const t=-this.blackPoint+this.brightness,e=1/Math.max(.001,this.whitePoint-this.blackPoint);this._material.setParameter("clrOffset",[t,t,t]),this._material.setParameter("clrScale",[e*this.tintClr.r*(1+this.temperature),e*this.tintClr.g,e*this.tintClr.b*(1-this.temperature),this.transparency]),this._material.setParameter("saturation",this.saturation),this._material.setParameter("selectedClr",[.2,.4,1,.35]),this._material.setParameter("lockedClr",[.5,.5,.5,.5]),this._material.setParameter("transformPalette",this.transformPalette.texture),this._material.update()}destroy(){this._removeShaders(),this.stateTexture.destroy(),this.transformTexture.destroy(),this.transformPalette.destroy()}_applyShaders(){const t=this.entity.gsplat;if(!t)return;if(t._unified||t.unified?this._material=this._app.scene.gsplat?.material??null:this._material=t.material??null,!this._material)return void console.warn("SplatEditAttachment: could not find gsplat material");const e=this.device?.isWebGPU?"wgsl":"glsl",n=this._material.getShaderChunks(e);this._originalModifyVS=n.get("gsplatModifyVS")??null,n.set("gsplatModifyVS","\n\n// ── Per-splat pick ID hijack ─────────────────────────────────────────\n// PlayCanvas normally writes the component ID (uId uniform) to the pcId\n// work buffer stream. We #undef GSPLAT_ID to prevent that default write,\n// then write the per-splat index ourselves from modifySplatColor.\n// The writePcId() function is already defined by gsplatWorkBufferOutputVS\n// (included before this chunk), so it remains available after the #undef.\n#ifdef GSPLAT_ID\n #define STORYSPLAT_WRITE_SPLAT_ID\n #undef GSPLAT_ID\n#endif\n\nuniform highp usampler2D splatState;\nuniform highp usampler2D splatTransform;\nuniform highp sampler2D transformPalette;\nuniform vec4 selectedClr;\nuniform vec4 lockedClr;\nuniform vec3 clrOffset;\nuniform vec4 clrScale;\nuniform float saturation;\n\nvoid modifySplatCenter(inout vec3 center) {\n // Read per-splat state\n uint vertexState = texelFetch(splatState, splat.uv, 0).r & 7u;\n\n // Deleted splats: push to clip space discard position\n if ((vertexState & 4u) != 0u) {\n center = vec3(0.0, 0.0, 1e10);\n return;\n }\n\n // Hidden splats: push to clip space discard position\n if ((vertexState & 2u) != 0u) {\n center = vec3(0.0, 0.0, 1e10);\n return;\n }\n\n // Apply per-splat transform from palette\n uint transformIndex = texelFetch(splatTransform, splat.uv, 0).r;\n if (transformIndex != 0u) {\n int u = int(transformIndex % 512u) * 3;\n int v = int(transformIndex / 512u);\n mat3x4 t;\n t[0] = texelFetch(transformPalette, ivec2(u, v), 0);\n t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);\n t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);\n\n mat4 paletteMat = transpose(mat4(t[0], t[1], t[2], vec4(0.0, 0.0, 0.0, 1.0)));\n center = (paletteMat * vec4(center, 1.0)).xyz;\n }\n}\n\nvoid modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) {\n // Could apply palette rotation/scale here if needed\n}\n\nvoid modifySplatColor(vec3 center, inout vec4 color) {\n // Read per-splat state\n uint vertexState = texelFetch(splatState, splat.uv, 0).r & 7u;\n\n // ── Write per-splat index to pcId stream for pick pass ──\n // The splat index is computed from splat.uv using the splatState texture dimensions.\n // We write (index + 1) so that index 0 doesn't collide with a zero-cleared buffer,\n // though PlayCanvas clears pick to white (0xFFFFFFFF) so this is mainly defensive.\n #ifdef STORYSPLAT_WRITE_SPLAT_ID\n {\n ivec2 texSize = textureSize(splatState, 0);\n uint splatIndex = uint(splat.uv.y * texSize.x + splat.uv.x);\n writePcId(uvec4(splatIndex, 0u, 0u, 0u));\n }\n #endif\n\n // Apply color adjustments\n color.rgb = (color.rgb + clrOffset) * clrScale.rgb;\n\n // Saturation\n float grey = dot(color.rgb, vec3(0.299, 0.587, 0.114));\n color.rgb = mix(vec3(grey), color.rgb, saturation);\n\n // Transparency\n color.a *= clrScale.a;\n\n // Selected: blend with selection color\n if ((vertexState & 1u) != 0u) {\n color.rgb = mix(color.rgb, selectedClr.rgb, selectedClr.a);\n }\n}\n"),this._material.setParameter("splatState",this.stateTexture),this._material.setParameter("splatTransform",this.transformTexture),this._material.setParameter("transformPalette",this.transformPalette.texture),this._material.setParameter("clrOffset",[0,0,0]),this._material.setParameter("clrScale",[1,1,1,1]),this._material.setParameter("saturation",1),this._material.setParameter("selectedClr",[.2,.4,1,.35]),this._material.setParameter("lockedClr",[.5,.5,.5,.5]),this._material.update()}_removeShaders(){if(!this._material)return;const t=this.device?.isWebGPU?"wgsl":"glsl",e=this._material.getShaderChunks(t);null!==this._originalModifyVS?e.set("gsplatModifyVS",this._originalModifyVS):e.delete("gsplatModifyVS"),this._material.update(),this._material=null}_uploadState(){const t=this.stateTexture.lock();t.set(this.state.subarray(0,t.length)),this.stateTexture.unlock()}_uploadTransform(){const t=this.transformTexture.lock();t.set(this.transform.subarray(0,t.length)),this.transformTexture.unlock()}_recountStats(){let t=0,e=0,n=0;for(let o=0;o<this.numSplats;o++){const i=this.state[o];i&jt.selected&&t++,i&jt.hidden&&e++,i&jt.deleted&&n++}this._stats={total:this.numSplats,selected:t,hidden:e,deleted:n,shBands:this._stats.shBands}}_detectSHBands(){const t=this.entity.gsplat;if(!t)return 0;const e=t.instance??t._instance,n=t._placement,o=e??n,i=t.asset?.resource??o?.resource,s=i?.gsplatData??e?.splat?.gsplatData;if(!s)return 0;let a=0;for(let t=0;t<45;t++)try{if(null==s.getProp(`f_rest_${t}`))break;a++}catch{break}return function(t){return t>=25?3:t>=10?2:t>=1?1:0}(a)}_inferTextureDimensions(t){const e=t?.sorter;if(e?.centers?.width)return{x:e.centers.width,y:e.centers.height};const n=t?.splat?.numSplats??t?.numSplats??0,o=Math.ceil(Math.sqrt(n));return{x:o,y:Math.ceil(n/o)}}_getNumSplats(t,e){return e?.gsplatData?.numSplats?e.gsplatData.numSplats:t?.splat?.numSplats?t.splat.numSplats:t?.numSplats?t.numSplats:this.texWidth*this.texHeight}}const ie={mask:0,rect:1,sphere:2,box:3};class se{constructor(t){this.glProgram=null,this.glFramebuffer=null,this.glResultTexture=null,this.glQuadVAO=null,this.glQuadVBO=null,this.resultWidth=0,this.resultHeight=0,this.device=t}async run(t,e,n,o,i,s,a){const r=this.device.gl;if(!r)return console.warn("[SplatEdit] Intersect: no WebGL2 context"),new Uint8Array(t);this._ensureProgram(r),this._ensureResultTexture(r,t,e.width);const l=this.glProgram,c=r.getParameter(r.CURRENT_PROGRAM),d=r.getParameter(r.FRAMEBUFFER_BINDING),p=r.getParameter(r.VERTEX_ARRAY_BINDING),h=r.getParameter(r.VIEWPORT),u=r.getParameter(r.ACTIVE_TEXTURE);r.useProgram(l),r.bindFramebuffer(r.FRAMEBUFFER,this.glFramebuffer),r.viewport(0,0,this.resultWidth,this.resultHeight),r.disable(r.DEPTH_TEST),r.disable(r.BLEND),r.disable(r.SCISSOR_TEST);let m=8;const g=[],f=(t,e)=>{const n=r.getUniformLocation(l,t);if(null===n)return;const o=m++;g.push(o),r.activeTexture(r.TEXTURE0+o);const i=e.impl;let s=i?._glTexture??i?.glTexture??null;s||(s=this._uploadPcTexture(r,e)),s?r.bindTexture(r.TEXTURE_2D,s):console.warn(`[SplatEdit] Intersect: no GL handle for '${t}'`),r.uniform1i(n,o)};f("transformA",e),f("splatTransform",n),f("transformPalette",o);const y=(t,e)=>{const n=r.getUniformLocation(l,t);n&&r.uniformMatrix4fv(n,!1,e)},v=(t,e,n)=>{const o=r.getUniformLocation(l,t);o&&r.uniform2f(o,e,n)},x=(t,e,n,o)=>{const i=r.getUniformLocation(l,t);i&&r.uniform3f(i,e,n,o)};y("matrix_model",i.data),v("splat_params",s.x,s.y),((t,e)=>{const n=r.getUniformLocation(l,t);null!==n&&r.uniform1i(n,e)})("mode",ie[a.mode]),v("resultParams",this.resultWidth,t),"mask"===a.mode?(f("maskTexture",a.maskTexture),y("matrix_viewProjection",a.viewProjection.data)):"rect"===a.mode?(((t,e,n,o,i)=>{const s=r.getUniformLocation(l,t);s&&r.uniform4f(s,e,n,o,i)})("rectBounds",a.rect.minX,a.rect.minY,a.rect.maxX,a.rect.maxY),y("matrix_viewProjection",a.viewProjection.data)):"sphere"===a.mode?(x("sphereCenter",a.center.x,a.center.y,a.center.z),((t,e)=>{const n=r.getUniformLocation(l,t);null!==n&&r.uniform1f(n,e)})("sphereRadius",a.radius)):"box"===a.mode&&(x("boxMin",a.min.x,a.min.y,a.min.z),x("boxMax",a.max.x,a.max.y,a.max.z)),r.bindVertexArray(this.glQuadVAO),r.drawArrays(r.TRIANGLE_STRIP,0,4);const b=new Uint8Array(this.resultWidth*this.resultHeight*4);r.readPixels(0,0,this.resultWidth,this.resultHeight,r.RGBA,r.UNSIGNED_BYTE,b);for(const t of g){r.activeTexture(r.TEXTURE0+t),r.bindTexture(r.TEXTURE_2D,null);const e=this.device.textureUnits;e?.[t]&&(e[t][0]=null)}r.bindVertexArray(p),r.bindFramebuffer(r.FRAMEBUFFER,d),r.useProgram(c),r.viewport(h[0],h[1],h[2],h[3]),r.activeTexture(u);const w=new Uint8Array(t);let S=0;for(let e=0;e<t;e++){const t=Math.floor(e/4),n=e%4;w[e]=b[4*t+n]>127?1:0,w[e]&&S++}return console.log(`[SplatEdit] Intersect: ${S}/${t} selected`),w}destroy(){const t=this.device.gl;t&&(this.glProgram&&t.deleteProgram(this.glProgram),this.glFramebuffer&&t.deleteFramebuffer(this.glFramebuffer),this.glResultTexture&&t.deleteTexture(this.glResultTexture),this.glQuadVAO&&t.deleteVertexArray(this.glQuadVAO),this.glQuadVBO&&t.deleteBuffer(this.glQuadVBO)),this.glProgram=null,this.glFramebuffer=null,this.glResultTexture=null,this.glQuadVAO=null,this.glQuadVBO=null}_uploadPcTexture(e,n){const o=n._levels?.[0];if(!o)return console.warn("[SplatEdit] _uploadPcTexture: no pixel data in _levels[0]"),null;const i=e.createTexture();if(!i)return null;e.bindTexture(e.TEXTURE_2D,i);const s=n.format;let a,r,l;if(s===t.PIXELFORMAT_RGBA8)a=e.RGBA8,r=e.RGBA,l=e.UNSIGNED_BYTE;else if(s===t.PIXELFORMAT_RGBA32F)a=e.RGBA32F,r=e.RGBA,l=e.FLOAT;else if(s===t.PIXELFORMAT_R8)a=e.R8,r=e.RED,l=e.UNSIGNED_BYTE;else if(s===t.PIXELFORMAT_R8U||33===s)a=e.R8UI,r=e.RED_INTEGER,l=e.UNSIGNED_BYTE;else{if(s!==t.PIXELFORMAT_R16U&&35!==s)return console.warn("[SplatEdit] _uploadPcTexture: unsupported format",s),e.deleteTexture(i),null;a=e.R16UI,r=e.RED_INTEGER,l=e.UNSIGNED_SHORT}e.texImage2D(e.TEXTURE_2D,0,a,n.width,n.height,0,r,l,o);const c=n.minFilter===t.FILTER_LINEAR?e.LINEAR:e.NEAREST,d=n.magFilter===t.FILTER_LINEAR?e.LINEAR:e.NEAREST;e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,c),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,d),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE);const p=n.impl;return p&&(p._glTexture=i),i}_ensureProgram(t){if(this.glProgram)return;const e=t.createShader(t.VERTEX_SHADER);t.shaderSource(e,"#version 300 es\nin vec2 vertex_position;\nout vec2 vUv;\n\nvoid main() {\n vUv = vertex_position * 0.5 + 0.5;\n gl_Position = vec4(vertex_position, 0.0, 1.0);\n}\n"),t.compileShader(e),t.getShaderParameter(e,t.COMPILE_STATUS)||console.error("[SplatEdit] VS compile error:",t.getShaderInfoLog(e));const n=t.createShader(t.FRAGMENT_SHADER);t.shaderSource(n,"#version 300 es\nprecision highp float;\nprecision highp int;\nprecision highp usampler2D;\n\nin vec2 vUv;\nout vec4 fragColor;\n\n// Splat data\nuniform highp sampler2D transformA; // Splat centers (RGBA32F)\nuniform highp usampler2D splatTransform; // Per-splat palette index\nuniform highp sampler2D transformPalette; // Transform palette\n\n// Camera\nuniform mat4 matrix_model;\nuniform mat4 matrix_viewProjection;\n\n// Splat data dimensions\nuniform vec2 splat_params; // x = 1/width, y = 1/height of transformA\n\n// Selection params\nuniform int mode; // 0=mask, 1=rect, 2=sphere, 3=box\nuniform sampler2D maskTexture; // For mode 0\nuniform vec4 rectBounds; // For mode 1: (minX, minY, maxX, maxY) in NDC\nuniform vec3 sphereCenter; // For mode 2\nuniform float sphereRadius; // For mode 2\nuniform vec3 boxMin; // For mode 3\nuniform vec3 boxMax; // For mode 3\n\n// Result texture dimensions\nuniform vec2 resultParams; // x = resultWidth, y = total splats\n\n// Read a splat center from the data texture\nvec3 readCenter(int splatIndex) {\n int texWidth = int(1.0 / splat_params.x);\n int u = splatIndex % texWidth;\n int v = splatIndex / texWidth;\n return texelFetch(transformA, ivec2(u, v), 0).xyz;\n}\n\n// Apply per-splat transform from palette\nvec3 applyTransform(vec3 pos, int splatIndex) {\n int texWidth = int(1.0 / splat_params.x);\n int su = splatIndex % texWidth;\n int sv = splatIndex / texWidth;\n uint transformIndex = texelFetch(splatTransform, ivec2(su, sv), 0).r;\n\n if (transformIndex == 0u) {\n return (matrix_model * vec4(pos, 1.0)).xyz;\n }\n\n int pu = int(transformIndex % 512u) * 3;\n int pv = int(transformIndex / 512u);\n mat3x4 t;\n t[0] = texelFetch(transformPalette, ivec2(pu, pv), 0);\n t[1] = texelFetch(transformPalette, ivec2(pu + 1, pv), 0);\n t[2] = texelFetch(transformPalette, ivec2(pu + 2, pv), 0);\n\n mat4 paletteTransform = transpose(mat4(t[0], t[1], t[2], vec4(0.0, 0.0, 0.0, 1.0)));\n return (matrix_model * paletteTransform * vec4(pos, 1.0)).xyz;\n}\n\n// Test a single splat against the selection criteria\nfloat testSplat(int splatIndex) {\n int totalSplats = int(resultParams.y);\n if (splatIndex >= totalSplats) return 0.0;\n\n vec3 center = readCenter(splatIndex);\n vec3 worldPos = applyTransform(center, splatIndex);\n\n if (mode == 0) {\n // Mask: project to NDC, sample mask texture\n vec4 clip = matrix_viewProjection * vec4(worldPos, 1.0);\n vec2 ndc = clip.xy / clip.w;\n vec2 maskUv = vec2(ndc.x * 0.5 + 0.5, -ndc.y * 0.5 + 0.5);\n if (maskUv.x < 0.0 || maskUv.x > 1.0 || maskUv.y < 0.0 || maskUv.y > 1.0) return 0.0;\n if (clip.w <= 0.0) return 0.0;\n return texture(maskTexture, maskUv).a > 0.5 ? 1.0 : 0.0;\n } else if (mode == 1) {\n // Rectangle: test NDC bounds\n vec4 clip = matrix_viewProjection * vec4(worldPos, 1.0);\n if (clip.w <= 0.0) return 0.0;\n vec2 ndc = clip.xy / clip.w;\n return (ndc.x >= rectBounds.x && ndc.x <= rectBounds.z &&\n ndc.y >= rectBounds.y && ndc.y <= rectBounds.w) ? 1.0 : 0.0;\n } else if (mode == 2) {\n // Sphere: world-space distance test\n float dist = length(worldPos - sphereCenter);\n return dist <= sphereRadius ? 1.0 : 0.0;\n } else if (mode == 3) {\n // Box: world-space AABB containment test\n return (worldPos.x >= boxMin.x && worldPos.x <= boxMax.x &&\n worldPos.y >= boxMin.y && worldPos.y <= boxMax.y &&\n worldPos.z >= boxMin.z && worldPos.z <= boxMax.z) ? 1.0 : 0.0;\n }\n\n return 0.0;\n}\n\nvoid main() {\n int resultWidth = int(resultParams.x);\n int px = int(gl_FragCoord.x);\n int py = int(gl_FragCoord.y);\n\n // Each pixel processes 4 splats\n int baseIndex = (py * resultWidth + px) * 4;\n\n fragColor = vec4(\n testSplat(baseIndex),\n testSplat(baseIndex + 1),\n testSplat(baseIndex + 2),\n testSplat(baseIndex + 3)\n );\n}\n"),t.compileShader(n),t.getShaderParameter(n,t.COMPILE_STATUS)||console.error("[SplatEdit] FS compile error:",t.getShaderInfoLog(n));const o=t.createProgram();t.attachShader(o,e),t.attachShader(o,n),t.linkProgram(o),t.getProgramParameter(o,t.LINK_STATUS)||console.error("[SplatEdit] Program link error:",t.getProgramInfoLog(o)),t.deleteShader(e),t.deleteShader(n),this.glProgram=o,this.glQuadVBO=t.createBuffer(),t.bindBuffer(t.ARRAY_BUFFER,this.glQuadVBO),t.bufferData(t.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,1,1]),t.STATIC_DRAW),this.glQuadVAO=t.createVertexArray(),t.bindVertexArray(this.glQuadVAO);const i=t.getAttribLocation(o,"vertex_position");t.enableVertexAttribArray(i),t.vertexAttribPointer(i,2,t.FLOAT,!1,0,0),t.bindVertexArray(null)}_ensureResultTexture(t,e,n){const o=Math.max(1,Math.floor(n/2)),i=Math.max(1,Math.ceil(e/(4*o)));if(this.resultWidth===o&&this.resultHeight===i)return;this.glFramebuffer&&t.deleteFramebuffer(this.glFramebuffer),this.glResultTexture&&t.deleteTexture(this.glResultTexture),this.resultWidth=o,this.resultHeight=i,this.glResultTexture=t.createTexture(),t.bindTexture(t.TEXTURE_2D,this.glResultTexture),t.texImage2D(t.TEXTURE_2D,0,t.RGBA8,o,i,0,t.RGBA,t.UNSIGNED_BYTE,null),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MIN_FILTER,t.NEAREST),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MAG_FILTER,t.NEAREST),t.bindTexture(t.TEXTURE_2D,null),this.glFramebuffer=t.createFramebuffer(),t.bindFramebuffer(t.FRAMEBUFFER,this.glFramebuffer),t.framebufferTexture2D(t.FRAMEBUFFER,t.COLOR_ATTACHMENT0,t.TEXTURE_2D,this.glResultTexture,0);const s=t.checkFramebufferStatus(t.FRAMEBUFFER);s!==t.FRAMEBUFFER_COMPLETE&&console.error("[SplatEdit] Framebuffer incomplete:",s),t.bindFramebuffer(t.FRAMEBUFFER,null)}}class ae{constructor(t){this.shader=null,this.device=t}async run(e,n,o,i,s,a,r){this._ensureShader();const l=this.device,c=n.width,d=e=>new t.Texture(l,{name:e,width:c,height:1,format:t.PIXELFORMAT_RGBA32F,mipmaps:!1,minFilter:t.FILTER_NEAREST,magFilter:t.FILTER_NEAREST}),p=d("selMin"),h=d("selMax"),u=d("visMin"),m=d("visMax"),g=new t.RenderTarget({colorBuffers:[p,h,u,m],depth:!1});l.scope.resolve("transformA").setValue(n),l.scope.resolve("splatState").setValue(o),l.scope.resolve("splatTransform").setValue(i),l.scope.resolve("transformPalette").setValue(s),l.scope.resolve("matrix_model").setValue(a.data),l.scope.resolve("splat_params").setValue([r.x,r.y]),l.scope.resolve("totalSplats").setValue(e),t.drawQuadWithShader(l,g,this.shader);const f=async e=>{const n=new t.RenderTarget({colorBuffer:e,depth:!1}),o=new Float32Array(4*c),i=l.gl;return i&&(i.bindFramebuffer(i.FRAMEBUFFER,n.impl?._glFrameBuffer),i.readPixels(0,0,c,1,i.RGBA,i.FLOAT,o)),n.destroy(),o},[y,v,x,b]=await Promise.all([f(p),f(h),f(u),f(m)]),w=(e,n)=>{const o=new t.Vec3(1/0,1/0,1/0),i=new t.Vec3(-1/0,-1/0,-1/0);for(let t=0;t<c;t++){const s=e[4*t],a=e[4*t+1],r=e[4*t+2],l=n[4*t],c=n[4*t+1],d=n[4*t+2];isFinite(s)&&(s<o.x&&(o.x=s),a<o.y&&(o.y=a),r<o.z&&(o.z=r)),isFinite(l)&&(l>i.x&&(i.x=l),c>i.y&&(i.y=c),d>i.z&&(i.z=d))}return isFinite(o.x)?{min:o,max:i}:null},S={selectionBound:w(y,v),visibleBound:w(x,b)};return g.destroy(),p.destroy(),h.destroy(),u.destroy(),m.destroy(),S}destroy(){this.shader=null}_ensureShader(){this.shader||(this.shader=new t.Shader(this.device,{name:"splatCalcBound",vshader:"#version 300 es\nin vec2 vertex_position;\nout vec2 vUv;\n\nvoid main() {\n vUv = vertex_position * 0.5 + 0.5;\n gl_Position = vec4(vertex_position, 0.0, 1.0);\n}\n",fshader:"#version 300 es\nprecision highp float;\nprecision highp int;\nprecision highp usampler2D;\n\nin vec2 vUv;\nlayout(location = 0) out vec4 outSelMin;\nlayout(location = 1) out vec4 outSelMax;\nlayout(location = 2) out vec4 outVisMin;\nlayout(location = 3) out vec4 outVisMax;\n\nuniform highp sampler2D transformA;\nuniform highp usampler2D splatState;\nuniform highp usampler2D splatTransform;\nuniform highp sampler2D transformPalette;\nuniform mat4 matrix_model;\nuniform vec2 splat_params; // x = 1/width, y = 1/height\nuniform float totalSplats;\n\nvec3 applyTransform(vec3 pos, ivec2 uv) {\n uint transformIndex = texelFetch(splatTransform, uv, 0).r;\n\n mat4 model = matrix_model;\n if (transformIndex != 0u) {\n int pu = int(transformIndex % 512u) * 3;\n int pv = int(transformIndex / 512u);\n mat3x4 t;\n t[0] = texelFetch(transformPalette, ivec2(pu, pv), 0);\n t[1] = texelFetch(transformPalette, ivec2(pu + 1, pv), 0);\n t[2] = texelFetch(transformPalette, ivec2(pu + 2, pv), 0);\n model = model * transpose(mat4(t[0], t[1], t[2], vec4(0.0, 0.0, 0.0, 1.0)));\n }\n\n return (model * vec4(pos, 1.0)).xyz;\n}\n\nvoid main() {\n int texWidth = int(1.0 / splat_params.x);\n int texHeight = int(1.0 / splat_params.y);\n int col = int(gl_FragCoord.x);\n\n // Initialize bounds to infinity\n vec3 selMin = vec3(1e30);\n vec3 selMax = vec3(-1e30);\n vec3 visMin = vec3(1e30);\n vec3 visMax = vec3(-1e30);\n\n for (int row = 0; row < texHeight; row++) {\n int splatIndex = row * texWidth + col;\n if (splatIndex >= int(totalSplats)) break;\n\n ivec2 uv = ivec2(col, row);\n uint state = texelFetch(splatState, uv, 0).r & 7u;\n\n // Skip deleted splats\n if ((state & 4u) != 0u) continue;\n\n vec3 center = texelFetch(transformA, uv, 0).xyz;\n vec3 worldPos = applyTransform(center, uv);\n\n // Check for infinity/NaN\n if (isinf(worldPos.x) || isnan(worldPos.x)) continue;\n\n // Visible bounds (everything not deleted)\n visMin = min(visMin, worldPos);\n visMax = max(visMax, worldPos);\n\n // Selected bounds\n if ((state & 1u) != 0u) {\n selMin = min(selMin, worldPos);\n selMax = max(selMax, worldPos);\n }\n }\n\n outSelMin = vec4(selMin, 1.0);\n outSelMax = vec4(selMax, 1.0);\n outVisMin = vec4(visMin, 1.0);\n outVisMax = vec4(visMax, 1.0);\n}\n"}))}}class re{constructor(t){this.shader=null,this.device=t}async run(e,n,o,i,s){this._ensureShader();const a=this.device,r=e.width,l=e.height,c=new t.Texture(a,{name:"splatPositions",width:r,height:l,format:t.PIXELFORMAT_RGBA32F,mipmaps:!1,minFilter:t.FILTER_NEAREST,magFilter:t.FILTER_NEAREST}),d=new t.RenderTarget({colorBuffer:c,depth:!1});a.scope.resolve("transformA").setValue(e),a.scope.resolve("splatTransform").setValue(n),a.scope.resolve("transformPalette").setValue(o),a.scope.resolve("matrix_model").setValue(i.data),a.scope.resolve("splat_params").setValue([s.x,s.y]),t.drawQuadWithShader(a,d,this.shader);const p=new Float32Array(r*l*4),h=a.gl;return h&&(h.bindFramebuffer(h.FRAMEBUFFER,d.impl?._glFrameBuffer),h.readPixels(0,0,r,l,h.RGBA,h.FLOAT,p)),d.destroy(),c.destroy(),p}destroy(){this.shader=null}_ensureShader(){this.shader||(this.shader=new t.Shader(this.device,{name:"splatCalcPositions",vshader:"#version 300 es\nin vec2 vertex_position;\nout vec2 vUv;\n\nvoid main() {\n vUv = vertex_position * 0.5 + 0.5;\n gl_Position = vec4(vertex_position, 0.0, 1.0);\n}\n",fshader:"#version 300 es\nprecision highp float;\nprecision highp int;\nprecision highp usampler2D;\n\nin vec2 vUv;\nout vec4 fragColor;\n\nuniform highp sampler2D transformA;\nuniform highp usampler2D splatTransform;\nuniform highp sampler2D transformPalette;\nuniform mat4 matrix_model;\nuniform vec2 splat_params; // x = 1/width, y = 1/height\n\nvoid main() {\n int texWidth = int(1.0 / splat_params.x);\n int col = int(gl_FragCoord.x);\n int row = int(gl_FragCoord.y);\n\n ivec2 uv = ivec2(col, row);\n vec3 center = texelFetch(transformA, uv, 0).xyz;\n\n // Apply per-splat transform from palette\n uint transformIndex = texelFetch(splatTransform, uv, 0).r;\n\n mat4 model = matrix_model;\n if (transformIndex != 0u) {\n int pu = int(transformIndex % 512u) * 3;\n int pv = int(transformIndex / 512u);\n mat3x4 t;\n t[0] = texelFetch(transformPalette, ivec2(pu, pv), 0);\n t[1] = texelFetch(transformPalette, ivec2(pu + 1, pv), 0);\n t[2] = texelFetch(transformPalette, ivec2(pu + 2, pv), 0);\n model = model * transpose(mat4(t[0], t[1], t[2], vec4(0.0, 0.0, 0.0, 1.0)));\n }\n\n vec4 worldPos = model * vec4(center, 1.0);\n fragColor = vec4(worldPos.xyz / worldPos.w, 0.0);\n}\n"}))}}class le{constructor(t){this._queue=Promise.resolve(),this._centersTexture=null,this.device=t,this._intersect=new se(t),this._calcBound=new ae(t),this._calcPositions=new re(t)}intersect(e,n){return this._enqueue(()=>{const o=new t.Vec2(1/e.texWidth,1/e.texHeight),i=e.entity.getWorldTransform(),s=this._getTransformATexture(e);return s?this._intersect.run(e.numSplats,s,e.transformTexture,e.transformPalette.texture,i,o,n):(console.warn("[SplatEdit] intersect: no transformA texture, returning empty result"),Promise.resolve(new Uint8Array(e.numSplats)))})}calcBound(e){return this._enqueue(()=>{const n=new t.Vec2(1/e.texWidth,1/e.texHeight),o=e.entity.getWorldTransform(),i=this._getTransformATexture(e);return i?this._calcBound.run(e.numSplats,i,e.stateTexture,e.transformTexture,e.transformPalette.texture,o,n):Promise.resolve({selectionBound:null,visibleBound:null})})}calcPositions(e){return this._enqueue(()=>{const n=new t.Vec2(1/e.texWidth,1/e.texHeight),o=e.entity.getWorldTransform(),i=this._getTransformATexture(e);return i?this._calcPositions.run(i,e.transformTexture,e.transformPalette.texture,o,n):Promise.resolve(new Float32Array(0))})}destroy(){this._intersect.destroy(),this._calcBound.destroy(),this._calcPositions.destroy(),this._centersTexture?.destroy(),this._centersTexture=null}_getTransformATexture(e){if(this._centersTexture)return this._centersTexture;const n=e.entity.gsplat;if(!n)return null;const o=n.instance??n._instance,i=n._placement,s=o??i,a=n.asset?.resource??s?.resource??n._resource,r=a?.streams??s?._streams;if(r){if("function"==typeof r.getTexture){const t=r.getTexture("dataCenter");if(t)return t}if(r.textures instanceof Map){const t=r.textures.get("dataCenter")??r.textures.get("transformA")??r.textures.get("means");if(t)return t}}if(s?.sorter?.centers)return s.sorter.centers;if(s?.splat?.transformA)return s.splat.transformA;const l=a?.centers??null;if(l&&l.length>0){const n=e.texWidth,o=e.texHeight,i=new t.Texture(this.device,{name:"splatEditCenters",width:n,height:o,format:t.PIXELFORMAT_RGBA32F,mipmaps:!1,minFilter:t.FILTER_NEAREST,magFilter:t.FILTER_NEAREST,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE}),s=i.lock(),a=Math.min(e.numSplats,Math.floor(l.length/3));for(let t=0;t<a;t++)s[4*t+0]=l[3*t+0],s[4*t+1]=l[3*t+1],s[4*t+2]=l[3*t+2],s[4*t+3]=1;return i.unlock(),this._centersTexture=i,i}return console.warn("[SplatEdit] _getTransformATexture: no centers data found.","streams keys:",r?.textures instanceof Map?[...r.textures.keys()]:"N/A","resource.centers:",!!l),null}_enqueue(t){const e=this._queue.then(t);return this._queue=e.then(()=>{},()=>{}),e}}class ce{constructor(){this._undoStack=[],this._redoStack=[],this._maxSize=100}get canUndo(){return this._undoStack.length>0}get canRedo(){return this._redoStack.length>0}execute(t){t.do();for(const t of this._redoStack)t.destroy?.();for(this._redoStack.length=0,this._undoStack.push(t);this._undoStack.length>this._maxSize;){const t=this._undoStack.shift();t?.destroy?.()}this.onChange?.()}undo(){const t=this._undoStack.pop();t&&(t.undo(),this._redoStack.push(t),this.onChange?.())}redo(){const t=this._redoStack.pop();t&&(t.do(),this._undoStack.push(t),this.onChange?.())}clear(){for(const t of this._undoStack)t.destroy?.();for(const t of this._redoStack)t.destroy?.();this._undoStack.length=0,this._redoStack.length=0,this.onChange?.()}}class de{constructor(t,e,n){this.name="Select",this.attachment=t,this.op=e,this.selectionResult=n,this.prevState=new Uint8Array(t.state)}do(){const{state:t}=this.attachment,e=this.attachment.numSplats;for(let n=0;n<e;n++)t[n]&jt.deleted||("set"===this.op?this.selectionResult[n]?t[n]|=jt.selected:t[n]&=~jt.selected:"add"===this.op?this.selectionResult[n]&&(t[n]|=jt.selected):"remove"===this.op&&this.selectionResult[n]&&(t[n]&=~jt.selected));this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class pe{constructor(t){this.name="Select All",this.attachment=t,this.prevState=new Uint8Array(t.state)}do(){const{state:t}=this.attachment,e=this.attachment.numSplats;for(let n=0;n<e;n++)t[n]&jt.deleted||(t[n]|=jt.selected);this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class he{constructor(t){this.name="Select None",this.attachment=t,this.prevState=new Uint8Array(t.state)}do(){const{state:t}=this.attachment,e=this.attachment.numSplats;for(let n=0;n<e;n++)t[n]&=~jt.selected;this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class ue{constructor(t){this.name="Invert Selection",this.attachment=t,this.prevState=new Uint8Array(t.state)}do(){const{state:t}=this.attachment,e=this.attachment.numSplats;for(let n=0;n<e;n++)t[n]&jt.deleted||(t[n]^=jt.selected);this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class me{constructor(t){this.name="Hide Selection",this.attachment=t,this.prevState=new Uint8Array(t.state)}do(){const{state:t}=this.attachment,e=this.attachment.numSplats;for(let n=0;n<e;n++)t[n]&jt.selected&&(t[n]=(t[n]|jt.hidden)&~jt.selected);this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class ge{constructor(t){this.name="Unhide All",this.attachment=t,this.prevState=new Uint8Array(t.state)}do(){const{state:t}=this.attachment,e=this.attachment.numSplats;for(let n=0;n<e;n++)t[n]&=~jt.hidden;this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class fe{constructor(t){this.name="Delete Selection",this.attachment=t,this.prevState=new Uint8Array(t.state)}do(){const{state:t}=this.attachment,e=this.attachment.numSplats;for(let n=0;n<e;n++)t[n]&jt.selected&&(t[n]=(t[n]|jt.deleted)&~jt.selected);this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class ye{constructor(t){this.name="Reset Deleted",this.attachment=t,this.prevState=new Uint8Array(t.state)}do(){const{state:t}=this.attachment,e=this.attachment.numSplats;for(let n=0;n<e;n++)t[n]&=~jt.deleted;this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class ve{constructor(t,e,n,o="Brush Select"){this.attachment=t,this.prevState=e,this.newState=n,this.name=o}do(){this.attachment.state.set(this.newState),this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class xe{constructor(t,e){this.name="Transform Splats",this.newTransformIndices=null,this.allocatedBase=0,this.allocatedCount=0,this.attachment=t,this.transform=e.clone(),this.prevTransformIndices=new Uint16Array(t.transform)}do(){const{state:e,transform:n}=this.attachment,o=this.attachment.transformPalette,i=this.attachment.numSplats,s=new Set;for(let t=0;t<i;t++)e[t]&jt.selected&&s.add(n[t]);this.allocatedCount=s.size,this.allocatedBase=o.alloc(this.allocatedCount);const a=new Map;let r=0;for(const e of s){const n=this.allocatedBase+r;a.set(e,n);const i=o.getTransform(e),s=new t.Mat4;s.mul2(this.transform,i),o.setTransform(n,s),r++}o.upload(),this.attachment.updateMaterialParams(),this.newTransformIndices=new Uint16Array(i);for(let t=0;t<i;t++)e[t]&jt.selected&&a.has(n[t])?(this.newTransformIndices[t]=a.get(n[t]),n[t]=this.newTransformIndices[t]):this.newTransformIndices[t]=n[t];this.attachment.updateTransform()}undo(){this.attachment.transform.set(this.prevTransformIndices),this.attachment.updateTransform(),this.allocatedCount>0&&(this.attachment.transformPalette.free(this.allocatedCount),this.attachment.transformPalette.upload())}destroy(){}}class be{constructor(t,e){this._attachment=null,this._active=!1,this._previewBaseState=null,this._app=t,this._camera=e,this._dataProcessor=new le(t.graphicsDevice),this._history=new ce,this._history.onChange=()=>{this.onUndoRedoChange?.(this._history.canUndo,this._history.canRedo),this._notifyStats()}}activate(t){this._active&&this.deactivate(),this._attachment=new oe(t,this._app),this._attachment.updateMaterialParams(),this._active=!0,this._notifyStats()}deactivate(){this._active&&(this._history.clear(),this._attachment?.destroy(),this._attachment=null,this._active=!1)}isActive(){return this._active}getAttachment(){return this._attachment}async selectByRect(t,e){if(!this._attachment)return;const n=this._getViewProjection(),o=await this._dataProcessor.intersect(this._attachment,{mode:"rect",rect:e,viewProjection:n});this._history.execute(new de(this._attachment,t,o))}async selectByMask(t,e){if(!this._attachment)return;const n=this._getViewProjection(),o=await this._dataProcessor.intersect(this._attachment,{mode:"mask",maskTexture:e,viewProjection:n});this._history.execute(new de(this._attachment,t,o))}async selectBySphere(t,e,n){if(!this._attachment)return;const o=await this._dataProcessor.intersect(this._attachment,{mode:"sphere",center:e,radius:n});this._history.execute(new de(this._attachment,t,o))}async selectByBox(t,e,n){if(!this._attachment)return;const o=await this._dataProcessor.intersect(this._attachment,{mode:"box",min:e,max:n});this._history.execute(new de(this._attachment,t,o))}selectAll(){this._attachment&&this._history.execute(new pe(this._attachment))}selectNone(){this._attachment&&this._history.execute(new he(this._attachment))}selectInvert(){this._attachment&&this._history.execute(new ue(this._attachment))}startSelectionPreview(){this._attachment&&(this._previewBaseState=new Uint8Array(this._attachment.state))}async previewSelectionByMask(t,e){if(!this._attachment)return;const n=this._getViewProjection(),o=await this._dataProcessor.intersect(this._attachment,{mode:"mask",maskTexture:e,viewProjection:n});this._applySelectionDirect(t,o)}commitSelectionPreview(){if(!this._attachment||!this._previewBaseState)return;const t=new Uint8Array(this._attachment.state);this._history.execute(new ve(this._attachment,this._previewBaseState,t,"Brush Select")),this._previewBaseState=null}cancelSelectionPreview(){this._attachment&&this._previewBaseState&&(this._attachment.state.set(this._previewBaseState),this._attachment.updateState(),this._previewBaseState=null,this._notifyStats())}hideSelection(){this._attachment&&this._history.execute(new me(this._attachment))}unhideAll(){this._attachment&&this._history.execute(new ge(this._attachment))}deleteSelection(){this._attachment&&this._history.execute(new fe(this._attachment))}resetDeleted(){this._attachment&&this._history.execute(new ye(this._attachment))}transformSelection(t){this._attachment&&this._history.execute(new xe(this._attachment,t))}undo(){this._history.undo()}redo(){this._history.redo()}get canUndo(){return this._history.canUndo}get canRedo(){return this._history.canRedo}async serializeToPly(t){if(!this._attachment)throw new Error("SplatEditSystem not active");return ne.serialize(this._attachment,t)}getStats(){return this._attachment?.stats??{total:0,selected:0,hidden:0,deleted:0,shBands:0}}async getSelectionBounds(){if(!this._attachment)return null;return(await this._dataProcessor.calcBound(this._attachment)).selectionBound}destroy(){this.deactivate(),this._dataProcessor.destroy()}_applySelectionDirect(t,e){if(!this._attachment)return;const{state:n}=this._attachment,o=this._attachment.numSplats;for(let i=0;i<o;i++)n[i]&jt.deleted||("set"===t?e[i]?n[i]|=jt.selected:n[i]&=~jt.selected:"add"===t?e[i]&&(n[i]|=jt.selected):"remove"===t&&e[i]&&(n[i]&=~jt.selected));this._attachment.updateState(),this._notifyStats()}_getViewProjection(){const e=this._camera.camera;if(!e)throw new Error("Camera entity has no camera component");const n=new t.Mat4;return n.mul2(e.projectionMatrix,e.viewMatrix),n}_notifyStats(){this._attachment&&this.onStatsChange?.(this._attachment.stats)}}const we="Mar 2, 15:20:03";export{we as BUILD_VERSION,R as CameraControls,Tt as CustomScriptSystem,r as DEFAULT_BUTTON_LABELS,qt as EditorCameraController,Pt as FrameSequencePlayer,Ht as GizmoManager,q as GsplatRelighting,tt as REVEAL_PRESETS,Ot as SceneApiError,Vt as SceneNotFoundError,Xt as SelectionManager,oe as SplatEditAttachment,be as SplatEditSystem,ne as SplatSerializer,jt as SplatState,Kt as TransformPalette,B as VoxelCollision,Ft as createViewer,Nt as createViewerFromSceneId,Bt as createViewerFromUrl,a as exportPropsToViewerConfig,Gt as fetchSceneMeta,o as generateHTML,i as generateHTMLFromUrl,c as generateViewerStyles,X as getGsplatRelightingClass,nt as getRevealPreset,kt as setupAnalyticsTracking,At as setupCustomScript,s as transformSceneToExportProps};
2
2
  //# sourceMappingURL=index.esm.js.map