storysplat-viewer 2.9.48 → 2.9.50

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";import{Script as e,Color as n,Vec2 as o,Vec3 as i,PIXELFORMAT_RGBA8 as s,SEMANTIC_POSITION as a,ADDRESS_CLAMP_TO_EDGE as r,BlendState as l,drawQuadWithShader as c}from"playcanvas";const d="2.9.48";function p(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}function h(t){return t.replace(/<\/script/gi,"<\\/script")}function u(t){const e=JSON.stringify(t);return void 0===e?"undefined":h(e).replace(/</g,"\\u003c").replace(/\u2028/g,"\\u2028").replace(/\u2029/g,"\\u2029")}const m=/^G-[A-Z0-9]{6,20}$/i;function g(t,e={}){const{cdnUrl:n=`https://unpkg.com/storysplat-viewer@${d}/dist/storysplat-viewer.umd.js`,title:o=t.name||"StorySplat Scene",description:i=`Interactive 3D scene: ${t.name||"StorySplat"}`,faviconUrl:s,customCSS:a="",lazyLoad:r,lazyLoadButtonText:l,analytics:c,disableAnalytics:g=!1,googleTagId:f}=e,y=r??t.uiOptions?.lazyLoad??!1,v=l??t.uiOptions?.lazyLoadButtonText,b=c&&!g?c:void 0,x=g?void 0:function(t){if("string"!=typeof t)return;const e=t.trim();return m.test(e)?e.toUpperCase():void 0}(f),w=s?`<link rel="icon" href="${p(s)}" />`:"",S=a?`<style>${a}</style>`:"",_=h(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="${p(i)}">\n <title>${p(o)}</title>\n ${w}\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 touch-action: none;\n -webkit-touch-callout: none;\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 ${S}\n</head>\n<body>\n <div id="app"></div>\n\n <script src="https://cdn.jsdelivr.net/npm/playcanvas@2.18.0/build/playcanvas.min.js"><\/script>\n <script src="${p(n)}"><\/script>\n <script>\n (function() {\n 'use strict';\n\n var sceneData = ${_};\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: ${y},\n lazyLoadButtonText: ${v?u(v):"undefined"}${b?`,\n analytics: ${u(b)}`:""}${g?",\n disableAnalytics: true":""}${x?`,\n googleTagId: ${u(x)}`:""}\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 f(t,e={}){const n=await fetch(t);if(!n.ok)throw new Error(`Failed to fetch scene: ${n.statusText}`);return g(await n.json(),e)}function y(t){const e=t.loadedModelUrl||t.splatUrl||"",n=t.sogModelUrl||t.sogUrl,o=t.compressedPlyUrl,i=t.lodMetaUrl,s=t.mobileLoadedModelUrl||t.mobileSplatUrl,a=t.mobileSogModelUrl||t.mobileSogUrl,r=t.mobileCompressedPlyUrl,l=t.mobileLodMetaUrl,c="string"==typeof t.activeSkyboxUrl&&t.activeSkyboxUrl?t.activeSkyboxUrl:"string"==typeof t.skyboxUrl&&t.skyboxUrl?t.skyboxUrl:void 0,d=t.skybox||(c?{url:c,rotation:t.skyboxRotation}:void 0),p=e.split("?")[0].split(".").pop()?.toLowerCase(),h="ply"===p||e.includes(".compressed.ply");let u=e;n?u=n:o?u=o:h||console.warn("[StorySplat Viewer] Original file format may not be compatible with PlayCanvas:",p);const m=(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,curveOut:t.curveOut}}),g=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:u,sogUrl:n,lodMetaUrl:i,mobileSplatUrl:s,mobileSogUrl:a,mobileLodMetaUrl:l,mobileCompressedPlyUrl:r,fallbackUrls:[...t.fallbackUrls||[],n!==u?n:null,o!==u?o:null,e!==u?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:m,hotspots:t.hotspots||[],portals:t.portals||[],skybox:d,skyboxUrl:d?.url,skyboxRotation:d?.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:g,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,watermarkImageUrl:t.uiOptions?.watermarkImageUrl,watermarkImageHeight:t.uiOptions?.watermarkImageHeight,buttonPosition:t.uiOptions?.buttonPosition||"inline",buttonLabels:t.uiOptions?.buttonLabels,customPreloaderLogoUrl:t.uiOptions?.customPreloaderLogoUrl,preloaderLogoSize:t.uiOptions?.preloaderLogoSize,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,sceneMenuLinks:t.uiOptions?.sceneMenuLinks,measurementsEnabled:t.uiOptions?.measurementsEnabled,sceneScale:t.uiOptions?.sceneScale,sceneScaleUnit:t.uiOptions?.sceneScaleUnit,measurementColor:t.uiOptions?.measurementColor,includeDefaultMeasurementsInViewer:t.uiOptions?.includeDefaultMeasurementsInViewer,defaultMeasurementsVisibleOnLoad:t.uiOptions?.defaultMeasurementsVisibleOnLoad,alwaysShowDefaultMeasurements:t.uiOptions?.alwaysShowDefaultMeasurements,hideShareButton:t.uiOptions?.hideShareButton,showFisheyeButton:t.uiOptions?.showFisheyeButton},measurements:t.measurements,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,eighthWallBaseUrl:t.eighthWallBaseUrl,customScript:t.customScript,templateType:t.uiOptions?.uiType||"minimal",additionalSplats:t.additionalSplats||[],keepMeshesInMemory:t.keepMeshesInMemory??!1,enabledExploreModes:t.enabledExploreModes,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":"medium"),revealStyle:t.revealStyle,swapTransitionType:t.swapTransitionType,lodSettings:t.lodSettings,orbitCameraSettings:t.orbitCameraSettings,walkCameraSettings:t.walkCameraSettings,walkSpawnOverride:t.walkSpawnOverride,refocusTapMode:t.refocusTapMode,enableSwapTransition:t.enableSwapTransition,splatSwapMode:t.splatSwapMode,compareSettings:t.compareSettings,entityAnimations:t.entityAnimations||[],postProcessing:t.postProcessing,mirrorPlanes:t.mirrorPlanes||[],weather:t.weather,segmentDataUrl:t.segmentDataUrl,segmentMetaUrl:t.segmentMetaUrl,enableSegmentHover:t.enableSegmentHover,narrationTrack:t.narrationTrack,skins:t.skins}}function v(t){const e=t.fov||t.waypoints?.[0]?.fov||60;return{splatUrl:t.splatUrl,sogUrl:t.sogUrl,lodMetaUrl:t.lodMetaUrl,mobileSplatUrl:t.mobileSplatUrl,mobileSogUrl:t.mobileSogUrl,mobileLodMetaUrl:t.mobileLodMetaUrl,mobileCompressedPlyUrl:t.mobileCompressedPlyUrl,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,enabledExploreModes:t.enabledExploreModes,initialSplatExploreMode:t.initialSplatExploreMode,includeXR:t.includeXR,xrMode:t.xrMode,eighthWallBaseUrl:t.eighthWallBaseUrl,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,orbitCameraSettings:t.orbitCameraSettings,walkCameraSettings:t.walkCameraSettings,walkSpawnOverride:t.walkSpawnOverride,refocusTapMode:t.refocusTapMode,enableSwapTransition:t.enableSwapTransition,splatSwapMode:t.splatSwapMode,compareSettings:t.compareSettings,entityAnimations:t.entityAnimations||[],postProcessing:t.postProcessing,mirrorPlanes:t.mirrorPlanes||[],weather:t.weather,segmentDataUrl:t.segmentDataUrl,segmentMetaUrl:t.segmentMetaUrl,enableSegmentHover:t.enableSegmentHover,narrationTrack:t.narrationTrack,skins:t.skins,measurements:t.measurements}}const b={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",share:"Share",capturingPhoto:"Capturing photo...",photoReady:"Photo ready",downloadPhoto:"Download Photo",photoDownloaded:"Photo downloaded",copyLink:"Copy Experience Link",linkCopied:"Link copied!",shareFailed:"Unable to share photo",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",exitPanorama:"Exit 360°",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 x(t,e){return t?.[e]||b[e]}function w(t="#CC5833",e="minimal",n){const o=n||{},i=o.globalTextColor||"white",s=t=>{if(!("string"!=typeof t||0===t.length||t.length>200||/[{};<>]|\/\*|\*\/|@import|expression\(|url\(|\\/i.test(t)||/[\r\n]/.test(t)))return t},a=s(o.popupTitleFontFamily),r=s(o.popupTitleLetterSpacing),l=s(o.popupContentFontFamily),c=s(o.popupContentLetterSpacing),d=s(o.infoBannerTitleFontFamily),p=s(o.infoBannerTitleLetterSpacing),h=s(o.infoBannerContentFontFamily),u=s(o.infoBannerContentLetterSpacing),m=s(o.shareModalFontFamily);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 font-weight: ${o.progressFontWeight||"normal"};\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 font-weight: ${o.buttonFontWeight||"normal"};\n border-radius: ${o.buttonBorderRadius||"4px"};\n cursor: pointer;\n transition: background 0.2s;\n text-align: center;\n backdrop-filter: blur(${o.dropdownBlur||"10px"});\n -webkit-backdrop-filter: blur(${o.dropdownBlur||"10px"});\n }\n\n .storysplat-btn:hover {\n background: ${o.buttonHoverBg||"rgba(0, 0, 0, 0.5)"};\n }\n\n .storysplat-btn-play {\n background: ${o.buttonBg||"rgba(0, 0, 0, 0.3)"};\n border: none;\n color: ${i};\n padding: 4px 8px;\n border-radius: 4px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n backdrop-filter: blur(${o.dropdownBlur||"10px"});\n -webkit-backdrop-filter: blur(${o.dropdownBlur||"10px"});\n }\n\n .storysplat-btn-play:hover {\n background: ${o.buttonHoverBg||"rgba(0, 0, 0, 0.5)"};\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: 4px;\n cursor: pointer;\n transition: background 0.2s;\n text-align: center;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\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 font-weight: ${o.modeBtnFontWeight||"normal"};\n border-radius: 4px;\n cursor: pointer;\n transition: all 0.2s ease;\n text-align: center;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\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 /* Share Button */\n .storysplat-share-btn {\n position: absolute;\n top: 10px;\n right: 10px;\n background: ${o.shareButtonBg||o.buttonBg||"rgba(0, 0, 0, 0.3)"};\n border: none;\n padding: 8px 10px;\n border-radius: ${o.shareButtonBorderRadius||o.buttonBorderRadius||"8px"};\n cursor: pointer;\n z-index: 1000;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background-color 0.3s ease;\n box-sizing: border-box;\n }\n .storysplat-share-btn svg,\n .storysplat-share-btn svg path {\n fill: ${o.shareButtonIconColor||o.buttonTextColor||i} !important;\n }\n .storysplat-share-btn:hover {\n background: ${o.shareButtonHoverBg||o.buttonHoverBg||"rgba(0, 0, 0, 0.5)"};\n }\n .storysplat-share-btn:disabled {\n cursor: wait;\n opacity: 0.65;\n }\n /* When in top-right-bar, override absolute positioning and match sibling height */\n .storysplat-top-right-bar .storysplat-share-btn {\n position: relative;\n top: auto;\n right: auto;\n align-self: stretch;\n }\n\n .storysplat-photo-share-backdrop {\n position: absolute;\n inset: 0;\n z-index: 100004;\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100%;\n padding: 10px;\n padding: max(10px, env(safe-area-inset-top)) max(10px, env(safe-area-inset-right)) max(10px, env(safe-area-inset-bottom)) max(10px, env(safe-area-inset-left));\n background: ${o.shareModalBackdropBg||"rgba(0, 0, 0, 0.45)"};\n backdrop-filter: blur(${o.shareModalBackdropBlur||"6px"});\n -webkit-backdrop-filter: blur(${o.shareModalBackdropBlur||"6px"});\n box-sizing: border-box;\n }\n\n .storysplat-photo-share-panel {\n width: min(460px, 100%);\n max-height: min(680px, calc(100% - 20px), calc(100vh - 20px));\n max-height: min(680px, calc(100% - 20px), calc(100dvh - 20px));\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n overscroll-behavior: contain;\n background: ${o.shareModalBg||"rgba(16, 16, 16, 0.94)"};\n border: 1px solid ${o.shareModalBorderColor||"rgba(255, 255, 255, 0.18)"};\n border-radius: ${o.shareModalBorderRadius||"8px"};\n color: ${o.shareModalTextColor||i};\n font-family: ${m||o.fontFamily||"system-ui, -apple-system, sans-serif"};\n box-shadow: 0 18px 60px rgba(0, 0, 0, 0.35);\n box-sizing: border-box;\n }\n\n .storysplat-photo-share-header {\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 16px 16px 12px;\n }\n\n .storysplat-photo-share-title {\n display: block;\n color: ${o.shareModalTitleColor||o.shareModalTextColor||i};\n font-size: ${o.shareModalTitleFontSize||"18px"};\n font-weight: ${o.shareModalTitleFontWeight||"750"};\n line-height: 1.15;\n white-space: nowrap;\n }\n\n .storysplat-photo-share-close {\n border: none;\n background: transparent;\n color: ${o.shareModalCloseColor||o.shareModalTextColor||i};\n cursor: pointer;\n font-size: 22px;\n line-height: 1;\n padding: 2px 4px;\n }\n\n .storysplat-photo-share-preview {\n flex: 0 1 auto;\n display: block;\n width: calc(100% - 32px);\n height: min(340px, 38vh);\n height: min(340px, 38dvh);\n min-height: 120px;\n margin: 0 16px 14px;\n object-fit: contain;\n border-radius: ${o.shareModalPreviewBorderRadius||"6px"};\n background: ${o.shareModalPreviewBg||"#111"};\n }\n\n .storysplat-photo-share-socials {\n flex: 0 0 auto;\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 8px;\n padding: 0 16px 12px;\n }\n\n .storysplat-photo-share-social {\n min-height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0 10px;\n border: 1px solid ${o.shareModalBorderColor||"rgba(255, 255, 255, 0.18)"};\n border-radius: ${o.shareModalButtonBorderRadius||"6px"};\n background: ${o.shareModalSocialButtonBg||"rgba(255, 255, 255, 0.06)"};\n color: ${o.shareModalSocialButtonTextColor||o.shareModalTextColor||i};\n cursor: pointer;\n font: inherit;\n font-size: ${o.shareModalSocialFontSize||"13px"};\n font-weight: ${o.shareModalSocialFontWeight||"650"};\n line-height: 1;\n text-align: center;\n text-decoration: none;\n white-space: nowrap;\n }\n .storysplat-photo-share-social:hover {\n background: ${o.shareModalSocialButtonHoverBg||o.shareModalActionButtonHoverBg||o.buttonHoverBg||"rgba(255, 255, 255, 0.12)"};\n }\n\n .storysplat-photo-share-actions {\n flex: 0 0 auto;\n display: grid;\n grid-template-columns: 1fr;\n gap: 8px;\n padding: 0 16px 16px;\n }\n\n .storysplat-photo-share-action {\n min-height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0 12px;\n border: 1px solid ${o.shareModalBorderColor||"rgba(255, 255, 255, 0.22)"};\n border-radius: ${o.shareModalButtonBorderRadius||"6px"};\n background: ${o.shareModalActionButtonBg||"rgba(255, 255, 255, 0.08)"};\n color: ${o.shareModalActionButtonTextColor||o.shareModalTextColor||i};\n cursor: pointer;\n font: inherit;\n font-size: ${o.shareModalActionFontSize||"14px"};\n font-weight: ${o.shareModalActionFontWeight||"650"};\n line-height: 1;\n text-align: center;\n white-space: nowrap;\n }\n .storysplat-photo-share-action:hover:not(:disabled) {\n background: ${o.shareModalActionButtonHoverBg||o.buttonHoverBg||"rgba(255, 255, 255, 0.14)"};\n }\n .storysplat-photo-share-action:disabled {\n cursor: wait;\n opacity: 0.65;\n }\n\n .storysplat-photo-share-action.primary {\n background: ${o.shareModalPrimaryButtonBg||t};\n border-color: ${o.shareModalPrimaryButtonBg||t};\n color: ${o.shareModalPrimaryButtonTextColor||o.shareModalActionButtonTextColor||o.shareModalTextColor||i};\n }\n\n .storysplat-photo-share-action.primary:hover:not(:disabled) {\n background: ${o.shareModalPrimaryButtonHoverBg||o.shareModalPrimaryButtonBg||t};\n border-color: ${o.shareModalPrimaryButtonHoverBg||o.shareModalPrimaryButtonBg||t};\n }\n\n @media (max-width: 520px), (max-height: 720px) {\n .storysplat-photo-share-backdrop {\n padding: 8px;\n padding: max(8px, env(safe-area-inset-top)) max(8px, env(safe-area-inset-right)) max(8px, env(safe-area-inset-bottom)) max(8px, env(safe-area-inset-left));\n }\n\n .storysplat-photo-share-panel {\n width: 100%;\n max-height: min(100%, calc(100vh - 16px));\n max-height: min(100%, calc(100dvh - 16px));\n }\n\n .storysplat-photo-share-header {\n padding: 12px 12px 8px;\n }\n\n .storysplat-photo-share-title {\n max-width: calc(100% - 34px);\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .storysplat-photo-share-preview {\n width: calc(100% - 24px);\n height: min(260px, 30vh);\n height: min(260px, 30dvh);\n min-height: 96px;\n margin: 0 12px 10px;\n }\n\n .storysplat-photo-share-socials {\n gap: 6px;\n padding: 0 12px 10px;\n }\n\n .storysplat-photo-share-actions {\n gap: 6px;\n padding: 0 12px 12px;\n }\n\n .storysplat-photo-share-social {\n min-height: 34px;\n padding: 0 8px;\n }\n\n .storysplat-photo-share-action {\n min-height: 34px;\n padding: 0 10px;\n }\n }\n\n @media (max-height: 600px) {\n .storysplat-photo-share-preview {\n height: min(180px, 24vh);\n height: min(180px, 24dvh);\n min-height: 72px;\n }\n }\n\n /* Mute Button - Minimal (inside toolbar) */\n .storysplat-mute-btn {\n position: relative;\n top: auto;\n left: auto;\n background: transparent;\n border: none;\n padding: 4px;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.2s ease;\n }\n\n .storysplat-mute-btn:hover {\n background: ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.08)"};\n }\n\n .storysplat-mute-btn svg {\n width: 20px;\n height: 20px;\n fill: ${i};\n }\n\n /* Relighting Toggle Button (inside toolbar) */\n .storysplat-relight-btn {\n position: relative;\n top: auto;\n left: auto;\n background: transparent;\n border: none;\n padding: 4px;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.2s ease, opacity 0.3s ease;\n }\n\n .storysplat-relight-btn:hover {\n background: ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.08)"};\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 /* Fisheye / Tiny Planet Toggle Button (inside toolbar) */\n .storysplat-fisheye-btn {\n position: relative;\n top: auto;\n left: auto;\n background: transparent;\n border: none;\n padding: 4px;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.2s ease, opacity 0.3s ease;\n color: ${i};\n }\n .storysplat-fisheye-btn:hover {\n background: ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.08)"};\n }\n .storysplat-fisheye-btn svg {\n width: 20px;\n height: 20px;\n transition: color 0.3s ease;\n }\n .storysplat-fisheye-btn.active {\n color: #4FC3F7;\n }\n\n /* Measurement Tape Measure Button (inside toolbar) */\n .storysplat-measure-btn {\n position: relative;\n top: auto;\n left: auto;\n background: transparent;\n border: none;\n padding: 4px;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.2s ease, opacity 0.3s ease;\n }\n\n .storysplat-measure-btn:hover {\n background: ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.08)"};\n }\n\n .storysplat-measure-btn.active {\n opacity: 1;\n }\n\n .storysplat-measure-btn.active svg {\n fill: #FF9800;\n }\n\n .storysplat-measure-btn svg {\n width: 20px;\n height: 20px;\n fill: ${i};\n }\n\n /* Measurement distance label overlay */\n .storysplat-measure-label {\n position: absolute;\n pointer-events: none;\n background: rgba(0, 0, 0, 0.75);\n color: white;\n font-size: 12px;\n padding: 3px 8px;\n border-radius: 4px;\n white-space: nowrap;\n transform: translate(-50%, -100%) translateY(-8px);\n z-index: 1002;\n font-family: ${o.fontFamily||'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'};\n }\n\n /* Measurement point marker */\n .storysplat-measure-point {\n position: absolute;\n width: 10px;\n height: 10px;\n background: var(--storysplat-measure-color, #FF9800);\n border: 2px solid white;\n border-radius: 50%;\n pointer-events: none;\n transform: translate(-50%, -50%);\n z-index: 1001;\n }\n\n /* Measurement line canvas overlay */\n .storysplat-measure-canvas {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n z-index: 1001;\n }\n\n /* Measurement list dropdown */\n .storysplat-measure-list {\n position: absolute;\n top: 52px;\n left: 10px;\n background: rgba(0, 0, 0, 0.8);\n backdrop-filter: blur(8px);\n border-radius: 6px;\n padding: 8px 0;\n min-width: 160px;\n max-height: 200px;\n overflow-y: auto;\n z-index: 1004;\n display: none;\n font-family: ${o.fontFamily||'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'};\n }\n .storysplat-measure-list-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 4px 10px 6px;\n border-bottom: 1px solid rgba(255,255,255,0.15);\n margin-bottom: 4px;\n }\n .storysplat-measure-list-title {\n font-size: 10px;\n font-weight: 600;\n color: rgba(255,255,255,0.6);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n .storysplat-measure-list-clear {\n background: none;\n border: none;\n color: rgba(255,255,255,0.5);\n font-size: 10px;\n cursor: pointer;\n padding: 2px 4px;\n }\n .storysplat-measure-list-clear:hover {\n color: #ff6b6b;\n }\n .storysplat-measure-list-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 5px 10px;\n color: white;\n font-size: 12px;\n }\n .storysplat-measure-list-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--storysplat-measure-color, #FF9800);\n flex-shrink: 0;\n }\n .storysplat-measure-list-empty {\n padding: 8px 10px;\n color: rgba(255,255,255,0.4);\n font-size: 11px;\n font-style: italic;\n }\n\n /* AI Re-skin dropdown (palette icon, inside top-left toolbar) */\n .storysplat-skins-btn {\n position: relative;\n top: auto;\n left: auto;\n background: transparent;\n border: none;\n padding: 4px;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.2s ease, opacity 0.3s ease;\n }\n .storysplat-skins-btn:hover {\n background: ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.08)"};\n }\n .storysplat-skins-btn.active svg {\n fill: ${t};\n }\n .storysplat-skins-btn svg {\n width: 20px;\n height: 20px;\n fill: ${i};\n }\n .storysplat-skins-dropdown {\n position: absolute;\n top: 52px;\n left: 10px;\n background: rgba(0, 0, 0, 0.85);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n border-radius: 8px;\n padding: 8px;\n width: 240px;\n max-height: 360px;\n overflow-y: auto;\n z-index: 1004;\n display: none;\n font-family: ${o.fontFamily||'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'};\n border: 1px solid rgba(255,255,255,0.1);\n }\n .storysplat-skins-dropdown.open { display: block; }\n .storysplat-skins-dropdown-header {\n font-size: 10px;\n font-weight: 600;\n color: rgba(255,255,255,0.6);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n padding: 2px 4px 8px;\n border-bottom: 1px solid rgba(255,255,255,0.12);\n margin-bottom: 8px;\n }\n .storysplat-skin-item {\n display: flex;\n gap: 8px;\n align-items: center;\n padding: 6px;\n border-radius: 6px;\n cursor: pointer;\n transition: background 0.15s ease;\n position: relative;\n overflow: hidden;\n }\n .storysplat-skin-item:hover { background: rgba(255,255,255,0.08); }\n .storysplat-skin-item.active { background: ${t}; }\n @keyframes storysplat-skin-shimmer {\n 0% { transform: translateX(-100%); }\n 100% { transform: translateX(100%); }\n }\n .storysplat-skin-item.loading::after {\n content: '';\n position: absolute;\n inset: 0;\n background: linear-gradient(\n 90deg,\n transparent 0%,\n rgba(255, 255, 255, 0.18) 50%,\n transparent 100%\n );\n animation: storysplat-skin-shimmer 1.2s ease-in-out infinite;\n pointer-events: none;\n }\n .storysplat-skin-item-thumb {\n width: 56px;\n height: 40px;\n border-radius: 4px;\n object-fit: cover;\n flex-shrink: 0;\n background: rgba(255,255,255,0.05);\n }\n .storysplat-skin-item-prompt {\n font-size: 11px;\n line-height: 1.3;\n color: rgba(255,255,255,0.9);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n }\n .storysplat-skins-empty {\n padding: 12px 6px;\n color: rgba(255,255,255,0.4);\n font-size: 11px;\n text-align: center;\n }\n .storysplat-skin-opacity-row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 4px 4px;\n margin-top: 8px;\n border-top: 1px solid rgba(255,255,255,0.12);\n }\n .storysplat-skin-opacity-row label {\n font-size: 10px;\n color: rgba(255,255,255,0.6);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n .storysplat-skin-opacity-row input[type=range] {\n flex: 1;\n accent-color: ${t};\n }\n .storysplat-skin-exit-btn {\n width: 100%;\n background: rgba(255,255,255,0.08);\n color: white;\n border: none;\n padding: 6px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 11px;\n margin-top: 6px;\n }\n .storysplat-skin-exit-btn:hover { background: rgba(255,255,255,0.15); }\n\n /* Skin overlay (above canvas, below toolbar/dropdowns).\n The 3D camera keeps its vertical FOV constant across viewer sizes, so\n we always match height: 100% and derive width from the captured\n canvas aspect ratio (set inline per activation). The wrapper owns\n the fade-in/out transition so dragging the opacity slider (which\n updates inline opacity on the inner img) stays instant. */\n .storysplat-skin-overlay-wrapper {\n position: absolute;\n top: 0;\n left: 50%;\n height: 100%;\n width: auto;\n transform: translateX(-50%);\n pointer-events: none;\n z-index: 999;\n opacity: 0;\n visibility: hidden;\n transition: opacity 350ms ease-out, visibility 0s linear 350ms;\n }\n .storysplat-skin-overlay-wrapper.active {\n opacity: 1;\n visibility: visible;\n transition: opacity 350ms ease-out, visibility 0s linear 0s;\n }\n .storysplat-skin-overlay {\n display: block;\n height: 100%;\n width: 100%;\n object-fit: fill;\n user-select: none;\n }\n\n .storysplat-ar-mode .storysplat-share-btn,\n .storysplat-ar-mode .storysplat-photo-share-backdrop,\n .storysplat-ar-mode .storysplat-skins-btn,\n .storysplat-ar-mode .storysplat-skins-dropdown,\n .storysplat-ar-mode .storysplat-skin-overlay-wrapper {\n display: none !important;\n }\n\n /* Top-left toolbar container (frosted glass) */\n .storysplat-toolbar {\n position: absolute;\n top: 10px;\n left: 10px;\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 4px;\n background: ${o.buttonBg||"rgba(0, 0, 0, 0.3)"};\n border-radius: 8px;\n border: 1px solid ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.08)"};\n backdrop-filter: blur(${o.dropdownBlur||"10px"});\n -webkit-backdrop-filter: blur(${o.dropdownBlur||"10px"});\n z-index: 1002;\n }\n\n /* Help Button - Minimal */\n .storysplat-help-btn {\n position: relative;\n top: auto;\n left: auto;\n width: 28px;\n height: 28px;\n border-radius: 6px;\n background: transparent;\n color: ${i};\n border: none;\n cursor: pointer;\n font-size: 15px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s ease;\n }\n\n .storysplat-help-btn:hover {\n background: ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.08)"};\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: 52px;\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-vr-btn {\n left: 10px;\n }\n\n .storysplat-ar-btn {\n left: 10px;\n }\n\n /* When both XR buttons are visible, sit AR to the right of VR */\n .storysplat-vr-btn.available ~ .storysplat-ar-btn.available {\n left: 70px;\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: ${o.infoBannerTitleFontWeight||"bold"};\n ${d?`font-family: ${d};`:""}\n ${p?`letter-spacing: ${p};`:""}\n ${o.infoBannerTitleColor?`color: ${o.infoBannerTitleColor};`:""}\n }\n\n .storysplat-waypoint-info p {\n margin: 0;\n font-size: ${o.infoBannerContentFontSize||"14px"};\n font-weight: ${o.infoBannerContentFontWeight||"normal"};\n line-height: ${o.infoBannerContentLineHeight||"1.5"};\n opacity: ${o.infoBannerContentOpacity??"0.9"};\n ${h?`font-family: ${h};`:""}\n ${u?`letter-spacing: ${u};`:""}\n ${o.infoBannerContentColor?`color: ${o.infoBannerContentColor};`:""}\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: ${o.popupTitleFontWeight||"600"};\n ${a?`font-family: ${a};`:""}\n ${r?`letter-spacing: ${r};`:""}\n padding-right: 30px;\n }\n\n .storysplat-hotspot-popup p {\n margin: 0 0 10px 0;\n line-height: ${o.popupContentLineHeight||"1.5"};\n font-size: ${o.popupContentFontSize||"14px"};\n font-weight: ${o.popupContentFontWeight||"normal"};\n ${l?`font-family: ${l};`:""}\n ${c?`letter-spacing: ${c};`:""}\n ${void 0!==o.popupContentOpacity?`opacity: ${o.popupContentOpacity};`:""}\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: ${o.portalPopupTitleFontSize||"16px"};\n font-weight: ${o.portalPopupTitleFontWeight||"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: ${o.portalPopupBtnFontSize||"13px"};\n font-weight: ${o.portalPopupBtnFontWeight||"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 /* Panorama portal loading spinner */\n .storysplat-panorama-loading {\n position: absolute;\n inset: 0;\n z-index: 999;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n opacity: 0;\n animation: storysplat-pano-loading-fadein 180ms ease-out 120ms forwards;\n transition: opacity 200ms ease-out;\n pointer-events: auto;\n }\n .storysplat-panorama-loading--hiding {\n opacity: 0;\n animation: none;\n }\n .storysplat-panorama-loading-spinner {\n width: 40px;\n height: 40px;\n border: 3px solid rgba(255, 255, 255, 0.2);\n border-top-color: white;\n border-radius: 50%;\n animation: storysplat-pano-spin 0.8s linear infinite;\n }\n @keyframes storysplat-pano-spin {\n to { transform: rotate(360deg); }\n }\n @keyframes storysplat-pano-loading-fadein {\n to { opacity: 1; }\n }\n\n /* Panorama portal exit button */\n .storysplat-panorama-exit-btn {\n position: absolute;\n top: 16px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 1000;\n padding: 10px 24px;\n background: rgba(0, 0, 0, 0.65);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n border: 1px solid rgba(255, 255, 255, 0.2);\n border-radius: 24px;\n color: white;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.2s ease, transform 0.2s ease;\n animation: storysplat-panorama-exit-fadein 0.5s ease 0.8s both;\n }\n .storysplat-panorama-exit-btn:hover {\n background: rgba(0, 0, 0, 0.85);\n transform: translateX(-50%) scale(1.05);\n }\n @keyframes storysplat-panorama-exit-fadein {\n from { opacity: 0; transform: translateX(-50%) translateY(-10px); }\n to { opacity: 1; transform: translateX(-50%) translateY(0); }\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 /* Tap for audio overlay */\n .storysplat-tap-for-audio {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: 50;\n color: white;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n font-size: 14px;\n font-weight: 500;\n letter-spacing: 0.3px;\n text-shadow: 0 1px 4px rgba(0, 0, 0, 0.6), 0 0 12px rgba(0, 0, 0, 0.3);\n pointer-events: none;\n opacity: 0;\n transition: opacity 0.4s ease;\n user-select: none;\n }\n .storysplat-tap-for-audio.visible {\n opacity: 1;\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: ${o.waypointListFontSize||"14px"};\n font-weight: ${o.waypointListFontWeight||"normal"};\n line-height: 1;\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: ${o.waypointListFontSize||"14px"};\n font-weight: ${o.waypointListFontWeight||"normal"};\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: ${o.watermarkBg||"rgba(0, 0, 0, 0.3)"};\n color: ${i};\n padding: 5px 10px;\n border-radius: 8px;\n border: 1px solid ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.08)"};\n font-size: ${o.watermarkFontSize||"12px"};\n font-weight: ${o.watermarkFontWeight||"normal"};\n z-index: 1000;\n pointer-events: auto;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n cursor: default;\n backdrop-filter: blur(${o.dropdownBlur||"10px"});\n -webkit-backdrop-filter: blur(${o.dropdownBlur||"10px"});\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-watermark.storysplat-watermark-image {\n background: transparent;\n border: none;\n padding: 0;\n backdrop-filter: none;\n -webkit-backdrop-filter: none;\n }\n\n .storysplat-watermark-logo {\n display: block;\n max-height: var(--storysplat-watermark-img-size, 100px);\n max-width: var(--storysplat-watermark-img-size, 100px);\n object-fit: contain;\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.sceneMenuFontSize||"13px"};\n font-weight: ${o.sceneMenuFontWeight||"normal"};\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 /* Folder headers stay small + bold regardless of sceneMenuFontSize/Weight —\n their job is to visually separate sections, not match item text. */\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.sceneMenuFontSize||"13px"};\n font-weight: ${o.sceneMenuFontWeight||"normal"};\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 .storysplat-scene-menu-link {\n text-decoration: none;\n color: ${o.sceneMenuLinkColor||"white"};\n }\n\n .storysplat-scene-menu-link-icon {\n width: 16px;\n height: 16px;\n fill: currentColor;\n flex-shrink: 0;\n opacity: 0.7;\n }\n\n .storysplat-scene-menu-link-ext {\n width: 12px;\n height: 12px;\n fill: currentColor;\n flex-shrink: 0;\n opacity: 0.4;\n margin-left: auto;\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\n\n /* ===== 8th Wall AR Mode Overrides ===== */\n /* Applied when container has .storysplat-ar-mode class (camera feed visible behind scene) */\n .storysplat-ar-mode .storysplat-waypoint-info {\n background: rgba(0, 0, 0, 0.75);\n }\n\n .storysplat-ar-mode .storysplat-hotspot-popup {\n background: rgba(0, 0, 0, 0.85) !important;\n }\n\n .storysplat-ar-mode .storysplat-custom-menu-toggle,\n .storysplat-ar-mode .storysplat-scene-menu-toggle {\n background: rgba(0, 0, 0, 0.7);\n }\n\n .storysplat-ar-mode .storysplat-toolbar .storysplat-btn {\n display: none;\n }\n\n .storysplat-ar-mode .storysplat-toolbar .storysplat-xr-btn {\n display: block;\n }\n\n .storysplat-ar-mode .storysplat-help-btn {\n display: none !important;\n }\n\n .storysplat-ar-mode .storysplat-measure-canvas {\n display: none !important;\n }\n\n /* Waypoint navigation (prev/next/play/progress) and explore-mode toggles\n don't apply when the scene is anchored to the real world. */\n .storysplat-ar-mode .storysplat-scroll-controls,\n .storysplat-ar-mode .storysplat-explore-controls,\n .storysplat-ar-mode .storysplat-waypoint-list-container,\n .storysplat-ar-mode .storysplat-waypoint-info,\n .storysplat-ar-mode .storysplat-scene-menu-container,\n .storysplat-ar-mode .storysplat-top-right-bar,\n .storysplat-ar-mode .storysplat-fullscreen-btn,\n .storysplat-ar-mode .storysplat-mute-btn,\n .storysplat-ar-mode .storysplat-mode-container {\n display: none !important;\n }\n\n /* Keep the AR button itself visible so the user can exit AR. */\n .storysplat-ar-mode .storysplat-ar-btn.available {\n display: block !important;\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=S[n];if(!t)continue;const i=C(o,n);e+=` ${t} { ${_.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 S={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"},_=new Set(["progressText","modeToggle","exploreControls","prevButton","playButton","nextButton"]);function C(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 M(t="#CC5833",e="minimal",n,o){const i=o?`storysplat-viewer-styles-${o}`:`storysplat-viewer-styles-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,s=document.getElementById(i);s&&s.remove();const a=document.createElement("style");return a.id=i,a.textContent=w(t,e,n),document.head.appendChild(a),a}function E(t,e,n,o){const i=document.createElement("div");i.className="storysplat-preloader";const s=!!e,a=document.createElement("div");a.className="storysplat-preloader-content";const r=document.createElement("div");if(r.className="storysplat-preloader-media",s){const t="video"===j(e)?document.createElement("video"):document.createElement("img");t.className="storysplat-preloader-image",t.src=e,t instanceof HTMLVideoElement?(t.autoplay=!0,t.loop=!0,t.muted=!0,t.playsInline=!0):t.alt="Custom Logo","number"==typeof o&&o>0&&(t.style.height=`${o}px`),r.appendChild(t)}else{const t=document.createElement("span");t.className="storysplat-preloader-logo-text",t.textContent="StorySplat",r.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),r.appendChild(e)}a.appendChild(r);const l=document.createElement("div");l.className="storysplat-preloader-progress";const c=document.createElement("div");c.className="storysplat-preloader-text",c.textContent=`${x(n,"loading")} 0%`;const d=document.createElement("div");return d.className="storysplat-preloader-bar",l.appendChild(c),l.appendChild(d),a.appendChild(l),i.appendChild(a),t.appendChild(i),s||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)}(),i}function P(t){t.classList.add("hidden"),setTimeout(()=>t.remove(),500)}function k(t,e,n=2e3){const o=t.querySelector(".storysplat-share-toast");o&&o.remove();const i=document.createElement("div");i.className="storysplat-share-toast",i.textContent=e,i.style.cssText="position:absolute;top:50px;right:10px;background:rgba(0,0,0,0.82);color:white;padding:8px 16px;border-radius:8px;font-size:14px;z-index:100005;pointer-events:none;font-family:system-ui,-apple-system,sans-serif;",t.appendChild(i),setTimeout(()=>i.remove(),n)}function T(t,e,n={}){const{uiColor:o="#4CAF50",showScrollControls:i=!0,showModeToggle:s=!0,showFullscreenButton:a=!0,showHelpButton:r=!1,showMuteButton:l=!1,showPreloader:c=!0,allowedCameraModes:d=["tour","explore"],enabledExploreModes:p=["orbit","fly"],defaultCameraMode:h="tour",customPreloaderLogoUrl:u,preloaderLogoSize:m,buttonLabels:g,hideWatermark:f=!1,watermarkText:y,watermarkLink:v,watermarkImageUrl:b,watermarkImageHeight:w,sceneId:S,showWaypointList:_=!0,template:C="minimal",hideProgressText:P=!0,viewerTheme:k,showRelightingToggle:T=!1,showFisheyeButton:A=!1,showSceneMenu:L,sceneMenuLinks:R,measurementsEnabled:z=!1,measurementColor:F,showShareButton:B=!0}=n,V={tour:x(g,"tour"),explore:x(g,"explore"),walk:x(g,"walk"),orbit:x(g,"orbit"),fly:x(g,"fly"),previous:x(g,"previous"),next:x(g,"next"),fullscreen:x(g,"fullscreen"),waypoints:x(g,"waypoints"),close:x(g,"close"),yes:x(g,"yes"),cancel:x(g,"cancel"),vr:x(g,"vr"),ar:x(g,"ar"),loading:x(g,"loading"),helpTitle:x(g,"helpTitle"),helpCameraModes:x(g,"helpCameraModes"),helpTourDesc:x(g,"helpTourDesc"),helpExploreDesc:x(g,"helpExploreDesc"),helpWalkDesc:x(g,"helpWalkDesc"),helpTourControls:x(g,"helpTourControls"),helpTourScroll:x(g,"helpTourScroll"),helpTourDrag:x(g,"helpTourDrag"),helpExploreControls:x(g,"helpExploreControls"),helpExploreLMB:x(g,"helpExploreLMB"),helpExploreRMB:x(g,"helpExploreRMB"),helpExploreWASD:x(g,"helpExploreWASD"),helpExploreShift:x(g,"helpExploreShift"),helpExploreScroll:x(g,"helpExploreScroll"),helpExploreDblClick:x(g,"helpExploreDblClick"),helpWalkControls:x(g,"helpWalkControls"),helpWalkClick:x(g,"helpWalkClick"),helpWalkWASD:x(g,"helpWalkWASD"),helpWalkMouse:x(g,"helpWalkMouse"),helpWalkShift:x(g,"helpWalkShift"),helpWalkSpace:x(g,"helpWalkSpace"),hotspotDefaultTitle:x(g,"hotspotDefaultTitle"),openExternalLink:x(g,"openExternalLink"),mute:x(g,"mute"),unmute:x(g,"unmute"),share:x(g,"share"),scenes:x(g,"scenes")},$={};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, .storysplat-measure-btn, .storysplat-measure-canvas, .storysplat-measure-label, .storysplat-measure-point, .storysplat-toolbar, .storysplat-share-btn, .storysplat-photo-share-backdrop, .storysplat-share-toast").forEach(t=>t.remove()),M(o,C,k,t.id),t.classList.add("storysplat-viewer-container"),c&&($.preloader=E(t,u,g,m));const O=document.createElement("div");O.className="storysplat-waypoint-info",O.innerHTML='\n <h2 class="storysplat-waypoint-title"></h2>\n <p class="storysplat-waypoint-description"></p>\n ',t.appendChild(O),$.waypointInfo=O;const N=document.createElement("div");if(N.className="storysplat-tap-for-audio",N.textContent="Tap for audio",N.style.display="none",t.appendChild(N),$.tapForAudio=N,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">${V.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">${V.next}</button>\n </div>\n <div class="storysplat-explore-controls">\n ${d.includes("explore")&&p.includes("orbit")?`<button class="storysplat-explore-btn" data-explore-mode="orbit">${V.orbit}</button>`:""}\n ${d.includes("explore")&&p.includes("fly")?`<button class="storysplat-explore-btn" data-explore-mode="fly">${V.fly}</button>`:""}\n ${d.includes("walk")?`<button class="storysplat-explore-btn" data-explore-mode="walk">${V.walk}</button>`:""}\n </div>\n ${s&&d.includes("tour")&&(d.includes("explore")||d.includes("walk"))?`\n <div class="storysplat-mode-container">\n <div class="storysplat-mode-toggle">\n ${d.includes("tour")?`<button class="storysplat-mode-btn ${"tour"===h?"selected":""}" data-mode="tour">${V.tour}</button>`:""}\n ${d.includes("explore")&&d.includes("walk")?`<button class="storysplat-mode-btn ${"explore"===h||"walk"===h?"selected":""}" data-mode="explore">${V.explore}</button>`:""}\n ${d.includes("explore")&&!d.includes("walk")?`<button class="storysplat-mode-btn ${"explore"===h?"selected":""}" data-mode="explore">${V.explore}</button>`:""}\n ${!d.includes("explore")&&d.includes("walk")?`<button class="storysplat-mode-btn ${"walk"===h?"selected":""}" data-mode="walk">${V.walk}</button>`:""}\n </div>\n </div>\n `:""}\n </div>\n `,t.appendChild(e),$.scrollControls=e,$.progressBar=e.querySelector(".storysplat-progress-bar"),$.progressText=e.querySelector(".storysplat-progress-text"),$.exploreControls=e.querySelector(".storysplat-explore-controls"),$.modeContainer=e.querySelector(".storysplat-mode-container"),$.prevButton=e.querySelector(".storysplat-btn-prev"),$.playButton=e.querySelector(".storysplat-btn-play"),$.nextButton=e.querySelector(".storysplat-btn-next"),"pro"===C){const e=document.createElement("div");e.className="storysplat-top-right-bar "+(a?"with-fullscreen":""),t.appendChild(e),$.modeContainer&&e.appendChild($.modeContainer)}const n=k?.uiLayout,o=t=>n?.elements?.[t]||n?.mobile?.[t];o("progressText")&&$.progressText&&t.appendChild($.progressText),o("exploreControls")&&$.exploreControls&&t.appendChild($.exploreControls),o("prevButton")&&$.prevButton&&t.appendChild($.prevButton),o("playButton")&&$.playButton&&t.appendChild($.playButton),o("nextButton")&&$.nextButton&&t.appendChild($.nextButton),"pro"!==C&&o("modeToggle")&&$.modeContainer&&t.appendChild($.modeContainer),P&&$.progressText&&($.progressText.style.display="none")}if(a){const e=document.createElement("button");e.className="storysplat-fullscreen-btn",e.setAttribute("aria-label",V.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),$.fullscreenButton=e}const W=document.createElement("div");if(W.className="storysplat-toolbar",t.appendChild(W),$.toolbar=W,l){const t=document.createElement("button");t.className="storysplat-mute-btn",t.setAttribute("aria-label",V.mute);const e=document.createElementNS("http://www.w3.org/2000/svg","svg");e.classList.add("storysplat-unmuted-icon"),e.setAttribute("viewBox","0 0 24 24");const n=document.createElementNS("http://www.w3.org/2000/svg","path");n.setAttribute("d","M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"),e.appendChild(n),t.appendChild(e);const o=document.createElementNS("http://www.w3.org/2000/svg","svg");o.classList.add("storysplat-muted-icon"),o.setAttribute("viewBox","0 0 24 24"),o.style.display="none";const i=document.createElementNS("http://www.w3.org/2000/svg","path");i.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"),o.appendChild(i),t.appendChild(o),W.appendChild(t),$.muteButton=t}if(T){const t=document.createElement("button");t.className="storysplat-relight-btn active",t.setAttribute("aria-label","Toggle Relighting");const e=document.createElementNS("http://www.w3.org/2000/svg","svg");e.setAttribute("viewBox","0 0 24 24");const n=document.createElementNS("http://www.w3.org/2000/svg","path");n.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"),e.appendChild(n),t.appendChild(e),W.appendChild(t),$.relightingButton=t}if(A){const t=document.createElement("button");t.className="storysplat-fisheye-btn",t.setAttribute("aria-label","Toggle Tiny Planet"),t.setAttribute("title","Tiny Planet");const e=document.createElementNS("http://www.w3.org/2000/svg","svg");e.setAttribute("viewBox","0 0 24 24");const n=document.createElementNS("http://www.w3.org/2000/svg","circle");n.setAttribute("cx","12"),n.setAttribute("cy","12"),n.setAttribute("r","9"),n.setAttribute("fill","none"),n.setAttribute("stroke","currentColor"),n.setAttribute("stroke-width","2");const o=document.createElementNS("http://www.w3.org/2000/svg","circle");o.setAttribute("cx","12"),o.setAttribute("cy","12"),o.setAttribute("r","3.5"),o.setAttribute("fill","currentColor"),e.appendChild(n),e.appendChild(o),t.appendChild(e),W.appendChild(t),$.fisheyeButton=t}if(_&&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="${V.waypoints}">\n ${V.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),$.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(B){const e=document.createElement("button");e.className="storysplat-share-btn",e.setAttribute("aria-label",V.share),e.setAttribute("title",V.share);const n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("viewBox","0 0 24 24"),n.style.width="20px",n.style.height="20px",n.style.fill="white";const o=document.createElementNS("http://www.w3.org/2000/svg","path");o.setAttribute("d","M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z"),n.appendChild(o),e.appendChild(n);const i=t.querySelector(".storysplat-top-right-bar");if(i)i.appendChild(e);else{if($.waypointListContainer){const n=a?45:10;requestAnimationFrame(()=>{const o=$.waypointListContainer?.querySelector(".storysplat-waypoint-list-toggle"),i=o?o.offsetWidth:120;e.style.right=`${n+i+8}px`,o&&(e.style.top=o.getBoundingClientRect().top-t.getBoundingClientRect().top+"px",e.style.height=o.offsetHeight+"px")})}else a&&(e.style.right="45px");t.appendChild(e)}$.shareButton=e}const H=e.portals&&e.portals.length>0,G=R&&R.length>0;if((H||G)&&!1!==L){const n=document.createElement("div");n.className="storysplat-scene-menu-container";const o=V.scenes,i=document.createElement("button");i.className="storysplat-scene-menu-toggle",i.setAttribute("aria-label",o);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","M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"),s.appendChild(a),i.appendChild(s);const r=document.createElement("span");r.className="storysplat-scene-menu-label",r.textContent=o,i.appendChild(r);const l=document.createElement("div");l.className="storysplat-scene-menu-dropdown",function(t,e,n=[]){const o={children:new Map,items:[]};function i(t,e){const n=e?.trim();if(!n)return void o.items.push(t);const i=n.split("/").map(t=>t.trim()).filter(Boolean);let s=o;for(const t of i)s.children.has(t)||s.children.set(t,{name:t,children:new Map,items:[]}),s=s.children.get(t);s.items.push(t)}const s=new Set;for(const t of e)t.menuOnly&&t.targetSceneId&&s.add(t.targetSceneId);for(const t of e)!t.menuOnly&&t.targetSceneId&&s.has(t.targetSceneId)||i({type:"portal",data:t},t.menuPath);for(const t of n)i({type:"link",data:t},t.menuPath);function a(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 r(e){const n=e.contentType&&"link"!==e.contentType,o=e.mediaUrl||e.url,i=n?document.createElement("div"):document.createElement("a");i.className="storysplat-scene-menu-item storysplat-scene-menu-link",n||(i.setAttribute("href",e.url),i.setAttribute("target",!1===e.openInNewTab?"_self":"_blank"),i.setAttribute("rel","noopener noreferrer")),i.setAttribute("data-link-id",e.id),n&&(i.setAttribute("data-content-type",e.contentType),i.setAttribute("data-media-url",o),i.style.cursor="pointer");const s=document.createElementNS("http://www.w3.org/2000/svg","svg");s.setAttribute("viewBox","0 0 24 24"),s.classList.add("storysplat-scene-menu-link-icon");const a=document.createElementNS("http://www.w3.org/2000/svg","path");a.setAttribute("d",U[e.icon||"link"]),s.appendChild(a),i.appendChild(s);const r=document.createElement("span");if(r.className="storysplat-scene-menu-item-label",r.textContent=e.label,i.appendChild(r),!n){const t=document.createElementNS("http://www.w3.org/2000/svg","svg");t.setAttribute("viewBox","0 0 24 24"),t.classList.add("storysplat-scene-menu-link-ext");const e=document.createElementNS("http://www.w3.org/2000/svg","path");e.setAttribute("d","M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"),t.appendChild(e),i.appendChild(t)}return i.addEventListener("click",i=>{i.stopPropagation(),n&&(i.preventDefault(),l(e.label,e.contentType,o,t.closest(".storysplat-viewer-container")||document.body))}),i}function l(t,e,n,o){const i=o.querySelector(".storysplat-menu-media-popup");i&&i.remove();const s=document.createElement("div");s.className="storysplat-menu-media-popup storysplat-hotspot-popup fullscreen",s.style.cssText="position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 100001; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,0.85);";let a=null;const r=()=>{a&&a();const t=s.querySelector("video");t&&(t.pause(),t.src=""),s.remove(),document.removeEventListener("keydown",l)},l=t=>{"Escape"===t.key&&r()};document.addEventListener("keydown",l);const c=document.createElement("button");c.className="storysplat-hotspot-popup-close",c.textContent="×",c.addEventListener("click",r),s.appendChild(c);const d=document.createElement("div");if(d.className="storysplat-hotspot-popup-content",d.style.cssText="width: 90%; max-width: 900px; max-height: 85vh; display: flex; flex-direction: column; align-items: center;",t){const e=document.createElement("h3");e.className="storysplat-hotspot-popup-title",e.textContent=t,e.style.cssText="color: white; margin: 0 0 12px 0; font-size: 18px;",d.appendChild(e)}if("pdf"===e)if(D())a=I(n,d);else{const e=document.createElement("iframe");e.src=n,e.title=t||"PDF Document",e.style.cssText="width: 100%; height: 70vh; max-height: 600px; border: none; border-radius: 8px;",d.appendChild(e)}else if("iframe"===e){const e=document.createElement("iframe");e.src=n,e.title=t||"Content",e.style.cssText="width: 100%; height: 70vh; max-height: 600px; border: none; border-radius: 8px;",d.appendChild(e)}else if("image"===e){const e=document.createElement("img");e.src=n,e.alt=t||"Image",e.style.cssText="max-width: 100%; max-height: 70vh; object-fit: contain; border-radius: 8px;",d.appendChild(e)}else if("video"===e){const t=document.createElement("video");t.src=n,t.controls=!0,t.setAttribute("playsinline",""),t.setAttribute("preload","metadata"),t.style.cssText="max-width: 100%; max-height: 70vh; border-radius: 8px;",d.appendChild(t)}s.appendChild(d),s.addEventListener("click",t=>{t.target===s&&r()}),o.appendChild(s)}function c(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 i=document.createElementNS("http://www.w3.org/2000/svg","svg");i.setAttribute("viewBox","0 0 24 24");const s=document.createElementNS("http://www.w3.org/2000/svg","path");s.setAttribute("d","M7 10l5 5 5-5z"),i.appendChild(s),o.appendChild(i),o.appendChild(document.createTextNode(n.name)),e.appendChild(o);const a=document.createElement("div");a.className="storysplat-scene-menu-folder-children",c(a,n),e.appendChild(a),t.appendChild(e)}for(const n of e.items)"portal"===n.type?t.appendChild(a(n.data)):t.appendChild(r(n.data))}c(t,o)}(l,e.portals||[],R||[]),n.appendChild(i),n.appendChild(l),t.appendChild(n),$.sceneMenuContainer=n,i.addEventListener("click",t=>{t.stopPropagation(),i.classList.toggle("open"),l.classList.toggle("open")}),l.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)||(i.classList.remove("open"),l.classList.remove("open"))})}if(z){F&&t.style.setProperty("--storysplat-measure-color",F);const e=document.createElement("button");e.className="storysplat-measure-btn",e.setAttribute("aria-label","Measure");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","M21 6H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 10H3V8h2v4h2V8h2v4h2V8h2v4h2V8h2v4h2V8h2v8z"),n.appendChild(o),e.appendChild(n);const i=document.createElement("canvas");i.className="storysplat-measure-canvas";const s=document.createElement("div");s.className="storysplat-measure-list",W.appendChild(e),t.appendChild(i),t.appendChild(s),$.measureButton=e,$.measureCanvas=i,$.measureList=s}if(e.skins&&Array.isArray(e.skins)&&e.skins.length>0){const n=document.createElement("button");n.className="storysplat-skins-btn",n.setAttribute("aria-label","Virtual Staging");const o=document.createElementNS("http://www.w3.org/2000/svg","svg");o.setAttribute("viewBox","0 0 24 24");const i=document.createElementNS("http://www.w3.org/2000/svg","path");i.setAttribute("d","M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"),o.appendChild(i),n.appendChild(o),W.appendChild(n);const s=document.createElement("div");s.className="storysplat-skins-dropdown";const a=document.createElement("div");a.className="storysplat-skins-dropdown-header",a.textContent="Virtual Staging",s.appendChild(a);const r=document.createElement("div");r.className="storysplat-skins-list",s.appendChild(r),e.skins.forEach((t,e)=>{const n=document.createElement("div");n.className="storysplat-skin-item",n.setAttribute("data-skin-id",t.id),n.setAttribute("data-skin-index",String(e));const o=document.createElement("img");o.className="storysplat-skin-item-thumb",o.src=t.thumbnailUrl||t.imageUrl,o.alt="",o.draggable=!1;const i=document.createElement("div");i.className="storysplat-skin-item-prompt",i.textContent=t.name||t.prompt||`Staging ${e+1}`,n.appendChild(o),n.appendChild(i),r.appendChild(n)});const l=document.createElement("div");l.className="storysplat-skin-opacity-row";const c=document.createElement("label");c.textContent="Blend";const d=document.createElement("input");d.type="range",d.min="0",d.max="100",d.value="100",l.appendChild(c),l.appendChild(d),s.appendChild(l);const p=document.createElement("button");p.className="storysplat-skin-exit-btn",p.textContent="Exit overlay",p.style.display="none",s.appendChild(p),t.appendChild(s);const h=document.createElement("div");h.className="storysplat-skin-overlay-wrapper";const u=document.createElement("img");u.className="storysplat-skin-overlay",u.alt="",u.draggable=!1,u.addEventListener("error",()=>{console.error("[StorySplat Viewer] Skin overlay image failed to load:",u.src)}),h.appendChild(u),t.appendChild(h),$.skinsButton=n,$.skinsDropdown=s,$.skinOverlayWrapper=h,$.skinOverlay=u,$.skinOpacitySlider=d,$.skinExitButton=p}const j=document.createElement("button");j.className="storysplat-xr-btn storysplat-vr-btn",j.setAttribute("aria-label",V.vr),j.textContent=V.vr,t.appendChild(j),$.vrButton=j;const X=document.createElement("button");if(X.className="storysplat-xr-btn storysplat-ar-btn",X.setAttribute("aria-label",V.ar),X.textContent=V.ar,t.appendChild(X),$.arButton=X,r){const e=document.createElement("button");e.className="storysplat-help-btn",e.setAttribute("title",V.helpTitle),e.textContent="?",W.insertBefore(e,W.firstChild),$.helpButton=e;const n=document.createElement("div");n.className="storysplat-help-panel";const o=document.createElement("h3");o.textContent=V.helpTitle,n.appendChild(o);const i=document.createElement("div");i.className="storysplat-help-tabs";[{key:"tour",label:V.tour},{key:"explore",label:V.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",V.helpTourDesc,[["Scroll",V.helpTourScroll],["Drag",V.helpTourDrag]],!0));const r=[["LMB",V.helpExploreLMB],["RMB",V.helpExploreRMB],["WASD",V.helpExploreWASD],["Shift",V.helpExploreShift],["Scroll",V.helpExploreScroll],["Dbl-click",V.helpExploreDblClick]];d.includes("walk")&&r.push(["",`— ${V.walk} —`],["Click",V.helpWalkClick],["WASD",V.helpWalkWASD],["Mouse",V.helpWalkMouse],["Shift",V.helpWalkShift],["Space",V.helpWalkSpace]),n.appendChild(a("explore",V.helpExploreDesc,r,!1)),t.appendChild(n),$.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 q=document.createElement("div");q.className="storysplat-hotspot-popup",q.id="hotspotContent",q.innerHTML=`\n <h2 class="storysplat-hotspot-popup-title"></h2>\n <div class="storysplat-hotspot-popup-content"></div>\n <button class="storysplat-hotspot-popup-close">${V.close}</button>\n `,t.appendChild(q),$.hotspotPopup=q;const Y=q.querySelector(".storysplat-hotspot-popup-close");Y?.addEventListener("click",()=>{q.classList.remove("visible","fullscreen")});const Z=document.createElement("div");Z.className="storysplat-portal-popup",Z.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">${V.yes}</button>\n <button class="storysplat-portal-popup-btn storysplat-portal-popup-cancel">${V.cancel}</button>\n </div>\n `,t.appendChild(Z),$.portalPopup=Z;const K=document.createElement("div");K.className="storysplat-joystick-container",K.innerHTML='\n <div class="storysplat-joystick-base"></div>\n <div class="storysplat-joystick-thumb"></div>\n ',t.appendChild(K),$.joystick=K,$.joystickThumb=K.querySelector(".storysplat-joystick-thumb");const Q=document.createElement("div");Q.className="storysplat-look-zone",Q.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(Q),$.lookZone=Q;const J=document.createElement("div");J.className="storysplat-wasd-hint";const tt=document.createElement("div");tt.className="storysplat-orbit-hint-row",tt.style.marginBottom="6px";const et=document.createElement("div");et.className="storysplat-orbit-hint-icon";const nt=document.createElementNS("http://www.w3.org/2000/svg","svg");nt.setAttribute("viewBox","0 0 24 24");const ot=document.createElementNS("http://www.w3.org/2000/svg","path");ot.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"),nt.appendChild(ot),et.appendChild(nt),tt.appendChild(et);const it=document.createElement("div");it.className="storysplat-orbit-hint-label";const st=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),at=e.refocusTapMode||"single";it.textContent=st?"single"===at?"Tap to refocus":"Double-tap to refocus":"single"===at?"Click to refocus":"Double-click to refocus",tt.appendChild(it),J.appendChild(tt);const rt=document.createElement("div");rt.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}`),rt.appendChild(e)}),J.appendChild(rt);const lt=document.createElement("div");lt.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}`),lt.appendChild(e)}),J.appendChild(lt);const ct=document.createElement("div");ct.className="storysplat-wasd-hint-label",ct.textContent="Move",J.appendChild(ct),t.appendChild(J),$.wasdHint=J;const dt=document.createElement("div");dt.className="storysplat-orbit-hint";const pt=document.createElement("div");pt.className="storysplat-orbit-hint-row";const ht=document.createElement("div");ht.className="storysplat-orbit-hint-icon";const ut=document.createElementNS("http://www.w3.org/2000/svg","svg");ut.setAttribute("viewBox","0 0 24 24");const mt=document.createElementNS("http://www.w3.org/2000/svg","path");mt.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"),ut.appendChild(mt),ht.appendChild(ut),pt.appendChild(ht);const gt=document.createElement("div");gt.className="storysplat-orbit-hint-label",gt.textContent="Drag to rotate",pt.appendChild(gt),dt.appendChild(pt);const ft=document.createElement("div");ft.className="storysplat-orbit-hint-row";const yt=document.createElement("div");yt.className="storysplat-orbit-hint-icon";const vt=document.createElementNS("http://www.w3.org/2000/svg","svg");vt.setAttribute("viewBox","0 0 24 24");const bt=document.createElementNS("http://www.w3.org/2000/svg","path");bt.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"),vt.appendChild(bt),yt.appendChild(vt),ft.appendChild(yt);const xt=document.createElement("div");xt.className="storysplat-orbit-hint-label";const wt=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),St=e.refocusTapMode||"single";xt.textContent=wt?"single"===St?"Tap to refocus":"Double-tap to refocus":"single"===St?"Click to refocus":"Double-click to refocus",ft.appendChild(xt),dt.appendChild(ft),t.appendChild(dt),$.orbitHint=dt;const _t=document.createElement("div");_t.className="storysplat-doubletap-hint";const Ct=document.createElement("div");Ct.className="storysplat-doubletap-hint-icon";const Mt=document.createElementNS("http://www.w3.org/2000/svg","svg");Mt.setAttribute("viewBox","0 0 24 24");const Et=document.createElementNS("http://www.w3.org/2000/svg","path");Et.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"),Mt.appendChild(Et),Ct.appendChild(Mt),_t.appendChild(Ct);const Pt=document.createElement("div");Pt.className="storysplat-doubletap-hint-text";const kt=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),Tt=e.refocusTapMode||"single";if(Pt.textContent=kt?"single"===Tt?"Tap to focus on a point":"Double-tap to focus on a point":"single"===Tt?"Click to focus on a point":"Double-click to focus on a point",_t.appendChild(Pt),t.appendChild(_t),$.doubleTapHint=_t,!f){const e=document.createElement("div");e.className="storysplat-watermark";const n=v||(S?`https://storysplat.com?ref=${S}`:"https://storysplat.com"),o=t=>{const e=document.createElement("a");return e.href=t,e.target="_blank",e};if(b){e.classList.add("storysplat-watermark-image"),w&&e.style.setProperty("--storysplat-watermark-img-size",`${w}px`);const t=o(n),i=document.createElement("img");i.src=b,i.alt="Logo",i.className="storysplat-watermark-logo",t.appendChild(i),e.appendChild(t)}else if(y){const t=o(n);t.textContent=y,e.appendChild(t)}else{e.appendChild(document.createTextNode("Created with "));const t=o(n);t.textContent="StorySplat",e.appendChild(t)}t.appendChild(e),$.watermark=e}if(n.debugMode){const e=document.createElement("div");e.className="storysplat-fps-counter",e.textContent="-- FPS",t.appendChild(e),$.fpsCounter=e}{const t=$.sceneMenuContainer,e=10;if(t){t.style.left="10px",t.style.top=`${e}px`;const n=t.querySelector(".storysplat-scene-menu-toggle");if(n){const t=()=>{const t=10+n.offsetWidth+8;W.style.left=`${t}px`;const o=n.offsetHeight||36,i=W.offsetHeight||36;W.style.top=`${e+(o-i)/2}px`};t();let o=null;"undefined"!=typeof ResizeObserver&&(o=new ResizeObserver(t),o.observe(n),o.observe(W)),window.addEventListener("resize",t);const i=$._cleanup;$._cleanup=()=>{i&&i(),o&&o.disconnect(),window.removeEventListener("resize",t)}}}0===W.children.length&&(W.style.display="none")}return $}function A(t,e,n="tour",o,i){const s=i?.container,a=i?.hideProgressText,r=t.prevButton||t.scrollControls?.querySelector(".storysplat-btn-prev"),l=t.nextButton||t.scrollControls?.querySelector(".storysplat-btn-next"),c=t.playButton||t.scrollControls?.querySelector(".storysplat-btn-play");if(r&&r.addEventListener("click",()=>e.prevWaypoint()),l&&l.addEventListener("click",()=>e.nextWaypoint()),c){const t=t=>{c.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>'};c.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.shareButton){const n=t.shareButton,i=async()=>{const t=window.location.href;if(navigator.share)try{return void await navigator.share({url:t})}catch{}try{await navigator.clipboard.writeText(t),k(s||n.parentElement||document.body,x(o,"linkCopied"))}catch{window.prompt("Copy this link:",t)}};n.addEventListener("click",async()=>{if(n.disabled)return;n.disabled=!0;const t=n.getAttribute("aria-label")||x(o,"share");n.setAttribute("aria-label",x(o,"capturingPhoto")),n.setAttribute("title",x(o,"capturingPhoto"));try{e.shareCurrentView?await e.shareCurrentView():await i()}catch(t){console.warn("[StorySplat Viewer] Share failed:",t),k(s||n.parentElement||document.body,x(o,"shareFailed"))}finally{n.disabled=!1,n.setAttribute("aria-label",t),n.setAttribute("title",t)}})}if(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 d=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)},p=e=>{t.exploreControls&&(e?t.exploreControls.classList.add("visible"):t.exploreControls.classList.remove("visible"))},h=s||t.scrollControls?.parentElement;if(d("tour"===n),p("explore"===n||"walk"===n),e.setCameraMode){const t=h?.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"),d("tour"===o),p("explore"===o||"walk"===o))})});const n=!!h?.querySelector('.storysplat-mode-btn[data-mode="walk"]');e.on("modeChange",({mode:e})=>{d("tour"===e),p("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 u=0,m=-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,r;s-u<33||(u=s,t.progressBar&&(t.progressBar.style.width=`${n}%`),t.progressText&&i!==m&&(m=i,t.progressText.innerHTML="",t.progressText.textContent=(a=o,r=i,(a?.percentageFormat||b.percentageFormat).replace("{n}",String(r)))))});let g=-1;e.on("waypointChange",({index:n,waypoint:o})=>{if(n===g)return;if(g=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 L(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}const R="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.8.69";let z=null;function D(){return/iPad|iPhone|iPod/.test(navigator.userAgent)||"MacIntel"===navigator.platform&&navigator.maxTouchPoints>1}function I(t,e){let n=!1;const o=document.createElement("div");o.style.cssText="width: 100%; height: 70vh; max-height: 600px; overflow-y: auto; border-radius: 8px; background: #525659; display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 8px 0;";const i=document.createElement("div");return i.textContent="Loading PDF...",i.style.cssText="color: white; padding: 20px; font-size: 14px;",o.appendChild(i),e.appendChild(o),(z||(z=import(`${R}/pdf.min.mjs`).then(t=>(t.GlobalWorkerOptions.workerSrc=`${R}/pdf.worker.min.mjs`,t)).catch(t=>(z=null,Promise.reject(t)))),z).then(async e=>{try{const s=await e.getDocument(t).promise;if(n)return;i.remove();for(let t=1;t<=s.numPages;t++){if(n)return;const e=await s.getPage(t);if(n)return;const i=Math.min(o.clientWidth-16,850),a=i/e.getViewport({scale:1}).width,r=e.getViewport({scale:a}),l=document.createElement("canvas");l.width=r.width,l.height=r.height,l.style.cssText=`width: ${r.width}px; max-width: 100%; height: auto; display: block;`,o.appendChild(l);const c=l.getContext("2d");c&&await e.render({canvasContext:c,viewport:r}).promise}}catch(e){if(n)return;i.textContent="Failed to load PDF.",console.error("[StorySplat] PDF.js render error:",e);const s=document.createElement("a");s.href=t,s.target="_blank",s.rel="noopener noreferrer",s.textContent="Open PDF in new tab",s.style.cssText="color: #4CAF50; margin-top: 8px; font-size: 14px;",o.appendChild(s)}}).catch(()=>{if(n)return;i.textContent="PDF viewer unavailable.";const e=document.createElement("a");e.href=t,e.target="_blank",e.rel="noopener noreferrer",e.textContent="Open PDF in new tab",e.style.cssText="color: #4CAF50; margin-top: 8px; font-size: 14px;",o.appendChild(e)}),()=>{n=!0}}function F(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 l=n.activationMode||"click",c=n.photoUrl||n.popupVideoUrl||"iframe"===n.contentType&&n.iframeUrl||"model"===n.contentType&&n.modelUrl||"pdf"===n.contentType&&n.pdfUrl;"click"===l&&c&&i.classList.add("fullscreen");const d=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}, ${d})`}else i.style.backgroundColor=n.backgroundColor}else i.style.backgroundColor=`rgba(0, 0, 0, ${d})`;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||x(o,"hotspotDefaultTitle"));let p="";if("iframe"===n.contentType&&n.iframeUrl&&(p+=`<iframe src="${n.iframeUrl}" title="${n.title||"Embedded content"}"></iframe>`),!n.popupVideoUrl||n.contentType&&"video"!==n.contentType||(p+=`<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||(p+=`<img src="${n.photoUrl}" alt="${n.title||"Hotspot image"}" />`),"model"===n.contentType&&n.modelUrl&&(p+='<div class="model-container"><div class="model-loading">Loading 3D model...</div><canvas class="model-canvas"></canvas></div>'),"pdf"===n.contentType&&n.pdfUrl&&(D()?p+='<div class="pdf-pdfjs-container"></div>':p+=`<iframe src="${L(n.pdfUrl)}" style="width: 100%; height: 70vh; max-height: 600px; border: none; border-radius: 8px;" title="${L(n.title||"PDF Document")}"></iframe>`),n.information&&(p+=`<p>${L(n.information)}</p>`),n.externalLinkUrl){const t=n.externalLinkButtonColor||"#007bff";p+=`\n <div onclick="window.open('${L(n.externalLinkUrl)}', '_blank', 'noopener,noreferrer')"\n class="storysplat-hotspot-popup-link" style="background-color: ${t}">\n ${L(n.externalLinkText||x(o,"openExternalLink"))}\n </div>\n `}if(a){if(a.innerHTML=p,"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,b=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),b=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,b*(v/o)),y())}t.preventDefault()},{passive:!1}),e.addEventListener("touchend",()=>{m=!1,v=0});const x=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))}});x.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=()=>{x.disconnect(),a.destroy()}}(e,n.modelUrl,i)}const e=a.querySelector(".pdf-pdfjs-container");if(e&&n.pdfUrl){const t=I(n.pdfUrl,e);i.__pdfCleanup=t}}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 B(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 V(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 $(t,e){t.lookZone&&(e?t.lookZone.classList.add("active"):t.lookZone.classList.remove("active"))}const U={link:"M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z",map:"M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z",calendar:"M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM9 10H7v2h2v-2zm4 0h-2v2h2v-2zm4 0h-2v2h2v-2z",phone:"M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z",cart:"M7 18c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2zM1 2v2h2l3.6 7.59-1.35 2.45c-.16.28-.25.61-.25.96 0 1.1.9 2 2 2h12v-2H7.42c-.14 0-.25-.11-.25-.25l.03-.12.9-1.63h7.45c.75 0 1.41-.41 1.75-1.03l3.58-6.49c.08-.14.12-.31.12-.48 0-.55-.45-1-1-1H5.21l-.94-2H1zm16 16c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2z",globe:"M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.91 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56-1.84-.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95c-.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z",download:"M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"};function O(t,e){t.wasdHint&&(e?t.wasdHint.classList.add("visible"):t.wasdHint.classList.remove("visible"))}function N(t,e){t.orbitHint&&(e?t.orbitHint.classList.add("visible"):t.orbitHint.classList.remove("visible"))}function W(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 H(t,e){t.doubleTapHint&&(t.doubleTapHint.classList.add("fading"),setTimeout(()=>{t.doubleTapHint?.classList.remove("visible","fading")},400))}function G(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")})}function j(t){const e=t.toLowerCase();return e.includes(".mp4")||e.includes(".webm")||e.includes(".mov")||e.includes(".ogg")?"video":e.includes(".gif")?"gif":"image"}const X=new t.Vec3,q=new t.Vec3,Y=new t.Quat,Z=new t.Pose,K=new t.InputFrame({move:[0,0,0],rotate:[0,0,0]}),Q=(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},J=(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=q.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 tt{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._orbitRotationLimits=null,this._orbitRotationLimitAnchor=null,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._prevPose=new t.Pose,this._prevPoseValid=!1,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),o.orbitRotationLimits&&this.setOrbitRotationLimits(o.orbitRotationLimits,!0)}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(X)}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,e={}){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 n=this._mode,o="orbit"===t&&!!e.orbitFocusPoint;if(n===t&&!o)return;switch(this._mode=t,this._flyToActive=!1,this._controller&&this._controller.detach(),this._mode){case"orbit":if(this._controller=this._orbitController,e.orbitFocusPoint){const t=this.camera.getPosition(),n="number"==typeof e.orbitDistance&&Number.isFinite(e.orbitDistance)&&e.orbitDistance>0?e.orbitDistance:t.distance(e.orbitFocusPoint);this._lastFocusPoint.copy(e.orbitFocusPoint),this._pose.look(t,e.orbitFocusPoint),this._startZoomDist=n,this._pose.distance=n}else if("fly"===n)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":this._controller=this._flyController,this.syncFromCamera(void 0,!0);break;case"focus":this._controller=this._focusController}const i=!0===e.snap;if(this._controller.attach(this._pose,i),"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)}!i&&"orbit"===this._mode&&this._applyOrbitRotationLimits()&&(this._controller.attach(this._pose,!1),this._applyPoseToCamera()),this.app.fire("cameracontrols:modechange",this._mode)}setMode(t){this._setMode(t)}setOrbitModeFromFocus(t,e,n=!1){this._setMode("orbit",{orbitFocusPoint:t,orbitDistance:e,snap:n})}getOrbitState(){return{focus:this._pose.getFocus(new t.Vec3),distance:this._pose.distance}}setOrbitRotationLimits(t,e=!1){const n=this._normalizeOrbitRotationLimits(t);this._orbitRotationLimits=n,n?(!e&&this._orbitRotationLimitAnchor||this._captureOrbitRotationLimitAnchor(),"orbit"===this._mode&&this._applyOrbitRotationLimits()&&(this._controller.attach(this._pose,!1),this._applyPoseToCamera())):this._orbitRotationLimitAnchor=null}setOrbitRotationLimitAnchor(e,n){const o=X.sub2(n,e);if(o.lengthSq()<1e-8)return;o.normalize();const i=Math.atan2(-o.y,Math.sqrt(o.x*o.x+o.z*o.z))*t.math.RAD_TO_DEG;let s=Math.atan2(-o.x,-o.z)*t.math.RAD_TO_DEG;for(;s>180;)s-=360;for(;s<-180;)s+=360;this._orbitRotationLimitAnchor={pitch:Math.max(-89,Math.min(89,-i)),yaw:s,focus:n.clone()}}_normalizeOrbitRotationLimits(e){if(!e?.enabled)return null;const n=(e,n)=>{const o="number"==typeof e&&Number.isFinite(e)?e:45;return t.math.clamp(o,0,n)};return{enabled:!0,up:n(e.up,180),down:n(e.down,180),left:n(e.left,180),right:n(e.right,180)}}_captureOrbitRotationLimitAnchor(){const t=this._pose.getFocus(X);this._lastFocusPoint.copy(t),this._orbitRotationLimitAnchor={pitch:this._pose.angles.x,yaw:this._pose.angles.y,focus:t.clone()}}_rebuildOrbitPoseFromFocus(t){const e=q.set(0,0,-1);Y.setFromEulerAngles(this._pose.angles).transformVector(e,e),this._pose.position.copy(t).sub(e.mulScalar(this._pose.distance))}_applyPoseToCamera(){this.camera.setPosition(this._pose.position),this.camera.setEulerAngles(this._pose.angles)}_shortestAngleDeltaDegrees(t,e){let n=t-e;for(;n>180;)n-=360;for(;n<-180;)n+=360;return n}_applyOrbitRotationLimits(){const e=this._orbitRotationLimits;if("orbit"!==this._mode||!e)return!1;this._orbitRotationLimitAnchor||this._captureOrbitRotationLimitAnchor();const n=this._orbitRotationLimitAnchor;if(!n)return!1;let o=!1;const i=n.pitch-e.down,s=n.pitch+e.up,a=t.math.clamp(this._pose.angles.x,i,s);Math.abs(a-this._pose.angles.x)>1e-4&&(this._pose.angles.x=a,o=!0);const r=this._shortestAngleDeltaDegrees(this._pose.angles.y,n.yaw),l=t.math.clamp(r,-e.left,e.right);return Math.abs(l-r)>1e-4&&(this._pose.angles.y=n.yaw+l,this._lastYaw=this._pose.angles.y,o=!0),o&&(this._rebuildOrbitPoseFromFocus(n.focus),this._lastFocusPoint.copy(n.focus)),o}_isOrbitPivotLocked(){return"orbit"===this._mode&&null!==this._orbitRotationLimits}_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){if(this._isOrbitPivotLocked())return;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);if((this._collisionEntities.length>0||null!==this._voxelCollision)&&this.checkCollision(this._flyToEndPos)){const e=X;let o=0,s=i;for(let i=0;i<8;i++){const i=.5*(o+s);e.lerp(n,t,i),this.checkCollision(e)?s=i:o=i}this._flyToEndPos.lerp(n,t,o)}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?X.copy(this.camera.getPosition()).sub(t).normalize().mulScalar(this._startZoomDist).add(t):this.camera.getPosition();this._controller.attach(Z.look(n,t))}reset(t,e){"focus"!==this._mode&&(this._preFocusMode=this._mode),this._setMode("focus"),this._controller.attach(Z.look(e,t))}syncFromCamera(t,e=!1,n=!1){if(this._isOrbitPivotLocked()&&!e)return;const o=this.camera.getPosition().clone();if(this._prevPositionValid=!1,t){this._lastFocusPoint.copy(t);const e=o.distance(t);this._startZoomDist=e,this._pose.distance=e,this._pose.look(o,t);let n=this._pose.angles.y;for(;n>180;)n-=360;for(;n<-180;)n+=360;this._pose.angles.y=n,this._lastYaw=n,this._pose.angles.x=Math.max(-89,Math.min(89,this._pose.angles.x)),this._pose.angles.z=0}else{const t=this.camera.getEulerAngles();this._pose.position.copy(o),this._pose.angles.x=t.x,this._pose.angles.y=t.y,this._pose.angles.z=0;let e=this._pose.angles.y;for(;e>180;)e-=360;for(;e<-180;)e+=360;this._pose.angles.y=e,this._lastYaw=e,this._pose.angles.x=Math.max(-89,Math.min(89,this._pose.angles.x));const n=this.camera.forward.clone();this._lastFocusPoint.copy(o).add(n.mulScalar(10));const i=10;this._startZoomDist=i,this._pose.distance=i}this._controller.attach(this._pose,n)}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,n=!1){this._isOrbitPivotLocked()&&!n||(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._orbitMobileInput.detach(),this._flyMobileInput.attach(t)):(this._flyMobileInput.detach(),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();Q(h,this.gamepadDeadZone.x,this.gamepadDeadZone.y),Q(u,this.gamepadDeadZone.x,this.gamepadDeadZone.y),this._state.axis.add(X.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=m*+(this.enablePan&&!this._isOrbitPivotLocked()),b=+this._flyMobileInput.layout.endsWith("joystick"),x=(this._state.shift?this.moveFastSpeed:this._state.ctrl?this.moveSlowSpeed:this.moveSpeed)*e,w=60*this.zoomSpeed*e,S=w*this.zoomPinchSens,_=60*this.rotateSpeed*e,C=_*this.rotateTouchSens,M=this.rotateSpeed*this.rotateJoystickSens*60*e,{deltas:E}=K,P=X.set(0,0,0),k=this._state.axis.clone().normalize();P.add(k.mulScalar(g*x*this.keyboardSpeedMultiplier));const T=J(this.cameraComponent,s[0],s[1],this._pose.distance);P.add(T.mulScalar(v*y));const A=q.set(0,0,a[0]);P.add(A.mulScalar(m*w)),E.move.append([P.x,P.y,P.z]),P.set(0,0,0);const L=q.set(s[0],s[1],0);P.add(L.mulScalar((1-m*y)*_)),this.invertRotation&&(P.y=-P.y),E.rotate.append([P.x,P.y,P.z]),P.set(0,0,0);const R=q.set(d[0],0,-d[1]);P.add(R.mulScalar(g*x));const z=J(this.cameraComponent,r[0],r[1],this._pose.distance);P.add(z.mulScalar(v*f));const D=q.set(0,0,l[0]);P.add(D.mulScalar(m*f*S)),E.move.append([P.x,P.y,P.z]),P.set(0,0,0);const I=q.set(r[0],r[1],0);P.add(I.mulScalar(m*(1-f)*C));const F=q.set(p[0],p[1],0);P.add(F.mulScalar(g*(b?M:C))),this.invertRotation&&(P.y=-P.y),E.rotate.append([P.x,P.y,P.z]),P.set(0,0,0);const B=q.set(h[0],0,-h[1]);if(P.add(B.mulScalar(g*x)),E.move.append([P.x,P.y,P.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])}P.set(0,0,0);const V=q.set(u[0],u[1],0);if(P.add(V.mulScalar(g*M)),this.invertRotation&&(P.y=-P.y),E.rotate.append([P.x,P.y,P.z]),this.app.xr?.active)return void K.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(K,e));const $=this._collisionEntities.length>0||null!==this._voxelCollision;if("fly"===this._mode&&$){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}else"orbit"===this._mode&&$?(this._prevPoseValid&&this.checkCollision(this._pose.position)?(this._pose.copy(this._prevPose),this._controller.attach(this._pose,!0)):(this._prevPose.copy(this._pose),this._prevPoseValid=!0),this._prevPositionValid=!1):(this._prevPositionValid=!1,this._prevPoseValid=!1);if("orbit"===this._mode){let t=this._pose.angles.y;for(;t>180;)t-=360;for(;t<-180;)t+=360;if(void 0!==this._lastYaw){const e=t-this._lastYaw;e>180?t-=360:e<-180&&(t+=360)}this._pose.angles.y=t,this._lastYaw=t}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._applyOrbitRotationLimits()&&this._controller.attach(this._pose,!0),this._applyPoseToCamera()}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 et(t,e,n){return t+4*e+16*n}function nt(t,e,n){const o=2*e;return n<32?1==(t[o]>>>n&1):1==(t[o+1]>>>n-32&1)}function ot(t,e){let n=t&(1<<e)-1,o=0;for(;n;)o++,n&=n-1;return o}class it{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 it(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 nt(this.leafData,h,et(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+ot(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(!nt(this.leafData,u,et(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),b=Math.max(h-n,0,n-f),x=Math.max(m-o,0,o-y);if(v*v+b*b+x*x<=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,b=1&t?l:m,x=2&t?c:g,w=4&t?d:f,S=Math.max(p-e,0,e-b),_=Math.max(y-n,0,n-x),C=Math.max(v-o,0,o-w);if(S*S+_*_+C*C<=i){const s=u+ot(h,t);if(this._descendSphere(s,e,n,o,i,p,y,v,b,x,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)}findGroundInvertedY(t,e,n){return t<this.gridMinX||t>=this.gridMaxX||e<this.gridMinZ||e>=this.gridMaxZ||0===this.nodes.length?null:this._findCeiling(0,t,e,n,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)&&nt(this.leafData,h,et(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,b=0|f|y;for(const t of[v,b]){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,b=2&t?l:m,x=4&t?c:g,w=h+ot(p,t),S=this._findGround(w,e,n,o,d,f,y,v,b,x);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.findGroundInvertedY(i,s,r);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)&&nt(this.leafData,h,et(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,b=2|f|y;for(const t of[v,b]){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,b=2&t?l:m,x=4&t?c:g,w=h+ot(p,t),S=this._findCeiling(w,e,n,o,d,f,y,v,b,x);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 nt(this.leafData,d,et(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+ot(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 b=-1/0,x=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),C=(u-d)/2+o-Math.abs(r-(d+u)/2);if(v<=0||_<=0||C<=0)continue;const M=o-Math.sqrt(y);M>b&&(b=M,v<=_&&v<=C?(x=Math.sign(s-(t+p)/2)*v,w=0,S=0):_<=C?(x=0,w=Math.sign(a-(c+h)/2)*_,S=0):(x=0,w=0,S=Math.sign(r-(d+u)/2)*C))}if(b<=0)break;if(g>0){const t=x*h+w*u+S*m;t<0&&(x-=t*h,w-=t*u,S-=t*m)}s+=x,a+=w,r+=S,l+=x,c+=w,d+=S,p=!0;const _=Math.sqrt(x*x+w*w+S*S);_>1e-6&&(h=x/_,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,b=4&t?a:u,x=(16777215&c)+ot(d,t);this._collectLeaves(x,m,g,f,y,v,b,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;nt(this.leafData,p,et(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,b=4&t?a:m,x=p+ot(d,t);this._collectSolid(x,c,g,f,y,v,b,r,l)}}}function st(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 at{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._walkTarget=null,this._walkTargetArrivalDist=.5,this.tmpVec=new t.Vec3,this.tmpVec2=new t.Vec3,this.tmpVec3=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)return;const i=o.split("?")[0].split(".").pop()?.toLowerCase()||"";if("glb"!==i&&"gltf"!==i)return void console.warn(`[CharacterController] Unsupported collision mesh format ".${i}" — only .glb and .gltf are supported. Skipping: ${o}. Convert the mesh to GLB, or use voxel collision / primitives instead.`);console.log("[CharacterController] Loading custom collision mesh:",o);const s=new t.Asset(`collision-custom-${n}`,"container",{url:o});try{await new Promise((i,a)=>{s.ready(()=>{try{const o=new t.Entity(`collision-custom-${n}`),a=s.resource;if(a&&a.instantiateRenderEntity){const t=a.instantiateRenderEntity();for(;t.children.length>0;)o.addChild(t.children[0]);t.destroy()}this.configureCollisionEntity(o,e),this.computeAndStoreBounds(o),i()}catch(t){console.error("[CharacterController] Error setting up custom mesh:",t),a(t)}}),s.on("error",t=>{console.error("[CharacterController] Error loading custom mesh:",o,t),a(t)}),this.app.assets.add(s),this.app.assets.load(s)})}catch(t){console.error("[CharacterController] Failed to load custom collision mesh:",o,t);try{this.app.assets.remove(s)}catch{}}}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=st(n.position,[0,0,0]);e.setPosition(o[0],o[1],-o[2]);const i=st(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,b=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(b,n))}else e.setRotation(b);const x=st(n.scaling,[1,1,1]),w=n.meshType;if("plane"===w){const t=.01;e.setLocalScale(3*x[0],x[2]*t,3*x[1])}else"cube"===w||"sphere"===w?e.setLocalScale(3*x[0],3*x[1],3*x[2]):"floor"===w?e.setLocalScale(100*x[0],1*x[1],100*x[2]):e.setLocalScale(x[0],x[1],x[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 e=new t.Vec3(0,0,-1);this.camera.getRotation().transformVector(e,e);const n=Math.atan2(-e.y,Math.sqrt(e.x*e.x+e.z*e.z))*(180/Math.PI),o=Math.atan2(-e.x,-e.z)*(180/Math.PI);this.pitch=-n,this.yaw=o,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.updateSplatTransform(),this.snapToGroundIfClose(),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=>{const e=document.activeElement?.tagName;"INPUT"===e||"TEXTAREA"===e||document.activeElement?.isContentEditable||(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,Math.abs(e)+Math.abs(n)>2&&(this.autoMoveForward&&(this.autoMoveForward=!1),this._walkTarget&&(this._walkTarget=null)),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,n=!0){if(!this.voxelCollision)return!1;const o=this.tmpVec;this.worldToFile(t,o);const i=this.voxelCollision.querySphere(o.x,o.y,o.z,e);if(!i)return!1;const s=this.tmpVec2;s.set(i.x,i.y,i.z);const a=this.tmpVec;return this.fileVecToWorld(s,a),t.x+=a.x,t.z+=a.z,n&&(t.y+=a.y),n||Math.abs(a.x)>1e-6||Math.abs(a.z)>1e-6}checkGround(t){const e=t.y-this.playerHeight,n=e+this.stepHeight;let o=null;const i=t=>{t>n||(null===o||t>o)&&(o=t)};if(this.floorEntity){const e=this.floorEntity.getPosition(),n=this.floorEntity.getLocalScale(),o=n.x/2,s=n.z/2;Math.abs(t.x-e.x)<o&&Math.abs(t.z-e.z)<s&&i(e.y)}for(const e of this.collisionEntities){const n=e.getPosition(),o=e.getLocalScale(),s=e._collisionMeshType;if("floor"===s||"plane"===s||"sphere"===s)continue;let a,r,l,c,d,p;const h=e._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=n.x,d=n.y,p=n.z,a=o.x/2,r=o.y/2,l=o.z/2);const u=d+r;Math.abs(t.x-c)<a+this.collisionRadius&&Math.abs(t.z-p)<l+this.collisionRadius&&i(u)}if(this.voxelCollision){const n=this.tmpVec,o=this.tmpVec2;n.set(t.x,e,t.z),this.worldToFile(n,o);const s=this.worldYPerFileY(),a=this.fileStepHeight(),r=s<0?this.voxelCollision.findGroundInvertedY(o.x,o.z,o.y-a):this.voxelCollision.findGround(o.x,o.z,o.y+a);if(null!==r){const t=this.tmpVec3;t.set(o.x,r,o.z);const e=this._splatEntity?this._splatMatrix.data:null;let n;n=e?e[1]*t.x+e[5]*t.y+e[9]*t.z+e[13]:-r,i(n)}}return o}snapToGroundIfClose(){const t=this.camera.getPosition().clone(),e=this.checkGround(t);if(null===e)return void(this.isGrounded=!1);t.y-this.playerHeight<=e+this.groundCheckDistance&&(t.y=e+this.playerHeight,this.camera.setPosition(t),this.velocity.y=0,this.isGrounded=!0,this.wasGroundedLastFrame=!0,this.lastGroundedTime=this.currentTime)}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);if(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),!this._walkTarget||0===n&&0===o||(this._walkTarget=null),this._walkTarget){const t=this.camera.getPosition(),i=this._walkTarget.x-t.x,s=this._walkTarget.z-t.z;if(Math.sqrt(i*i+s*s)<this._walkTargetArrivalDist)this._walkTarget=null;else{let t=Math.atan2(-i,-s)*(180/Math.PI)-this.yaw;t>180&&(t-=360),t<-180&&(t+=360);const a=Math.abs(t),r=(120+80*Math.min(a/90,1))*e;this.yaw+=Math.max(-r,Math.min(r,t)),o=1,n=0}}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,!1);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),at.MAX_LANDING_DIP)),this.landingDipAmount*=1-Math.min(1,e*at.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?at.SPRINT_BOB_FREQ:at.WALK_BOB_FREQ,n=i?at.SPRINT_BOB_AMP_Y:at.WALK_BOB_AMP_Y,o=i?at.SPRINT_BOB_AMP_X:at.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,b=c.z+this.bobOffsetX*this.right.z;this.camera.setPosition(y,v,b),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 it.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}worldYPerFileY(){return this._splatEntity&&this._splatMatrix.data[5]||-1}fileStepHeight(){const t=Math.abs(this.worldYPerFileY());return t>1e-6?this.stepHeight/t:this.stepHeight}get voxelCollisionInstance(){return this.voxelCollision}get collisionMeshEntities(){return this.collisionEntities}addCollisionEntity(t){this.collisionEntities.includes(t)||(this.collisionEntities.push(t),t._collisionMeshType||(t._collisionMeshType="cube"),"floor"===t._collisionMeshType&&(this.floorEntity=t))}removeCollisionEntity(t){const e=this.collisionEntities.indexOf(t);-1!==e&&this.collisionEntities.splice(e,1),this.floorEntity===t&&(this.floorEntity=null)}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}walkTo(t,e=.5){this._walkTarget=t.clone(),this._walkTargetArrivalDist=e}cancelWalkTo(){this._walkTarget=null}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"),d=this.app.scene.layers.getLayerByName("VoxelDebug");c.addComponent("render",{type:"asset",meshInstances:[l],castShadows:!1,receiveShadows:!1,...d?{layers:[d.id]}:{}}),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}findGroundAt(t){return this.checkGround(t)}}at.WALK_BOB_FREQ=10,at.WALK_BOB_AMP_Y=.025,at.WALK_BOB_AMP_X=.012,at.SPRINT_BOB_FREQ=14,at.SPRINT_BOB_AMP_Y=.04,at.SPRINT_BOB_AMP_X=.02,at.LANDING_DIP_DECAY=8,at.MAX_LANDING_DIP=.15;const rt="\nuniform vec4 uMirrorClipPlane;\n\nvoid mirrorClipSplat(vec3 center, inout vec3 scale) {\n vec3 n = uMirrorClipPlane.xyz;\n if (dot(n, n) > 0.25) {\n float dist = dot(center, n) - uMirrorClipPlane.w;\n if (dist < 0.0) {\n scale = vec3(0.0);\n }\n }\n}\n",lt="\nuniform uMirrorClipPlane: vec4f;\n\nfn mirrorClipSplat(center: vec3f, scale: ptr<function, vec3f>) {\n let n = uniform.uMirrorClipPlane.xyz;\n if (dot(n, n) > 0.25) {\n let dist = dot(center, n) - uniform.uMirrorClipPlane.w;\n if (dist < 0.0) {\n *scale = vec3f(0.0);\n }\n }\n}\n",ct=rt+"\nvoid modifySplatCenter(inout vec3 center) {}\n\nvoid modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) {\n mirrorClipSplat(modifiedCenter, scale);\n}\n\nvoid modifySplatColor(vec3 center, inout vec4 color) {}\n",dt=lt+"\nfn modifySplatCenter(center: ptr<function, vec3f>) {}\n\nfn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr<function, vec4f>, scale: ptr<function, vec3f>) {\n mirrorClipSplat(modifiedCenter, scale);\n}\n\nfn modifySplatColor(center: vec3f, color: ptr<function, vec4f>) {}\n",pt=rt+"\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];\nuniform mat4 uRelightModelMatrix;\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 // Transform center from model-space to world-space via the entity's world matrix.\n // Unified mode: identity (centers already world-space in work buffer).\n // Non-unified mode: entity world transform (needed when swap splats have overrides).\n vec3 worldPos = (uRelightModelMatrix * vec4(center, 1.0)).xyz;\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",ht=lt+"\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>;\nuniform uRelightModelMatrix: mat4x4f;\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 // Transform center from model-space to world-space via the entity's world matrix.\n // Unified mode: identity (centers already world-space in work buffer).\n // Non-unified mode: entity world transform (needed when swap splats have overrides).\n let worldPos = (uniform.uRelightModelMatrix * vec4f(center, 1.0)).xyz;\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",ut=pt+"\nvoid modifySplatCenter(inout vec3 center) {}\n\nvoid modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) {\n mirrorClipSplat(modifiedCenter, scale);\n}\n\nvoid modifySplatColor(vec3 center, inout vec4 color) {\n applyRelighting(center, color);\n}\n",mt=ht+"\nfn modifySplatCenter(center: ptr<function, vec3f>) {}\n\nfn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr<function, vec4f>, scale: ptr<function, vec3f>) {\n mirrorClipSplat(modifiedCenter, scale);\n}\n\nfn modifySplatColor(center: vec3f, color: ptr<function, vec4f>) {\n applyRelighting(center, color);\n}\n";let gt=null;const ft=16;function yt(){if(gt)return gt;const e=t.createScript("gsplatRelighting");return Object.assign(e.prototype,{material:null,_effectInitialized:!1,_shadersNeedApplication:!1,_shaderManagedExternally:!1,_isUnified:!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._isUnified=!!this.entity.gsplat?.unified,this._ambientArray=[1,1,1],this._posArray=new Float32Array(48),this._colorArray=new Float32Array(48),this._rangeArray=new Float32Array(ft),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(e){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._isUnified?this._setUniform("uRelightModelMatrix",t.Mat4.IDENTITY.data):this._setUniform("uRelightModelMatrix",this.entity.getWorldTransform().data),this.material.update())):this.entity.gsplat&&(this._effectInitialized=!0,this._isUnified=!!this.entity.gsplat?.unified,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,ft);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<ft;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:()=>ut,getShaderWGSL:()=>mt,_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()}}),gt=e,e}const vt={get class(){return yt()}};let bt=null;function xt(){if(bt)return bt;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:20,acceleration:0,delay:.5,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);this._setUniform("uWarpedRadius",t/Math.sqrt(1+t/5))},_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=t/Math.sqrt(1+t/5),n=this.delay;if(0===this.acceleration)return n+e/this.speed;const o=this.speed*this.speed+2*this.acceleration*e;if(o<0)return 1/0;return n+(-this.speed+Math.sqrt(o))/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;\nuniform float uWarpedRadius; // warped max scene radius for reverse-wave\n\n// Shared globals (initialized once per vertex)\nfloat g_dist;\nfloat g_dotWavePos;\nfloat g_liftTime;\nfloat g_liftWavePos;\nfloat g_reverseReveal; // 0 = not reverse-revealed, 1 = fully revealed by reverse wave\n\nvoid initShared(vec3 center) {\n float rawDist = length(center - uCenter);\n // Distance warp: compress far distances so distant splats reveal sooner.\n // See gsplat-reveal-bloom.ts for the derivation. K=5 matches bloom.\n const float K = 5.0;\n g_dist = rawDist / sqrt(1.0 + rawDist / K);\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 // Reverse wave: outer 10% of splats reveal from outside-in simultaneously.\n // distFromEdge is small for edge splats (reveal first), grows inward.\n // revWavePos grows over time → reveals progressively inward.\n g_reverseReveal = 0.0;\n float outerThreshold = uWarpedRadius * 0.9;\n if (g_dist > outerThreshold && uWarpedRadius > 0.0) {\n float distFromEdge = uWarpedRadius - g_dist;\n float revWavePos = uSpeed * uTime + 0.5 * uAcceleration * uTime * uTime;\n g_reverseReveal = 1.0 - smoothstep(revWavePos - uBandWidth, revWavePos + uBandWidth, distFromEdge);\n }\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 mirrorClipSplat(modifiedCenter, scale);\n\n // Reverse wave: if fully revealed from outside-in, show at full size\n if (g_reverseReveal >= 1.0) return;\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: use reverse reveal if active, otherwise invisible\n if (g_reverseReveal > 0.0) {\n scale *= g_reverseReveal;\n } else {\n scale = vec3(0.0);\n }\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;\nuniform uWarpedRadius: 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;\nvar<private> g_reverseReveal: f32;\n\nfn initShared(center: vec3f) {\n let rawDist = length(center - uniform.uCenter);\n // Distance warp: compress far distances so distant splats reveal sooner.\n // See gsplat-reveal-bloom.ts for the derivation. K=5 matches bloom.\n let K: f32 = 5.0;\n g_dist = rawDist / sqrt(1.0 + rawDist / K);\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 // Reverse wave: outer 10% of splats reveal from outside-in simultaneously.\n // distFromEdge is small for edge splats (reveal first), grows inward.\n // revWavePos grows over time → reveals progressively inward.\n g_reverseReveal = 0.0;\n let outerThreshold = uniform.uWarpedRadius * 0.9;\n if (g_dist > outerThreshold && uniform.uWarpedRadius > 0.0) {\n let distFromEdge = uniform.uWarpedRadius - g_dist;\n let revWavePos = uniform.uSpeed * uniform.uTime + 0.5 * uniform.uAcceleration * uniform.uTime * uniform.uTime;\n g_reverseReveal = 1.0 - smoothstep(revWavePos - uniform.uBandWidth, revWavePos + uniform.uBandWidth, distFromEdge);\n }\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 mirrorClipSplat(modifiedCenter, scale);\n\n // Reverse wave: if fully revealed from outside-in, show at full size\n if (g_reverseReveal >= 1.0) {\n return;\n }\n\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: use reverse reveal if active, otherwise invisible\n if (g_reverseReveal > 0.0) {\n *scale *= g_reverseReveal;\n } else {\n *scale = vec3f(0.0);\n }\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?ht:pt)+"\n"+o.replace("// EFFECT_HOOK","applyRelighting(center, color);")}else{o=("wgsl"===n?lt:rt)+o}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.entity.script&&this.entity.script.gsplatCompareClip;if(e&&e.enabled)return e.material=null,e._shadersNeedApplication=!0,void(this.material=null);const n=this.app.graphicsDevice,o=n?.isWebGPU?"wgsl":"glsl";this.material.getShaderChunks(o).delete("gsplatModifyVS"),this.material.update(),this.material=null},_setUniform(t,e){this.material?.setParameter(t,e)},destroy(){this._removeShaders()}}),bt=e,e}let wt=null;let St=null;let _t=null;function Ct(){if(_t)return _t;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:100,acceleration:32,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,e=t/Math.sqrt(1+t/5);if(0===this.acceleration)return e/this.speed+.5;const n=this.speed*this.speed+2*this.acceleration*e;if(n<0)return 1/0;return(-this.speed+Math.sqrt(n))/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));const t=this._autoEndRadius>0?this._autoEndRadius:this.endRadius;this._setUniform("uWarpedRadius",t/Math.sqrt(1+t/5)),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\nuniform float uWarpedRadius; // warped max scene radius for reverse-wave\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 float rawDist = length(center - uCenter);\n\n // Distance warp: compress far distances so distant splats reveal sooner,\n // while preserving near-field pacing. Formula saturates smoothly as rawDist grows:\n // warped = raw / sqrt(1 + raw/K)\n // With K=5: near rawDist ~10 → ~4.5, rawDist ~500 → ~49 (~10x compression).\n // This makes the reveal wave reach distant horizon splats ~10x sooner.\n const float K = 5.0;\n g_dist = rawDist / sqrt(1.0 + rawDist / K);\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 // --- Forward wave (inside-out) ---\n float forwardBloom;\n if (effectiveDist > wavePos + uBandWidth * 0.5) {\n forwardBloom = 0.0;\n } else if (effectiveDist > wavePos - uBandWidth * 0.5) {\n forwardBloom = smoothstep(wavePos + uBandWidth * 0.5, wavePos - uBandWidth * 0.5, effectiveDist);\n } else {\n float distBehind = (wavePos - uBandWidth * 0.5) - effectiveDist;\n float settleTime = distBehind / max(uWaveSpeed + uAcceleration * uTime, 1.0);\n float overshootDecay = exp(-settleTime * 6.0);\n forwardBloom = 1.0 + (uOvershoot - 1.0) * overshootDecay;\n }\n\n // --- Reverse wave (outside-in) for outer 10% of splats ---\n // Starts at time 0 from the scene edge, sweeping inward simultaneously.\n // Only affects splats in the outer 10% of warped distance.\n float reverseBloom = 0.0;\n float outerThreshold = uWarpedRadius * 0.9;\n if (g_dist > outerThreshold && uWarpedRadius > 0.0) {\n float distFromEdge = uWarpedRadius - effectiveDist;\n // Reverse wave also uses the same speed/accel\n float revWavePos = uWaveSpeed * uTime + 0.5 * uAcceleration * uTime * uTime;\n if (distFromEdge < revWavePos + uBandWidth * 0.5) {\n if (distFromEdge > revWavePos - uBandWidth * 0.5) {\n reverseBloom = smoothstep(revWavePos + uBandWidth * 0.5, revWavePos - uBandWidth * 0.5, distFromEdge);\n } else {\n float distBehind = (revWavePos - uBandWidth * 0.5) - distFromEdge;\n float settleTime = distBehind / max(uWaveSpeed + uAcceleration * uTime, 1.0);\n float overshootDecay = exp(-settleTime * 6.0);\n reverseBloom = 1.0 + (uOvershoot - 1.0) * overshootDecay;\n }\n }\n }\n\n // Take whichever wave reveals first\n g_bloom = max(forwardBloom, reverseBloom);\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 mirrorClipSplat(modifiedCenter, 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;\nuniform uWarpedRadius: f32;\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 let rawDist = length(center - uniform.uCenter);\n\n // Distance warp: compress far distances so distant splats reveal sooner,\n // while preserving near-field pacing. See GLSL version for the derivation.\n let K: f32 = 5.0;\n g_dist = rawDist / sqrt(1.0 + rawDist / K);\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 // --- Forward wave (inside-out) ---\n var forwardBloom: f32;\n if (effectiveDist > wavePos + uniform.uBandWidth * 0.5) {\n forwardBloom = 0.0;\n } else if (effectiveDist > wavePos - uniform.uBandWidth * 0.5) {\n forwardBloom = 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 forwardBloom = 1.0 + (uniform.uOvershoot - 1.0) * overshootDecay;\n }\n\n // --- Reverse wave (outside-in) for outer 10% of splats ---\n var reverseBloom: f32 = 0.0;\n let outerThreshold = uniform.uWarpedRadius * 0.9;\n if (g_dist > outerThreshold && uniform.uWarpedRadius > 0.0) {\n let distFromEdge = uniform.uWarpedRadius - effectiveDist;\n let revWavePos = uniform.uWaveSpeed * uniform.uTime + 0.5 * uniform.uAcceleration * uniform.uTime * uniform.uTime;\n if (distFromEdge < revWavePos + uniform.uBandWidth * 0.5) {\n if (distFromEdge > revWavePos - uniform.uBandWidth * 0.5) {\n reverseBloom = smoothstep(revWavePos + uniform.uBandWidth * 0.5, revWavePos - uniform.uBandWidth * 0.5, distFromEdge);\n } else {\n let distBehind = (revWavePos - uniform.uBandWidth * 0.5) - distFromEdge;\n let settleTime = distBehind / max(uniform.uWaveSpeed + uniform.uAcceleration * uniform.uTime, 1.0);\n let overshootDecay = exp(-settleTime * 6.0);\n reverseBloom = 1.0 + (uniform.uOvershoot - 1.0) * overshootDecay;\n }\n }\n }\n\n g_bloom = max(forwardBloom, reverseBloom);\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 mirrorClipSplat(modifiedCenter, scale);\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?ht:pt)+"\n"+o.replace("// EFFECT_HOOK","applyRelighting(center, color);")}else{o=("wgsl"===n?lt:rt)+o}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.entity.script&&this.entity.script.gsplatCompareClip;if(e&&e.enabled)return e.material=null,e._shadersNeedApplication=!0,void(this.material=null);const n=this.app.graphicsDevice,o=n?.isWebGPU?"wgsl":"glsl";this.material.getShaderChunks(o).delete("gsplatModifyVS"),this.material.update(),this.material=null},_setUniform(t,e){this.material?.setParameter(t,e)},destroy(){this._removeShaders()}}),_t=e,e}let Mt=null;const Et=1e6;class Pt{constructor(e,n={}){this.extents=new t.Vec3(12,12,12),this.density=2,this.speed=1,this.drift=.15,this.opacity=.8,this.color=[1,1,1],this.particleMinSize=.006,this.particleMaxSize=.012,this.elongate=1,this._container=null,this._format=null,this._time=0,this._baseCellArray=[0,0,0],this._camPosArray=[0,0,0],this._gridHalfArray=[0,0,0],this._particleSizeArray=[0,0],this._effectiveHalfCells=[0,0,0],this._scalarsDirty=!0,this.app=e,this.entity=new t.Entity("GsplatWeather"),this.followEntity=n.followEntity??null,this.applyOptions(n),this._buildFormat(),this._buildContainer()}applyOptions(t){t.extents&&this.extents.set(t.extents.x,t.extents.y,t.extents.z),void 0!==t.density&&(this.density=t.density),void 0!==t.speed&&(this.speed=t.speed),void 0!==t.drift&&(this.drift=t.drift),void 0!==t.opacity&&(this.opacity=t.opacity),t.color&&(this.color=[t.color[0],t.color[1],t.color[2]]),void 0!==t.particleMinSize&&(this.particleMinSize=t.particleMinSize),void 0!==t.particleMaxSize&&(this.particleMaxSize=t.particleMaxSize),void 0!==t.elongate&&(this.elongate=t.elongate),void 0!==t.followEntity&&(this.followEntity=t.followEntity),this._scalarsDirty=!0}markScalarsDirty(){this._scalarsDirty=!0}get cellSize(){return 1/Math.max(this.density,.1)}_halfCells(t){return Math.min(128,Math.max(1,Math.floor(t*Math.max(this.density,.1))))}get numParticles(){return 2*this._halfCells(this.extents.x)*this._halfCells(this.extents.y)*2*this._halfCells(this.extents.z)*2}_buildFormat(){const e=t;this._format=new e.GSplatFormat(this.app.graphicsDevice,[{name:"data",format:e.PIXELFORMAT_RGBA8}],{readGLSL:"\n uniform float uTime;\n uniform float uCellSize;\n uniform vec3 uGridHalf;\n uniform vec3 uBaseCell;\n uniform vec3 uCameraPos;\n uniform float uSpeed;\n uniform float uDrift;\n uniform float uOpacity;\n uniform vec3 uColor;\n uniform vec2 uParticleSize;\n uniform float uElongate;\n\n vec3 weatherLocalPos;\n vec3 weatherWC;\n vec4 sd;\n\n float weatherHash(vec3 p) {\n return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453);\n }\n\n vec3 getCenter() {\n sd = loadData();\n\n float dx = floor(sd.r * 255.0 + 0.5) - uGridHalf.x;\n float dz = floor(sd.b * 255.0 + 0.5) - uGridHalf.z;\n float dy = floor(sd.g * 255.0 + 0.5);\n\n vec3 worldCell = uBaseCell + vec3(dx, dy - uGridHalf.y, dz);\n weatherWC = worldCell;\n\n vec3 col = vec3(worldCell.x, 0.0, worldCell.z);\n\n float fx = weatherHash(col + vec3(1.0, 0.0, 0.0));\n float fz = weatherHash(col + vec3(0.0, 0.0, 3.0));\n\n fx += (weatherHash(worldCell + vec3(4.0, 0.0, 0.0)) - 0.5) * 0.4;\n fz += (weatherHash(worldCell + vec3(0.0, 0.0, 5.0)) - 0.5) * 0.4;\n\n float spd = mix(0.3, 0.8, weatherHash(col)) * uSpeed;\n\n float gridYf = uGridHalf.y * 2.0;\n float colY = (dy + weatherHash(worldCell + vec3(0.0, 2.0, 0.0))) / gridYf;\n colY = fract(colY - uTime * spd / gridYf);\n\n float phase = weatherHash(col + vec3(7.0, 0.0, 0.0)) * 6.28318;\n fx += sin(uTime * 0.8 + phase) * uDrift;\n fz += cos(uTime * 0.6 + phase) * uDrift;\n\n weatherLocalPos = vec3(\n (dx + fx) * uCellSize,\n (-uGridHalf.y + colY * gridYf) * uCellSize,\n (dz + fz) * uCellSize\n );\n return weatherLocalPos;\n }\n\n vec4 getColor() {\n vec3 camOffset = uCameraPos - uBaseCell * uCellSize;\n float dist = length(weatherLocalPos - camOffset);\n float maxDist = min(uGridHalf.x, uGridHalf.z) * uCellSize * 0.9;\n float fade = 1.0 - smoothstep(maxDist * 0.6, maxDist, dist);\n float alpha = mix(0.5, 0.9, weatherHash(weatherWC + 20.0)) * fade * uOpacity;\n return vec4(uColor, alpha);\n }\n\n vec3 getScale() {\n float size = mix(uParticleSize.x, uParticleSize.y, weatherHash(weatherWC + 10.0));\n return vec3(size, size * uElongate, size);\n }\n\n vec4 getRotation() { return vec4(0.0, 0.0, 0.0, 1.0); }\n",readWGSL:"\n uniform uTime: f32;\n uniform uCellSize: f32;\n uniform uGridHalf: vec3f;\n uniform uBaseCell: vec3f;\n uniform uCameraPos: vec3f;\n uniform uSpeed: f32;\n uniform uDrift: f32;\n uniform uOpacity: f32;\n uniform uColor: vec3f;\n uniform uParticleSize: vec2f;\n uniform uElongate: f32;\n\n var<private> weatherLocalPos: vec3f;\n var<private> weatherWC: vec3f;\n var<private> sd: vec4f;\n\n fn weatherHash(p: vec3f) -> f32 {\n return fract(sin(dot(p, vec3f(127.1, 311.7, 74.7))) * 43758.5453);\n }\n\n fn getCenter() -> vec3f {\n sd = loadData();\n\n let dx = floor(sd.r * 255.0 + 0.5) - uniform.uGridHalf.x;\n let dz = floor(sd.b * 255.0 + 0.5) - uniform.uGridHalf.z;\n let dy = floor(sd.g * 255.0 + 0.5);\n\n let worldCell = uniform.uBaseCell + vec3f(dx, dy - uniform.uGridHalf.y, dz);\n weatherWC = worldCell;\n\n let col = vec3f(worldCell.x, 0.0, worldCell.z);\n\n var fx = weatherHash(col + vec3f(1.0, 0.0, 0.0));\n var fz = weatherHash(col + vec3f(0.0, 0.0, 3.0));\n\n fx = fx + (weatherHash(worldCell + vec3f(4.0, 0.0, 0.0)) - 0.5) * 0.4;\n fz = fz + (weatherHash(worldCell + vec3f(0.0, 0.0, 5.0)) - 0.5) * 0.4;\n\n let spd = mix(0.3, 0.8, weatherHash(col)) * uniform.uSpeed;\n\n let gridYf = uniform.uGridHalf.y * 2.0;\n var colY = (dy + weatherHash(worldCell + vec3f(0.0, 2.0, 0.0))) / gridYf;\n colY = fract(colY - uniform.uTime * spd / gridYf);\n\n let phase = weatherHash(col + vec3f(7.0, 0.0, 0.0)) * 6.28318;\n fx = fx + sin(uniform.uTime * 0.8 + phase) * uniform.uDrift;\n fz = fz + cos(uniform.uTime * 0.6 + phase) * uniform.uDrift;\n\n weatherLocalPos = vec3f(\n (dx + fx) * uniform.uCellSize,\n (-uniform.uGridHalf.y + colY * gridYf) * uniform.uCellSize,\n (dz + fz) * uniform.uCellSize\n );\n return weatherLocalPos;\n }\n\n fn getColor() -> vec4f {\n let camOffset = uniform.uCameraPos - uniform.uBaseCell * uniform.uCellSize;\n let dist = length(weatherLocalPos - camOffset);\n let maxDist = min(uniform.uGridHalf.x, uniform.uGridHalf.z) * uniform.uCellSize * 0.9;\n let fade = 1.0 - smoothstep(maxDist * 0.6, maxDist, dist);\n let alpha = mix(0.5, 0.9, weatherHash(weatherWC + 20.0)) * fade * uniform.uOpacity;\n return vec4f(uniform.uColor, alpha);\n }\n\n fn getScale() -> vec3f {\n let size = mix(uniform.uParticleSize.x, uniform.uParticleSize.y, weatherHash(weatherWC + 10.0));\n return vec3f(size, size * uniform.uElongate, size);\n }\n\n fn getRotation() -> vec4f { return vec4f(0.0, 0.0, 0.0, 1.0); }\n"})}rebuild(){this._buildContainer()}_buildContainer(){const e=t;this._container&&(this._container.destroy(),this._container=null);const n=this.app.graphicsDevice;let o=this._halfCells(this.extents.x),i=this._halfCells(this.extents.y),s=this._halfCells(this.extents.z),a=2*o*(2*i)*(2*s);if(a>Et){const t=Math.cbrt(Et/a);o=Math.max(1,Math.floor(o*t)),i=Math.max(1,Math.floor(i*t)),s=Math.max(1,Math.floor(s*t));const e=2*o*(2*i)*(2*s);console.warn(`[GsplatWeather] Clamped particle count from ${a} to ${e} (cap 1000000)`),a=e}const r=2*o,l=2*i,c=2*s,d=this.cellSize;this._effectiveHalfCells[0]=o,this._effectiveHalfCells[1]=i,this._effectiveHalfCells[2]=s,this._container=new e.GSplatContainer(n,a,this._format);const p=this._container.getTexture("data").lock(),h=this._container.centers;let u=0;for(let t=0;t<r;t++)for(let e=0;e<l;e++)for(let n=0;n<c;n++)p[4*u+0]=t,p[4*u+1]=e,p[4*u+2]=n,p[4*u+3]=256*Math.random()|0,h[3*u+0]=(t-o+.5)*d,h[3*u+1]=(e-i+.5)*d,h[3*u+2]=(n-s+.5)*d,u++;this._container.getTexture("data").unlock();const m=o*d,g=i*d,f=s*d;this._container.aabb=new t.BoundingBox(t.Vec3.ZERO,new t.Vec3(m,g,f)),this._container.update(a,!0),this.entity.gsplat?this.entity.gsplat.resource=this._container:this.entity.addComponent("gsplat",{resource:this._container,unified:!0})}update(t){if(!this._container||!this.entity.gsplat)return;if(!this.entity.enabled)return;if(this._time+=t,this.opacity<=0)return;const e=this.cellSize;let n=0,o=0,i=0;if(this.followEntity){const t=this.followEntity.getPosition();n=t.x,o=t.y,i=t.z}else{const t=this.entity.getPosition();n=t.x,o=t.y,i=t.z}const s=Math.floor(n/e),a=Math.floor(o/e),r=Math.floor(i/e);this.entity.setPosition(s*e,a*e,r*e),this._baseCellArray[0]=s,this._baseCellArray[1]=a,this._baseCellArray[2]=r,this._camPosArray[0]=n,this._camPosArray[1]=o,this._camPosArray[2]=i,this._gridHalfArray[0]=this._effectiveHalfCells[0],this._gridHalfArray[1]=this._effectiveHalfCells[1],this._gridHalfArray[2]=this._effectiveHalfCells[2],this._scalarsDirty&&(this._particleSizeArray[0]=this.particleMinSize,this._particleSizeArray[1]=this.particleMaxSize);const l=this.entity.gsplat;l.setParameter("uTime",this._time),l.setParameter("uCellSize",e),l.setParameter("uGridHalf",this._gridHalfArray),l.setParameter("uBaseCell",this._baseCellArray),l.setParameter("uCameraPos",this._camPosArray),this._scalarsDirty&&(l.setParameter("uSpeed",this.speed),l.setParameter("uDrift",this.drift),l.setParameter("uOpacity",this.opacity),l.setParameter("uColor",this.color),l.setParameter("uParticleSize",this._particleSizeArray),l.setParameter("uElongate",this.elongate),this._scalarsDirty=!1);const c=l.material;c&&"function"==typeof c.update&&c.update()}destroy(){this._container&&(this._container.destroy(),this._container=null),this.entity&&this.entity.destroy()}}const kt={snow:{density:2,speed:1,drift:.15,opacity:.85,color:[1,1,1],particleMinSize:.006,particleMaxSize:.014,elongate:1},rain:{density:3,speed:18,drift:.02,opacity:.55,color:[.8,.85,.95],particleMinSize:.004,particleMaxSize:.008,elongate:12}},Tt={fast:{speed:40,acceleration:8,delay:.1,oscillationIntensity:.1,dotTint:{r:0,g:1,b:1},waveTint:{r:1,g:.5,b:0},endRadius:500},medium:{speed:20,acceleration:4,delay:.5,oscillationIntensity:.2,dotTint:{r:0,g:1,b:1},waveTint:{r:1,g:.5,b:0},endRadius:500},slow:{speed:12,acceleration:0,delay:.75,oscillationIntensity:.25,dotTint:{r:0,g:1,b:1},waveTint:{r:1,g:.5,b:0},endRadius:500}},At={fast:{speed:32,acceleration:100,delay:0,oscillationIntensity:.15,dotTint:{r:1,g:.6,b:.15},waveTint:{r:.2,g:.5,b:1},endRadius:500},medium:{speed:20,acceleration:72,delay:0,oscillationIntensity:.2,dotTint:{r:1,g:.6,b:.15},waveTint:{r:.2,g:.5,b:1},endRadius:500},slow:{speed:12,acceleration:40,delay:0,oscillationIntensity:.25,dotTint:{r:1,g:.6,b:.15},waveTint:{r:.2,g:.5,b:1},endRadius:500}};function Lt(t,e="bloom"){if("none"===t)return;return("bloom"===e?At:Tt)[t]}class Rt{constructor(t){this.isDragging=!1,this.labelFadeTimeout=null,this.snapPositions=[0,.5,1],this.lastSnapIndex=1,this.destroyed=!1,this._resizeObserver=null,this.container=t.container,this.config=t.config,this.onPositionChange=t.onPositionChange,this.orientation=t.config.orientation||"vertical",this.position=(t.config.initialPosition??50)/100;const e=t.theme||{};this.overlay=document.createElement("div"),this.overlay.className="ss-compare-overlay",Object.assign(this.overlay.style,{position:"absolute",inset:"0",pointerEvents:"none",zIndex:"10",overflow:"hidden"});const n=e.compareLineColor||t.config.lineColor||"#ffffff",o=t.config.lineOpacity??.6;t.config.handleColor;const i={position:"absolute",pointerEvents:"none",boxShadow:"0 0 4px rgba(0,0,0,0.4)"};this.lineA=document.createElement("div"),this.lineA.className="ss-compare-line",Object.assign(this.lineA.style,i),this.lineB=document.createElement("div"),this.lineB.className="ss-compare-line",Object.assign(this.lineB.style,i),this._applyLineColor(n,o),this.handle=document.createElement("div"),this.handle.className="ss-compare-handle",this.handle.tabIndex=0,this.handle.setAttribute("role","slider"),this.handle.setAttribute("aria-label","Compare slider"),this.handle.setAttribute("aria-valuemin","0"),this.handle.setAttribute("aria-valuemax","100"),Object.assign(this.handle.style,{position:"absolute",cursor:"vertical"===this.orientation?"ew-resize":"ns-resize",pointerEvents:"auto",display:"flex",alignItems:"center",justifyContent:"center",transform:"translate(-50%, -50%)",transition:"box-shadow 0.15s ease",outline:"none",touchAction:"none",boxSizing:"border-box"}),this.updateHandleStyle(t.config.handleStyle||"arrows");const s=e.compareLabelBg||"rgba(0,0,0,0.5)",a=e.compareLabelColor||"#ffffff",r=e.compareLabelFontSize||"13px";this.beforeLabel=this._createLabel(t.config.beforeLabel||"Before",s,a,r),this.afterLabel=this._createLabel(t.config.afterLabel||"After",s,a,r),this.overlay.appendChild(this.lineA),this.overlay.appendChild(this.lineB),this.overlay.appendChild(this.handle),this.overlay.appendChild(this.beforeLabel),this.overlay.appendChild(this.afterLabel),!1===t.config.showLabels&&(this.beforeLabel.style.display="none",this.afterLabel.style.display="none"),this.container.appendChild(this.overlay),this._updateLayout(),this._onPointerDown=this._handlePointerDown.bind(this),this._onPointerMove=this._handlePointerMove.bind(this),this._onPointerUp=this._handlePointerUp.bind(this),this._onKeyDown=this._handleKeyDown.bind(this),this._onDblClick=this._handleDblClick.bind(this),this.handle.addEventListener("pointerdown",this._onPointerDown),window.addEventListener("pointermove",this._onPointerMove),window.addEventListener("pointerup",this._onPointerUp),!1!==t.config.enableKeyboard&&this.handle.addEventListener("keydown",this._onKeyDown),!1!==t.config.enableSnap&&this.handle.addEventListener("dblclick",this._onDblClick),this.handle.addEventListener("focus",()=>{this.handle.style.boxShadow="0 0 0 3px rgba(59, 130, 246, 0.5)"}),this.handle.addEventListener("blur",()=>{this.handle.style.boxShadow=""}),this._resizeObserver=new ResizeObserver(()=>{this.destroyed||this._updateLayout()}),this._resizeObserver.observe(this.container),this._resetLabelFade()}_createLabel(t,e,n,o){const i=document.createElement("div");return i.className="ss-compare-label",i.textContent=t,Object.assign(i.style,{position:"absolute",top:"20px",pointerEvents:"none",background:e,color:n,fontSize:o,fontFamily:"system-ui, -apple-system, sans-serif",padding:"4px 12px",borderRadius:"12px",whiteSpace:"nowrap",transition:"opacity 0.3s ease",userSelect:"none"}),i}static _hexToRgba(t,e){const n=t.replace("#","");if(!/^[0-9a-fA-F]{6}$/.test(n))return`rgba(255,255,255,${e})`;return`rgba(${parseInt(n.substring(0,2),16)},${parseInt(n.substring(2,4),16)},${parseInt(n.substring(4,6),16)},${e})`}_applyLineColor(t,e){const n=Rt._hexToRgba(t,e);this.lineA.style.background=n,this.lineB.style.background=n}_updateLayout(){const t=100*this.position+"%",e=this.handle.offsetWidth||40,n=this.handle.offsetHeight||40,o=("vertical"===this.orientation?n:e)/2;if("vertical"===this.orientation){const e=this.container.clientHeight||600,n=this.container.clientWidth||800,i=e/2;if(Object.assign(this.lineA.style,{left:t,top:"0",width:"2px",height:`${Math.max(0,i-o)}px`}),Object.assign(this.lineB.style,{left:t,top:`${i+o}px`,width:"2px",height:`${Math.max(0,e-i-o)}px`}),this.handle.style.left=t,this.handle.style.top="50%",this.beforeLabel){const t=20,e=this.position*n;this.beforeLabel.style.left=`${Math.max(8,e-t-60)}px`,this.beforeLabel.style.right="",this.beforeLabel.style.transform="",this.afterLabel.style.left=`${Math.min(n-8,e+t)}px`,this.afterLabel.style.right="",this.afterLabel.style.transform=""}}else{const e=this.container.clientWidth||800,n=this.container.clientHeight||600,i=e/2;Object.assign(this.lineA.style,{top:t,left:"0",height:"2px",width:`${Math.max(0,i-o)}px`}),Object.assign(this.lineB.style,{top:t,left:`${i+o}px`,height:"2px",width:`${Math.max(0,e-i-o)}px`}),this.handle.style.top=t,this.handle.style.left="50%",this.beforeLabel&&(this.beforeLabel.style.left="50%",this.beforeLabel.style.transform="translateX(-50%)",this.beforeLabel.style.top=`${Math.max(8,this.position*n-40)}px`,this.afterLabel.style.left="50%",this.afterLabel.style.transform="translateX(-50%)",this.afterLabel.style.top=`${Math.min(n-30,this.position*n+20)}px`)}this.handle.setAttribute("aria-valuenow",String(Math.round(100*this.position)))}_handlePointerDown(t){t.preventDefault(),t.stopPropagation(),this.isDragging=!0,this.handle.setPointerCapture(t.pointerId),this._showLabels()}_handlePointerMove(t){if(!this.isDragging)return;t.preventDefault();const e=this.container.getBoundingClientRect();let n;n="vertical"===this.orientation?(t.clientX-e.left)/e.width:(t.clientY-e.top)/e.height,this._setPosition(Math.max(0,Math.min(1,n)))}_handlePointerUp(){this.isDragging&&(this.isDragging=!1,this._resetLabelFade())}_handleKeyDown(t){const e=.02;let n=this.position;if("vertical"===this.orientation)if("ArrowLeft"===t.key)n-=e;else{if("ArrowRight"!==t.key)return;n+=e}else if("ArrowUp"===t.key)n-=e;else{if("ArrowDown"!==t.key)return;n+=e}t.preventDefault(),this._setPosition(Math.max(0,Math.min(1,n))),this._showLabels(),this._resetLabelFade()}_handleDblClick(){this.lastSnapIndex=(this.lastSnapIndex+1)%this.snapPositions.length,this._setPosition(this.snapPositions[this.lastSnapIndex]),this._showLabels(),this._resetLabelFade()}_setPosition(t){this.position=t,this._updateLayout(),this.onPositionChange(t)}_showLabels(){this.beforeLabel.style.opacity="1",this.afterLabel.style.opacity="1"}_resetLabelFade(){this.labelFadeTimeout&&clearTimeout(this.labelFadeTimeout),this._showLabels(),this.labelFadeTimeout=setTimeout(()=>{this.destroyed||(this.beforeLabel.style.opacity="0",this.afterLabel.style.opacity="0")},3e3)}getPosition(){return this.position}setPosition(t){this._setPosition(Math.max(0,Math.min(1,t)))}setLabels(t,e){this.beforeLabel.textContent=t,this.afterLabel.textContent=e}setOrientation(t){this.orientation!==t&&(this.orientation=t,this.handle.style.cursor="vertical"===t?"ew-resize":"ns-resize",this.updateHandleStyle(this.config.handleStyle||"arrows"),this._updateLayout())}updateLineStyle(t,e){const n=t||this.config.lineColor||"#ffffff",o=e??this.config.lineOpacity??.6;void 0!==t&&(this.config.lineColor=t),void 0!==e&&(this.config.lineOpacity=e),this._applyLineColor(n,o)}updateHandleColor(t,e){this.config.handleColor=t,void 0!==e&&(this.config.handleOpacity=e),this.updateHandleStyle(this.config.handleStyle||"arrows")}_handleRgba(){const t=this.config.handleColor||this.config.lineColor||"#ffffff",e=this.config.handleOpacity??.6;return Rt._hexToRgba(t,e)}updateHandleStyle(t){const e=this._handleRgba();for(this.config.handleColor||this.config.lineColor;this.handle.firstChild;)this.handle.removeChild(this.handle.firstChild);const n="rgba(0,0,0,0.6)";if("arrows"===t)Object.assign(this.handle.style,{width:"40px",height:"40px",borderRadius:"50%",borderStyle:"solid",borderWidth:"2px",borderColor:e,background:n}),this.handle.appendChild(function(t,e="white"){const n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("width","20"),n.setAttribute("height","20"),n.setAttribute("viewBox","0 0 20 20"),n.setAttribute("fill","none");const o=document.createElementNS("http://www.w3.org/2000/svg","path"),i=document.createElementNS("http://www.w3.org/2000/svg","path"),s={stroke:e,"stroke-width":"1.5","stroke-linecap":"round","stroke-linejoin":"round"};for(const[t,e]of Object.entries(s))o.setAttribute(t,e),i.setAttribute(t,e);return"vertical"===t?(o.setAttribute("d","M6 10L2 10M2 10L4.5 7.5M2 10L4.5 12.5"),i.setAttribute("d","M14 10L18 10M18 10L15.5 7.5M18 10L15.5 12.5")):(o.setAttribute("d","M10 6L10 2M10 2L7.5 4.5M10 2L12.5 4.5"),i.setAttribute("d","M10 14L10 18M10 18L7.5 15.5M10 18L12.5 15.5")),n.appendChild(o),n.appendChild(i),n}(this.orientation,e));else if("circle"===t)Object.assign(this.handle.style,{width:"28px",height:"28px",borderRadius:"50%",borderStyle:"solid",borderWidth:"2px",borderColor:e,background:n});else{const t="vertical"===this.orientation?{width:"6px",height:"40px"}:{width:"40px",height:"6px"};Object.assign(this.handle.style,{...t,borderRadius:"3px",border:"none",background:e})}this.config.handleStyle=t,this._updateLayout()}setEnableKeyboard(t){this.handle.removeEventListener("keydown",this._onKeyDown),t&&this.handle.addEventListener("keydown",this._onKeyDown)}setEnableSnap(t){this.handle.removeEventListener("dblclick",this._onDblClick),t&&this.handle.addEventListener("dblclick",this._onDblClick)}show(){this.overlay.style.display=""}hide(){this.overlay.style.display="none"}destroy(){this.destroyed=!0,this.labelFadeTimeout&&clearTimeout(this.labelFadeTimeout),this._resizeObserver?.disconnect(),this._resizeObserver=null,this.handle.removeEventListener("pointerdown",this._onPointerDown),window.removeEventListener("pointermove",this._onPointerMove),window.removeEventListener("pointerup",this._onPointerUp),this.handle.removeEventListener("keydown",this._onKeyDown),this.handle.removeEventListener("dblclick",this._onDblClick),this.overlay.remove()}}var zt,Dt={},It={},Ft={};function Bt(){if(zt)return Ft;zt=1,Object.defineProperty(Ft,"__esModule",{value:!0}),Ft.loop=Ft.conditional=Ft.parse=void 0;Ft.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};Ft.conditional=function(t,e){return function(n,o,i,s){e(n,o,i)&&s(n,t,o,i)}};return Ft.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}},Ft}var Vt,$t,Ut={};function Ot(){if(Vt)return Ut;Vt=1,Object.defineProperty(Ut,"__esModule",{value:!0}),Ut.readBits=Ut.readArray=Ut.readUnsigned=Ut.readString=Ut.peekBytes=Ut.readBytes=Ut.peekByte=Ut.readByte=Ut.buildStream=void 0;Ut.buildStream=function(t){return{data:t,pos:0}};var t=function(){return function(t){return t.data[t.pos++]}};Ut.readByte=t;Ut.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)}};Ut.readBytes=e;Ut.peekBytes=function(t){return function(e){return e.data.subarray(e.pos,e.pos+t)}};Ut.readString=function(t){return function(n){return Array.from(e(t)(n)).map(function(t){return String.fromCharCode(t)}).join("")}};Ut.readUnsigned=function(t){return function(n){var o=e(2)(n);return t?(o[1]<<8)+o[0]:(o[0]<<8)+o[1]}};Ut.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 Ut.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},{})}},Ut}var Nt,Wt={};var Ht,Gt,jt={};var Xt=function(){if(Gt)return Dt;Gt=1,Object.defineProperty(Dt,"__esModule",{value:!0}),Dt.decompressFrames=Dt.decompressFrame=Dt.parseGIF=void 0;var t,e=($t||($t=1,function(t){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var e=Bt(),n=Ot(),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}(It)),(t=It)&&t.__esModule?t:{default:t}),n=Bt(),o=Ot(),i=(Nt||(Nt=1,Object.defineProperty(Wt,"__esModule",{value:!0}),Wt.deinterlace=void 0,Wt.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}),Wt),s=(Ht||(Ht=1,Object.defineProperty(jt,"__esModule",{value:!0}),jt.lzw=void 0,jt.lzw=function(t,e,n){var o,i,s,a,r,l,c,d,p,h,u,m,g,f,y,v,b=4096,x=n,w=new Array(n),S=new Array(b),_=new Array(b),C=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<x;){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){C[f++]=_[d],c=d,g=d;continue}for(l=d,d==o&&(C[f++]=g,d=c);d>i;)C[f++]=_[d],d=S[d];g=255&_[d],C[f++]=g,o<b&&(S[o]=c,_[o]=g,0===(++o&s)&&o<b&&(a++,s+=o)),c=l}f--,w[y++]=C[f],p++}for(p=y;p<x;p++)w[p]=0;return w}),jt);Dt.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 Dt.decompressFrame=a,Dt.decompressFrames=function(t,e){return t.frames.filter(function(t){return t.image}).map(function(n){return a(n,t.gct,e)})},Dt}();class qt{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=Xt.parseGIF(n);if(this.frames=Xt.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 Yt(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 Zt(t){return Math.abs(t)<1e-10?0:t}class Kt{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(${Zt(a[0])},${Zt(-a[1])},${Zt(a[2])},${Zt(a[3])},${Zt(a[4])},${Zt(-a[5])},${Zt(a[6])},${Zt(a[7])},${Zt(a[8])},${Zt(-a[9])},${Zt(a[10])},${Zt(a[11])},${Zt(a[12])},${Zt(-a[13])},${Zt(a[14])},${Zt(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(${Zt(t[0])},${Zt(t[1])},${Zt(t[2])},${Zt(t[3])},${Zt(-t[4])},${Zt(-t[5])},${Zt(-t[6])},${Zt(-t[7])},${Zt(t[8])},${Zt(t[9])},${Zt(t[10])},${Zt(t[11])},${Zt(t[12])},${Zt(t[13])},${Zt(t[14])},${Zt(t[15])})`}(n)} scale(${Zt(1/e.pixelWidth)}, ${Zt(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=Yt(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,n){const o=(t,e,o)=>e<=o?t>=e&&t<=o:!!n&&(t>=e||t<=o);this.meshes.forEach(n=>{const i=n.config;if(i.visibilityRange){const s=i.visibilityRange;let a=!0;"percentage"===s.type?a=o(t,s.start,s.end):"waypoint"===s.type&&(a=o(e,s.start,s.end)),n.entity.enabled=a}if(i.billboard&&i.billboardRange){const s=i.billboardRange;let a=!1;"percentage"===s.type?a=o(t,s.start,s.end):"waypoint"===s.type&&(a=o(e,s.start,s.end)),n.entity._billboardActive=a}else i.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=Yt(e.position,{x:0,y:0,z:0});i.setPosition(s.x,s.y,-s.z);const a=Yt(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,b=d*h*g-p*u*m;return i.setRotation(-y,-v,b,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 Qt(t,e){const n=new Kt(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 Jt=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"]),te=new Set(["width","height","clientWidth","clientHeight","getBoundingClientRect","addEventListener","removeEventListener"]),ee=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"]),ne=["window","document","self","globalThis","top","parent","frames","opener","exports","module","require","define","fetch","XMLHttpRequest","WebSocket","EventSource","localStorage","sessionStorage","indexedDB","caches","Worker","SharedWorker","ServiceWorker","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 oe{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&&Jt.has(e)){const n=t[e];return"function"==typeof n?n.bind(t):n}},set:()=>!1,has:(t,e)=>"string"==typeof e&&Jt.has(e),ownKeys:()=>[...Jt],getPrototypeOf:()=>null})),l=(e=this.api.canvas,new Proxy(e,{get(t,e){if(!ee.has(e)&&"string"==typeof e&&te.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",...ne],u=`'use strict';\n(function(eval) {\n${t}\n}).call(this, undefined);`,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,...ne.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 ie(t,e,n,o,i,s){if(!o||""===o.trim())return null;console.log("[Custom Script] Initializing with hardened sandbox...");const a=new oe(o);return a.initialize({viewer:t,canvas:n,getScrollPercentage:i,getCurrentWaypointIndex:s},e),a.execute(),a}class se{constructor(e,n,o={}){this.options=o,this.frameAssets=new Map,this.currentFrame=0,this.isPlaying=!1,this.lastFrameTime=0,this.loadingFrames=new Set,this.destroyed=!1,this.listeners=new Map,this.app=e,this.frameUrls=n.frameUrls,this.fps=n.fps||24,this.loop=!1!==n.loop,this.preloadCount=n.preloadCount||10,this.frameInterval=1e3/this.fps,this.entity=new t.Entity("frameSequenceSplat"),this.entity.addComponent("gsplat",{unified:!0}),n.rotation&&this.entity.setEulerAngles(n.rotation[0],n.rotation[1],n.rotation[2]),this.entity.enabled=!1,this.app.root.addChild(this.entity),this.preloadInitialFrames(),n.autoplay&&this.preloadFrame(0).then(()=>{this.destroyed||this.play()})}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},{reorder:!1});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)}}displayFrame(t){if(this.destroyed)return!1;const e=this.frameAssets.get(t);if(!e||!e.loaded)return!1;const n=this.entity.gsplat;return n&&(n.asset=e),this.entity.enabled=!0,this.currentFrame=t,this.emit("frameChange",t,this.frameUrls.length),this.options.onFrameChange?.(t,this.frameUrls.length),this.updatePreloadWindow(),!0}async displayFrameAsync(t){if(this.destroyed)return;if(this.displayFrame(t))return;await this.preloadFrame(t)&&!this.destroyed&&this.displayFrame(t)}update(t){if(!this.isPlaying||this.destroyed)return;const e=performance.now(),n=e-this.lastFrameTime;if(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)&&(this.lastFrameTime=e-n%this.frameInterval)}}play(){this.isPlaying||this.destroyed||(this.isPlaying=!0,this.lastFrameTime=performance.now(),this.entity.enabled||this.displayFrameAsync(this.currentFrame),this.emit("play"))}pause(){this.isPlaying=!1,this.emit("pause")}stop(){this.isPlaying=!1,this.currentFrame=0,this.displayFrameAsync(0),this.emit("stop")}setFrame(t){t<0&&(t=0),t>=this.frameUrls.length&&(t=this.frameUrls.length-1),this.displayFrameAsync(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.entity.setPosition(t,e,n)}setRotation(t,e,n){this.entity.setEulerAngles(t,e,n)}setScale(t,e,n){this.entity.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.entity.destroy(),this.listeners.clear()}}class ae{static isRetryableStatus(t){return 408===t||429===t||t>=500}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.beaconFlushed=!1,this.retryNotBeforeMs=0,this.handleCanvasClick=null,this.handleUiClick=null,this.viewerContainer=null,this.samplingPaused=!1,this.viewerEventBindings=[],this.sessionId=crypto.randomUUID();try{this.visitorId=localStorage.getItem("storysplat_visitor_id")||sessionStorage.getItem("storysplat_visitor_id")||"",this.visitorId||(this.visitorId=crypto.randomUUID());try{localStorage.setItem("storysplat_visitor_id",this.visitorId)}catch{}}catch{this.visitorId=crypto.randomUUID()}this.handleVisibility=()=>{"hidden"===document.visibilityState&&this.beaconFlush(!1)},this.handleUnload=()=>this.beaconFlush(!0),this.setupEventListeners(),this.sampleCamera(!0),this.startCameraSampling(),this.startFlushInterval(),document.addEventListener("visibilitychange",this.handleVisibility),window.addEventListener("beforeunload",this.handleUnload),this.observeViewerEvent("error",()=>this.destroy())}ts(){return Date.now()-this.sessionStart}quantize(t,e){return Math.round(t/e)*e}safeEventName(t){return String(t||"").replace(/[^a-zA-Z0-9 _-]+/g," ").replace(/\s+/g," ").trim().slice(0,64).trim()||"unknown"}isSafeTrackingId(t){return/^[a-zA-Z0-9_-]{1,128}$/.test(t)}safeHostname(t){const e=String(t||"").trim().toLowerCase().replace(/:\d+$/,"").slice(0,200);return e&&!e.includes("..")&&/^[a-z0-9.-]{1,200}$/.test(e)?e:""}safeUrlForAnalytics(t,e){const n=String(t||"").trim();if(!n)return"";try{const t=new URL(n);return/^https?:$/.test(t.protocol)?`${t.origin}${t.pathname}`.slice(0,e):""}catch{return n.replace(/[\u0000-\u001F<>]/g,"").slice(0,e)}}getEmbedSourceMeta(){try{return{hostname:this.safeHostname(window.location.hostname||""),referrer:this.safeUrlForAnalytics(document.referrer||"",500),embedUrl:this.safeUrlForAnalytics(window.location.href||"",500)}}catch{return{hostname:"",referrer:"",embedUrl:""}}}recordInteraction(t,e){const n=this.safeEventName(e);this.pendingEvents.push({type:t,name:n}),this.timeline.push({t:this.ts(),k:"u",c:t,n:n})}elementText(t){return(t.getAttribute("aria-label")||t.getAttribute("title")||t.textContent||t.dataset.linkId||t.dataset.portalId||t.getAttribute("href")||"").trim()}describeButton(t){const e=t.classList;if(e.contains("storysplat-btn-prev"))return"previous waypoint";if(e.contains("storysplat-btn-next"))return"next waypoint";if(e.contains("storysplat-btn-play"))return"play pause";if(e.contains("storysplat-mode-btn"))return`camera mode ${t.dataset.mode||this.elementText(t)}`;if(e.contains("storysplat-explore-btn"))return`explore mode ${t.dataset.exploreMode||this.elementText(t)}`;if(e.contains("storysplat-fullscreen-btn"))return"fullscreen";if(e.contains("storysplat-mute-btn"))return"mute toggle";if(e.contains("storysplat-help-btn"))return"help";if(e.contains("storysplat-help-tab"))return`help tab ${t.dataset.tab||this.elementText(t)}`;if(e.contains("storysplat-share-btn"))return"share";if(e.contains("storysplat-photo-share-close"))return"photo share close";if(e.contains("storysplat-photo-share-action"))return`photo share ${this.elementText(t)}`;if(e.contains("storysplat-relight-btn"))return"relighting toggle";if(e.contains("storysplat-fisheye-btn"))return"tiny planet toggle";if(e.contains("storysplat-waypoint-list-toggle"))return"waypoint menu toggle";if(e.contains("storysplat-scene-menu-toggle"))return"scene menu toggle";if(e.contains("storysplat-portal-popup-confirm"))return"portal confirm";if(e.contains("storysplat-portal-popup-cancel"))return"portal cancel";if(e.contains("storysplat-hotspot-popup-close"))return"hotspot popup close";if(e.contains("storysplat-measure-btn"))return"measure toggle";if(e.contains("storysplat-skins-btn"))return"virtual staging toggle";if(e.contains("storysplat-skin-exit-btn"))return"virtual staging exit";if(e.contains("storysplat-vr-btn"))return"vr";if(e.contains("storysplat-ar-btn"))return"ar";if(e.contains("storysplat-lazy-load-start-btn"))return"start experience";const n=this.elementText(t);if(n)return n;const o=Array.from(e).find(t=>t.startsWith("storysplat-"));return o?o.replace(/^storysplat-/,"").replace(/-/g," "):"button"}trackStagingView(t){if(!this.isSafeTrackingId(t))return;const e=`${this.baseUrl}/api/track-embed`,n=JSON.stringify({sceneId:this.sceneId,ownerId:this.ownerId,type:"staging-view",skinId:t,visitorId:this.visitorId,sessionId:this.sessionId});fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:n}).then(t=>{!t.ok&&ae.isRetryableStatus(t.status)&&(this.saveToRetryQueue(n),429===t.status&&this.noteRetryAfter(t))}).catch(()=>{this.saveToRetryQueue(n)})}trackUiClick(t){if(this.destroyed)return;const e=t.target;if(!(e instanceof Element))return;if(e===this.viewer.canvas||e.closest("canvas")===this.viewer.canvas)return;const n=e.closest('.storysplat-scene-menu-item, .storysplat-scene-menu-folder-header, .storysplat-waypoint-item, .storysplat-hotspot-popup-link, .storysplat-skin-item, button, a, [role="button"]');if(n&&this.viewerContainer?.contains(n)&&!(n instanceof HTMLButtonElement&&n.disabled)){if(n.classList.contains("storysplat-skin-item")){const t=n.dataset.skinId||n.getAttribute("data-skin-id")||"",e=Number(n.dataset.skinIndex),o=Number.isFinite(e)?`staging ${e+1}`:"virtual staging image",i=this.elementText(n)||o;return this.recordInteraction("ui",`virtual staging ${i}`),void(t&&this.trackStagingView(t))}if(n.classList.contains("storysplat-scene-menu-item")){const t=this.elementText(n);if(n.dataset.portalId)return void this.recordInteraction("menu",`portal ${t||n.dataset.portalId}`);if(n.dataset.linkId){const e=n.dataset.contentType||"link";return this.recordInteraction("menu",`${e} ${t||n.dataset.linkId}`),void("link"===e&&this.recordInteraction("link",`menu ${t||n.dataset.linkId}`))}return void this.recordInteraction("menu",t||"scene menu item")}if(n.classList.contains("storysplat-scene-menu-folder-header"))this.recordInteraction("menu",`folder ${this.elementText(n)}`);else{if(n.classList.contains("storysplat-waypoint-item")){const t=n.dataset.waypointIndex;return void this.recordInteraction("menu",`waypoint ${t?Number(t)+1:this.elementText(n)}`)}if(n.classList.contains("storysplat-hotspot-popup-link"))this.recordInteraction("link",`hotspot ${this.elementText(n)}`);else{if(n instanceof HTMLAnchorElement){const t=this.elementText(n);return void this.recordInteraction("link",t||n.href||"link")}this.recordInteraction("ui",this.describeButton(n))}}}}sampleCamera(t=!1){if(!(this.destroyed||this.samplingPaused||this.ts()>this.maxDurationMs))try{const e=this.viewer.getPosition(),n=this.viewer.getRotation(),o=Math.abs(e.x-this.lastPos.x)+Math.abs(e.y-this.lastPos.y)+Math.abs(e.z-this.lastPos.z),i=Math.abs(n.x-this.lastRot.x)+Math.abs(n.y-this.lastRot.y)+Math.abs(n.z-this.lastRot.z);if(!t&&o<.1&&i<2)return;this.lastPos={...e},this.lastRot={...n},this.timeline.push({t:this.ts(),k:"c",p:[this.quantize(e.x,.01),this.quantize(e.y,.01),this.quantize(e.z,.01)],r:[this.quantize(n.x,.1),this.quantize(n.y,.1),this.quantize(n.z,.1)]})}catch{}}startCameraSampling(){this.cameraIntervalId=setInterval(()=>this.sampleCamera(),1e3)}observeViewerEvent(t,e){(this.viewer.onInternal?this.viewer.onInternal.bind(this.viewer):this.viewer.on.bind(this.viewer))(t,e),this.viewerEventBindings.push({viewer:this.viewer,event:t,callback:e})}setupEventListeners(){this.observeViewerEvent("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})}),this.observeViewerEvent("modeChange",t=>{const e=t?.mode||"unknown";this.pendingEvents.push({type:"mode",name:e}),this.timeline.push({t:this.ts(),k:"m",m:e})}),this.observeViewerEvent("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]})}),this.observeViewerEvent("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||""})}),this.observeViewerEvent("progressUpdate",t=>{void 0!==t?.progress&&this.timeline.push({t:this.ts(),k:"s",v:Math.round(1e3*t.progress)/1e3})}),this.observeViewerEvent("playbackStart",()=>{this.timeline.push({t:this.ts(),k:"pb",a:"start"})}),this.observeViewerEvent("playbackStop",()=>{this.timeline.push({t:this.ts(),k:"pb",a:"stop"})}),this.observeViewerEvent("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{}try{this.viewerContainer=this.viewer.canvas.closest(".storysplat-viewer-container")||this.viewer.canvas.parentElement,this.viewerContainer&&(this.handleUiClick=t=>this.trackUiClick(t),this.viewerContainer.addEventListener("click",this.handleUiClick,!0))}catch{}}detachDomListeners(){if(this.handleCanvasClick){try{this.viewer.canvas.removeEventListener("click",this.handleCanvasClick)}catch{}this.handleCanvasClick=null}if(this.handleUiClick&&this.viewerContainer){try{this.viewerContainer.removeEventListener("click",this.handleUiClick,!0)}catch{}this.handleUiClick=null,this.viewerContainer=null}}detachViewerEventListeners(){for(const t of this.viewerEventBindings)try{t.viewer.off(t.event,t.callback)}catch{}this.viewerEventBindings=[]}beginPortalTransition(){this.destroyed||(this.samplingPaused=!0,this.detachDomListeners(),this.detachViewerEventListeners())}attachViewer(t){this.destroyed||(this.detachDomListeners(),this.detachViewerEventListeners(),this.viewer=t,this.samplingPaused=!1,this.setupEventListeners(),this.observeViewerEvent("error",()=>this.destroy()),this.sampleCamera(!0))}startFlushInterval(){this.flushIntervalId=setInterval(()=>this.flush(),3e4)}buildAnalyticsPayload(){const t=Math.round((Date.now()-this.sessionStart)/1e3),e=this.pendingEvents.splice(0,100),n=this.getEmbedSourceMeta();return{sceneId:this.sceneId,ownerId:this.ownerId,type:"analytics",sessionId:this.sessionId,visitorId:this.visitorId,sessionDuration:t,events:e,hostname:n.hostname,referrer:n.referrer}}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};if(0===this.chunkIndex){const t=this.getEmbedSourceMeta();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(),referrer:t.referrer,embedUrl:t.embedUrl,hostname:t.hostname}}if(t){const t=Date.now()-this.sessionStart;n.duration=Math.round(t/1e3),n.durationMs=t}return this.chunkIndex++,n}async drainRetryQueue(t,e){if(Date.now()<this.retryNotBeforeMs)return;let n;try{const t=localStorage.getItem(ae.QUEUE_KEY);if(!t)return;if(n=JSON.parse(t),!Array.isArray(n)||0===n.length)return}catch{try{localStorage.removeItem(ae.QUEUE_KEY)}catch{}return}const o=Date.now(),i=n.filter(t=>t&&"string"==typeof t.body&&"number"==typeof t.savedAt&&o-t.savedAt<ae.QUEUE_MAX_AGE_MS);if(0===i.length){try{localStorage.removeItem(ae.QUEUE_KEY)}catch{}return}const s=await Promise.allSettled(i.map(n=>{const o=new AbortController,i=setTimeout(()=>o.abort(),ae.RETRY_FETCH_TIMEOUT_MS);return fetch(t,{method:"POST",headers:e,body:n.body,signal:o.signal}).finally(()=>clearTimeout(i))})),a=[];s.forEach((t,e)=>{if("fulfilled"===t.status){const n=t.value;if(n.ok)return;ae.isRetryableStatus(n.status)&&(a.push(i[e]),429===n.status&&this.noteRetryAfter(n))}else a.push(i[e])});try{0===a.length?localStorage.removeItem(ae.QUEUE_KEY):localStorage.setItem(ae.QUEUE_KEY,JSON.stringify(a))}catch{}}noteRetryAfter(t){const e=t.headers.get("Retry-After");if(!e)return;let n=0;const o=Number(e);if(Number.isFinite(o)&&o>=0)n=1e3*o;else{const t=Date.parse(e);Number.isNaN(t)||(n=Math.max(0,t-Date.now()))}n=Math.min(Math.max(n,1e3),3e5),this.retryNotBeforeMs=Math.max(this.retryNotBeforeMs,Date.now()+n)}saveToRetryQueue(t){try{const e=localStorage.getItem(ae.QUEUE_KEY),n=e?JSON.parse(e):[],o=Array.isArray(n)?n:[];for(o.push({body:t,savedAt:Date.now()});o.length>ae.MAX_QUEUE_SIZE;)o.shift();localStorage.setItem(ae.QUEUE_KEY,JSON.stringify(o))}catch{}}async flush(){if(this.destroyed)return;const t=`${this.baseUrl}/api/track-embed`,e={"Content-Type":"application/json"};if(await this.drainRetryQueue(t,e),this.pendingEvents.length>0){const n=JSON.stringify(this.buildAnalyticsPayload());try{const o=await fetch(t,{method:"POST",headers:e,body:n});!o.ok&&ae.isRetryableStatus(o.status)&&(this.saveToRetryQueue(n),429===o.status&&this.noteRetryAfter(o))}catch{this.saveToRetryQueue(n)}}if(this.timeline.length>0){const n=JSON.stringify(this.buildSessionChunkPayload(!1));try{const o=await fetch(t,{method:"POST",headers:e,body:n});!o.ok&&ae.isRetryableStatus(o.status)&&(this.saveToRetryQueue(n),429===o.status&&this.noteRetryAfter(o))}catch{this.saveToRetryQueue(n)}}}beaconFlush(t){if(this.destroyed||this.beaconFlushed)return;t&&(this.beaconFlushed=!0);const e=`${this.baseUrl}/api/track-embed`;if(this.pendingEvents.length>0)try{const t=this.buildAnalyticsPayload();navigator.sendBeacon(e,new Blob([JSON.stringify(t)],{type:"text/plain"}))}catch{}if(this.timeline.length>0||t)try{const n=this.buildSessionChunkPayload(t);navigator.sendBeacon(e,new Blob([JSON.stringify(n)],{type:"text/plain"}))}catch{}}destroy(){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.detachDomListeners(),this.detachViewerEventListeners())}}function re(t,e,n,o){const i=new ae(t,e,n,o);return{destroy:()=>i.destroy(),beginPortalTransition:()=>i.beginPortalTransition(),attachViewer:t=>i.attachViewer(t)}}ae.QUEUE_KEY="storysplat_analytics_queue",ae.MAX_QUEUE_SIZE=10,ae.QUEUE_MAX_AGE_MS=36e5,ae.RETRY_FETCH_TIMEOUT_MS=5e3;const le=/^G-[A-Z0-9]{6,20}$/i;function ce(t){return function(t){return"string"==typeof t&&le.test(t.trim())}(t)?t.trim().toUpperCase():null}function de(t,e="unknown",n=100){return(String(t||e).trim()||e).slice(0,n)}function pe(t,e,n){const o=ce(e);if(!o||"undefined"==typeof window||"undefined"==typeof document)return{destroy:()=>{}};!function(t){const e=`storysplat-ga-${t.replace(/[^A-Z0-9_-]/gi,"-")}`;if(!document.getElementById(e)){const n=document.createElement("script");n.id=e,n.async=!0,n.src=`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(t)}`,document.head.appendChild(n)}window.dataLayer=window.dataLayer||[],"function"!=typeof window.gtag&&(window.gtag=function(...t){window.dataLayer.push(t)}),window.gtag("js",new Date),window.gtag("config",t,{send_page_view:!1})}(o);const i=window.gtag;if("function"!=typeof i)return{destroy:()=>{}};const s=Date.now(),a=de(n?.sceneId,"",128),r=de(n?.sceneName||document.title,"",120),l=[],c=(e,n)=>{(t.onInternal?t.onInternal.bind(t):t.on.bind(t))(e,n),l.push({event:e,callback:n})};i("event","page_view",{page_title:r||document.title,storysplat_scene_id:a,storysplat_scene_name:r}),c("waypointChange",t=>{i("event","storysplat_waypoint",{waypoint_name:de(t?.waypoint?.name||t?.name),waypoint_index:t?.index??0,storysplat_scene_id:a})}),c("hotspotClick",t=>{i("event","storysplat_hotspot",{hotspot_name:de(t?.hotspot?.title||t?.hotspot?.name),hotspot_id:de(t?.hotspot?.id,"",128),storysplat_scene_id:a})}),c("modeChange",t=>{i("event","storysplat_mode_change",{camera_mode:de(t?.mode),storysplat_scene_id:a})}),c("portalActivated",t=>{i("event","storysplat_portal",{portal_target:de(t?.targetSceneName),portal_target_id:de(t?.targetSceneId,"",128),storysplat_scene_id:a})}),c("playbackStart",()=>{i("event","storysplat_playback",{action:"start",storysplat_scene_id:a})}),c("playbackStop",()=>{i("event","storysplat_playback",{action:"stop",storysplat_scene_id:a})}),c("playbackComplete",()=>{i("event","storysplat_playback",{action:"complete",storysplat_scene_id:a})});const d=()=>{const t=Math.round((Date.now()-s)/1e3);i("event","storysplat_engagement",{session_duration:t,storysplat_scene_id:a})};return window.addEventListener("beforeunload",d),{destroy:()=>{window.removeEventListener("beforeunload",d);for(const e of l)try{t.off(e.event,e.callback)}catch{}l.length=0}}}function he(t,e){const n=t.keyframes;if(0===n.length)return null;if(1===n.length)return n[0].value;if(e<=n[0].time)return n[0].value;if(e>=n[n.length-1].time)return n[n.length-1].value;let o=0,i=n.length-1;for(;o<i-1;){const t=o+i>>1;n[t].time<=e?o=t:i=t}const s=n[o],a=n[i],r=a.time-s.time;if(r<=0)return s.value;const l=function(t,e){switch(e){case"linear":default:return t;case"ease-in":return t*t*t;case"ease-out":return 1-(1-t)*(1-t)*(1-t);case"ease-in-out":return t*t*(3-2*t)}}((e-s.time)/r,s.easing);return s.value+(a.value-s.value)*l}class ue{constructor(t,e,n={}){this.states=new Map,this.destroyed=!1,this.warnedEntities=new Set,this.curveCache=new Map,this.curveSetCache=new Map,this.entityLookup=t,this.sceneHandlers=n,this.animations=e;for(const t of e)"independent"===t.timelineMode&&this.states.set(t.id,{time:0,delayRemaining:t.delay||0,direction:1,playing:t.autoplay&&t.enabled})}update(t,e){if(!this.destroyed)for(const n of this.animations)if(n.enabled)if("scroll"===n.timelineMode){const t=100*e;this.evaluateAndApply(n,t)}else{const e=this.states.get(n.id);if(!e||!e.playing)continue;if(e.delayRemaining>0){e.delayRemaining-=t;continue}if(e.time+=t*e.direction,e.time>=n.duration)switch(n.playbackMode){case"loop":e.time=e.time%n.duration;break;case"pingpong":{const t=e.time-n.duration;e.time=Math.max(0,n.duration-t),e.direction=-1;break}default:e.time=n.duration,e.playing=!1}else e.time<=0&&-1===e.direction&&(e.time=Math.min(n.duration,Math.abs(e.time)),e.direction=1);this.evaluateAndApply(n,e.time)}}evaluateAndApply(t,e){const n=new Map;for(const o of t.tracks){const t=he(o,e);null!==t&&n.set(o.property,t)}if(0===n.size)return;if("fog"===t.entityType||"weather"===t.entityType){const e=this.sceneHandlers[t.entityType];return void(e&&e.apply(n))}const o=this.entityLookup(t.entityId,t.entityType);if(!o){const e=`${t.entityId}:${t.entityType}`;return void(this.warnedEntities.has(e)||(this.warnedEntities.add(e),console.warn(`[EntityAnim] Entity not found: id="${t.entityId}", type="${t.entityType}"`)))}this.applyValues(o,n,t.id)}getOrCreateCurve(e,n){let o=this.curveCache.get(e);return o?o.keys[0][1]=n:(o=new t.Curve([0,n]),this.curveCache.set(e,o)),o}getOrCreateCurveSet(e,n,o,i){let s=this.curveSetCache.get(e);return s?(s.curves[0].keys[0][1]=n,s.curves[1].keys[0][1]=o,s.curves[2].keys[0][1]=i):(s=new t.CurveSet([[0,n],[0,o],[0,i]]),this.curveSetCache.set(e,s)),s}applyValues(e,n,o){const i=e.getLocalPosition().clone(),s=e.getLocalEulerAngles().clone(),a=e.getLocalScale().clone();n.has("position.x")&&(i.x=n.get("position.x")),n.has("position.y")&&(i.y=n.get("position.y")),n.has("position.z")&&(i.z=-n.get("position.z"));const r=180/Math.PI;n.has("rotation.x")&&(s.x=n.get("rotation.x")*r),n.has("rotation.y")&&(s.y=n.get("rotation.y")*r),n.has("rotation.z")&&(s.z=-n.get("rotation.z")*r),n.has("scale.x")&&(a.x=n.get("scale.x")),n.has("scale.y")&&(a.y=n.get("scale.y")),n.has("scale.z")&&(a.z=n.get("scale.z")),e.setLocalPosition(i),e.setLocalEulerAngles(s),e.setLocalScale(a);const l=e.particlesystem;if(l){if(n.has("particle.rate")){const t=n.get("particle.rate");l.rate=t>0?1/t:0}if(n.has("particle.lifetime")&&(l.lifetime=n.get("particle.lifetime")),n.has("particle.speed")){const e=n.get("particle.speed");l.emitterShape===t.EMITTERSHAPE_SPHERE?l.radialSpeedGraph=this.getOrCreateCurve(`${o}-speed`,e):l.localVelocityGraph=this.getOrCreateCurveSet(`${o}-localVel`,e,e,e)}if(n.has("particle.speed2")){const e=n.get("particle.speed2");l.emitterShape===t.EMITTERSHAPE_SPHERE?l.radialSpeedGraph2=this.getOrCreateCurve(`${o}-speed2`,e):l.localVelocityGraph2=this.getOrCreateCurveSet(`${o}-localVel2`,e,e,e)}n.has("particle.radialSpeed")&&(l.radialSpeedGraph=this.getOrCreateCurve(`${o}-radialSpeed`,n.get("particle.radialSpeed"))),n.has("particle.radialSpeed2")&&(l.radialSpeedGraph2=this.getOrCreateCurve(`${o}-radialSpeed2`,n.get("particle.radialSpeed2"))),n.has("particle.scale")&&(l.scaleGraph=this.getOrCreateCurve(`${o}-scale`,n.get("particle.scale"))),n.has("particle.scale2")&&(l.scaleGraph2=this.getOrCreateCurve(`${o}-scale2`,n.get("particle.scale2"))),n.has("particle.rotationSpeedMin")&&(l.rotationSpeedGraph=this.getOrCreateCurve(`${o}-rotSpeedMin`,n.get("particle.rotationSpeedMin"))),n.has("particle.rotationSpeedMax")&&(l.rotationSpeedGraph2=this.getOrCreateCurve(`${o}-rotSpeedMax`,n.get("particle.rotationSpeedMax")));const e=n.get("particle.gravity.x"),i=n.get("particle.gravity.y"),s=n.get("particle.gravity.z");if(void 0!==e||void 0!==i||void 0!==s){const t=l.velocityGraph?.curves?.[0]?.keys?.[0]?.[1]??0,n=l.velocityGraph?.curves?.[1]?.keys?.[0]?.[1]??0,a=l.velocityGraph?.curves?.[2]?.keys?.[0]?.[1]??0;l.velocityGraph=this.getOrCreateCurveSet(`${o}-velocity`,e??t,i??n,s??a)}n.has("particle.opacity")&&(l.alphaGraph=this.getOrCreateCurve(`${o}-opacity`,n.get("particle.opacity"))),(n.has("particle.scale")||n.has("particle.scale2"))&&l.reset&&l.reset()}}play(){for(const t of this.animations){if("independent"!==t.timelineMode||!t.enabled)continue;const e=this.states.get(t.id);e&&(e.playing=!0)}}pause(){for(const t of this.states.values())t.playing=!1}reset(){for(const t of this.animations){if("independent"!==t.timelineMode)continue;const e=this.states.get(t.id);e&&(e.time=0,e.direction=1,e.delayRemaining=t.delay||0,e.playing=t.autoplay&&t.enabled)}}playAnimation(t){const e=this.states.get(t);e&&(e.playing=!0)}pauseAnimation(t){const e=this.states.get(t);e&&(e.playing=!1)}resetAnimation(t){const e=this.animations.find(e=>e.id===t),n=this.states.get(t);n&&e&&(n.time=0,n.direction=1,n.delayRemaining=e.delay||0)}evaluateAt(t,e){const n=this.animations.find(e=>e.id===t);n?this.evaluateAndApply(n,e):console.warn(`[EntityAnim] evaluateAt: animation "${t}" not found in ${this.animations.length} animations`)}evaluateAllAt(t,e){if(!this.destroyed)for(const n of this.animations)n.enabled&&("scroll"===n.timelineMode?void 0!==e&&this.evaluateAndApply(n,100*e):this.evaluateAndApply(n,t))}setAnimations(t){this.animations=t,this.warnedEntities.clear(),this.curveCache.clear(),this.curveSetCache.clear();const e=new Set(this.states.keys());for(const n of t)"independent"!==n.timelineMode||e.has(n.id)||this.states.set(n.id,{time:0,delayRemaining:n.delay||0,direction:1,playing:n.autoplay&&n.enabled});const n=new Set(t.map(t=>t.id));for(const t of this.states.keys())n.has(t)||this.states.delete(t)}getEntityTransform(e,n){if("fog"===n||"weather"===n){const t=this.sceneHandlers[n],e=t?.readValues();return e?{position:{x:0,y:0,z:0},rotation:{x:0,y:0,z:0},scale:{x:1,y:1,z:1},particleProps:e}:null}const o=this.entityLookup(e,n);if(!o)return null;const i=o.getLocalPosition(),s=o.getLocalEulerAngles(),a=o.getLocalScale(),r=Math.PI/180,l={position:{x:i.x,y:i.y,z:-i.z},rotation:{x:s.x*r,y:s.y*r,z:-s.z*r},scale:{x:a.x,y:a.y,z:a.z}},c=o.particlesystem;return c&&(l.particleProps={"particle.rate":c.rate>0?1/c.rate:0,"particle.lifetime":c.lifetime??5,"particle.speed":c.emitterShape===t.EMITTERSHAPE_SPHERE?c.radialSpeedGraph?.keys?.[0]?.[1]??1:c.localVelocityGraph?.curves?.[0]?.keys?.[0]?.[1]??1,"particle.speed2":c.emitterShape===t.EMITTERSHAPE_SPHERE?c.radialSpeedGraph2?.keys?.[0]?.[1]??1:c.localVelocityGraph2?.curves?.[0]?.keys?.[0]?.[1]??1,"particle.radialSpeed":c.radialSpeedGraph?.keys?.[0]?.[1]??0,"particle.radialSpeed2":c.radialSpeedGraph2?.keys?.[0]?.[1]??0,"particle.rotationSpeedMin":c.rotationSpeedGraph?.keys?.[0]?.[1]??0,"particle.rotationSpeedMax":c.rotationSpeedGraph2?.keys?.[0]?.[1]??0},c.velocityGraph&&(l.particleProps["particle.gravity.x"]=c.velocityGraph.curves?.[0]?.keys?.[0]?.[1]??0,l.particleProps["particle.gravity.y"]=c.velocityGraph.curves?.[1]?.keys?.[0]?.[1]??0,l.particleProps["particle.gravity.z"]=c.velocityGraph.curves?.[2]?.keys?.[0]?.[1]??0)),l}destroy(){this.destroyed=!0,this.states.clear(),this.animations=[],this.warnedEntities.clear(),this.curveCache.clear(),this.curveSetCache.clear()}}class me{constructor(e,n,o){this.metadata=null,this.highlightedSegment=0,this._material=null,this.entity=e;const i=e.root;if(this.device=i?.app?.graphicsDevice??t.app?.graphicsDevice,!this.device)throw new Error("SegmentAttachment: cannot find graphics device");this._numSplats=o;const s=e.gsplat;if(!s)throw new Error("SegmentAttachment: entity has no gsplat component");const a=s.instance,r=s._placement,l=s.asset?.resource??a?.resource??r?.resource,c=l?.textureDimensions??l?.streams?.textureDimensions;if(c)this._texWidth=c.x,this._texHeight=c.y;else{const t=Math.ceil(Math.sqrt(o));this._texWidth=t,this._texHeight=t}this.segmentIds=new Uint16Array(this._texWidth*this._texHeight);const d=Math.min(n.length,this.segmentIds.length);this.segmentIds.set(n.subarray(0,d)),this.segmentTexture=new t.Texture(this.device,{name:"segmentIds",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._uploadSegments()}updateMaterialParams(){if(!this._material){const t=this.entity.gsplat;if(!t)return;const e=t.instance??t._placement;if(!e)return;this._material=e.material??e.meshInstance?.material}this._material&&(this._material.setParameter("segmentTexture",this.segmentTexture),this._material.setParameter("highlightedSegment",this.highlightedSegment),this._material.update())}getSegmentAtSplat(t){return t<0||t>=this._numSplats?0:this.segmentIds[t]}setHighlight(t){this.highlightedSegment!==t&&(this.highlightedSegment=t,this.updateMaterialParams())}clearHighlight(){this.setHighlight(0)}destroy(){this.segmentTexture.destroy(),this._material=null}_uploadSegments(){const t=this.segmentTexture.lock(),e=t instanceof Uint16Array?t:new Uint16Array(t instanceof ArrayBuffer?t:t.buffer);e.set(this.segmentIds.subarray(0,e.length)),this.segmentTexture.unlock()}}async function ge(t,e,n){const o=await fetch(e);if(!o.ok)throw new Error(`Failed to fetch segment data: ${o.status}`);const i=await o.arrayBuffer(),s=new Uint16Array(i),a=s.length,r=new me(t,s,a);if(n)try{const t=await fetch(n);t.ok&&(r.metadata=await t.json())}catch(t){console.warn("[SegmentAttachment] Failed to load segment metadata:",t)}return r}const fe="x-storysplat-ar-log",ye="storysplatArDebug",ve=/(authorization|cookie|set[-_]?cookie|credential|password|secret|session(?:[-_]?id)?|signature|signed|token|jwt|csrf|xsrf|api[-_]?key|key)$/i,be=/(url|uri|href|baseUrl)$/i,xe=/\bhttps?:\/\/[^\s"'<>`]+/gi,we=/(^|[\s"'(])\/[^\s"'<>`)]*[?#][^\s"'<>`)]*/g,Se=/((?:authorization|cookie|set[-_]?cookie|credential|password|secret|session(?:[-_]?id)?|signature|signed|token|jwt|csrf|xsrf|api[-_]?key|key)=)[^&\s"'<>`]+/gi,_e=/Bearer\s+[A-Za-z0-9._~+/=-]+/gi,Ce=/\b(authorization|cookie|set-cookie|x-csrf-token|x-xsrf-token|csrf-token|xsrf-token|jwt|api[-_]?key)\s*:\s*[^;\r\n]+/gi;function Me(t){try{const e=new URL(t,"undefined"!=typeof window?window.location.origin:"http://localhost");return"http://localhost"===e.origin&&t.startsWith("/")?e.pathname:`${e.origin}${e.pathname}`}catch(t){return"[redacted-url]"}}function Ee(t){return t.replace(xe,t=>Me(t)).replace(we,(t,e)=>`${e}${Me(t.slice(e.length))}`).replace(Se,"$1[redacted]").replace(_e,"Bearer [redacted]").replace(Ce,"$1: [redacted]")}function Pe(){if("undefined"!=typeof window)try{const t=new URL(window.location.href);return`${t.origin}${t.pathname}`}catch(t){return}}function ke(){return"undefined"!=typeof window&&(("localhost"===(t=window.location.hostname)||"127.0.0.1"===t||"0.0.0.0"===t||t.endsWith(".local")||/^10\./.test(t)||/^192\.168\./.test(t)||/^172\.(1[6-9]|2\d|3[0-1])\./.test(t))&&function(){if("undefined"==typeof window)return!1;try{return"1"===new URLSearchParams(window.location.search).get(ye)||"true"===window.localStorage?.getItem(ye)}catch(t){return!1}}());var t}function Te(t,e,n){const o=function(t){if(void 0===t)return;const e=new WeakSet;try{return JSON.parse(JSON.stringify(t,(t,n)=>{const o=String(t);if(ve.test(o))return"[redacted]";if(be.test(o)&&"string"==typeof n)return Me(n);if("string"==typeof n)return Ee(n);if("function"==typeof n)return`[Function ${n.name||"anonymous"}]`;if("bigint"==typeof n)return n.toString();if(n instanceof Error)return{name:n.name,message:Ee(n.message)};if(n&&"object"==typeof n){if(e.has(n))return"[Circular]";e.add(n)}return n}))}catch(e){return Ee(String(t))}}(n),i=Ee(e),s="[StorySplat AR]",a=void 0===o?[s,i]:[s,i,o];"error"===t?console.error(...a):"warn"===t?console.warn(...a):console.log(...a),function(t){if(ke())try{const e=JSON.stringify(t);fetch("/__storysplat_ar_log",{method:"POST",headers:{"Content-Type":"application/json",[fe]:"1"},body:e,keepalive:e.length<6e4}).catch(()=>{})}catch(t){}}({source:"storysplat-ar",level:t,message:i,data:o,timestamp:(new Date).toISOString(),url:Pe()})}const Ae=(t,e)=>Te("debug",t,e),Le=(t,e)=>Te("info",t,e),Re=(t,e)=>Te("warn",t,e),ze=(t,e)=>Te("error",t,e);let De=null;function Ie(t,e){return De||(De=(async()=>{const n=function(t,e){if(e)return e.replace(/\/$/,"");const n=t?.getAttribute("data-8thwall-base");if(n)return n.replace(/\/$/,"");const o=document.documentElement.getAttribute("data-8thwall-base");return o?o.replace(/\/$/,""):"/cdn/8thwall"}(t,e);var o;Le("Loading 8th Wall engine",{baseUrl:n}),await(o=`${n}/xr.js`,new Promise((t,e)=>{if(document.querySelector(`script[src="${o}"]`))return void t();const n=document.createElement("script");n.src=o,n.async=!0,n.onload=()=>t(),n.onerror=()=>e(new Error(`[StorySplat] Failed to load 8th Wall script: ${o}`)),document.head.appendChild(n)}));const i=await function(t=15e3){return new Promise((e,n)=>{if(window.XR8)return void e(window.XR8);const o=setTimeout(()=>{window.removeEventListener("xrloaded",i),n(new Error(`[StorySplat] 8th Wall engine did not initialize within ${t}ms`))},t);function i(){clearTimeout(o),window.removeEventListener("xrloaded",i),window.XR8?e(window.XR8):n(new Error("[StorySplat] xrloaded event fired but XR8 global not found"))}window.addEventListener("xrloaded",i)})}();return Le("Loading 8th Wall SLAM module"),await i.loadChunk("slam"),i.XrController.configure({scale:"absolute"}),Le("8th Wall engine loaded",{version:i.version(),hasPlayCanvasWrapper:!!i.PlayCanvas?.run,hasXrController:!!i.XrController}),i})(),De.catch(t=>{ze("8th Wall engine load failed",t),De=null}),De)}function Fe(){return!!window.XR8}function Be(){return new Promise(t=>{navigator.xr?navigator.xr.isSessionSupported("immersive-ar").then(e=>t(e)).catch(()=>t(!1)):t(!1)})}class Ve{constructor(t){this.isPlaced=!1,this.reticle=null,this.reticleMat=null,this.reticleMesh=null,this.reticleOpacity=0,this.reticleVisibleUntil=0,this.currentScale=1,this.loggedFirstHit=!1,this.trackingStatus="",this.hitTestErrorCount=0,this.trackingGateWarned=!1,this.lastPinchDistance=0,this.lastRotationAngle=0,this.isTwoFingerGesture=!1,this.wasMultiTouch=!1,this.touchStartX=0,this.touchStartY=0,this.touchMoved=!1,this.lastTapTime=0,this.lastTouchHandledAt=0,this.doubleTapThresholdMs=300,this.tapMoveThresholdPx=12,this.reticleHoldMs=420,this.app=t.app,this.sceneRoot=t.sceneRoot,this.canvas=t.canvas,this.minScale=t.minScale??.1,this.maxScale=t.maxScale??5,this.tapMode=t.tapMode??"single",this.shouldIgnoreTap=t.shouldIgnoreTap,this.onPlaced=t.onPlaced,this.boundTouchStart=this.handleTouchStart.bind(this),this.boundTouchMove=this.handleTouchMove.bind(this),this.boundTouchEnd=this.handleTouchEnd.bind(this),this.boundClick=this.handleClick.bind(this),this.boundDblClick=this.handleDblClick.bind(this)}start(){this.isPlaced=!1,this.currentScale=1,this.loggedFirstHit=!1,this.trackingGateWarned=!1,this.hitTestErrorCount=0,this.reticleOpacity=0,this.reticleVisibleUntil=0,Le("AR placement controls started",{sceneRootEnabled:this.sceneRoot.enabled,sceneRootPosition:this.sceneRoot.getPosition(),tapMode:this.tapMode}),this.sceneRoot.enabled=!0,this.createReticle(),this.canvas.addEventListener("touchstart",this.boundTouchStart,{passive:!1}),this.canvas.addEventListener("touchmove",this.boundTouchMove,{passive:!1}),this.canvas.addEventListener("touchend",this.boundTouchEnd,{passive:!1}),this.canvas.addEventListener("click",this.boundClick),this.canvas.addEventListener("dblclick",this.boundDblClick),this.app.on("update",this.updateReticlePulse,this)}setTrackingStatus(t){this.trackingStatus=t}setTapMode(t){this.tapMode=t,this.lastTapTime=0}stop(){Le("AR placement controls stopped",{wasPlaced:this.isPlaced,sceneRootPosition:this.sceneRoot.getPosition(),sceneRootScale:this.sceneRoot.getLocalScale()}),this.canvas.removeEventListener("touchstart",this.boundTouchStart),this.canvas.removeEventListener("touchmove",this.boundTouchMove),this.canvas.removeEventListener("touchend",this.boundTouchEnd),this.canvas.removeEventListener("click",this.boundClick),this.canvas.removeEventListener("dblclick",this.boundDblClick),this.app.off("update",this.updateReticlePulse,this),this.reticle&&(this.reticle.destroy(),this.reticle=null),this.reticleMat?.destroy(),this.reticleMat=null,this.reticleMesh?.destroy(),this.reticleMesh=null}createReticle(){this.reticle=new t.Entity("ar-reticle");const e=new t.StandardMaterial;e.emissive=new t.Color(1,1,1),e.diffuse=new t.Color(0,0,0),e.useLighting=!1,e.opacity=1,e.blendType=t.BLEND_NORMAL,e.depthTest=!1,e.depthWrite=!1,e.cull=t.CULLFACE_NONE,e.update(),this.reticleMat=e;const n=new t.TorusGeometry({tubeRadius:.048,ringRadius:.193,segments:24,sides:10});this.reticleMesh=t.Mesh.fromGeometry(this.app.graphicsDevice,n);const o=new t.MeshInstance(this.reticleMesh,e);o.drawOrder=9999,this.reticle.addComponent("render",{meshInstances:[o],castShadows:!1}),this.reticle.setLocalScale(.72,.32,.72),this.reticle.enabled=!1,this.app.root.addChild(this.reticle)}updateReticlePulse(t){if(!this.reticle||!this.reticleMat)return;const e=performance.now()<this.reticleVisibleUntil?1:0,n=e>this.reticleOpacity?16:5;this.reticleOpacity+=(e-this.reticleOpacity)*Math.min(1,t*n),Math.abs(this.reticleOpacity-e)<.01&&(this.reticleOpacity=e),this.reticleOpacity>0?(this.reticleMat.opacity=.85*this.reticleOpacity,this.reticleMat.update(),this.reticle.enabled=!0):this.reticle.enabled=!1}getHitAtClientPoint(t,e){const n=window.XR8;if(!n)return null;if("NORMAL"!==this.trackingStatus)return this.trackingGateWarned||(this.trackingGateWarned=!0,Re("AR placement tap ignored until XR8 tracking is NORMAL",{status:this.trackingStatus})),null;const o=this.canvas.getBoundingClientRect();if(o.width<=0||o.height<=0)return null;const i=Math.max(0,Math.min(1,(t-o.left)/o.width)),s=Math.max(0,Math.min(1,(e-o.top)/o.height));try{const t=n.XrController.hitTest(i,s,["FEATURE_POINT","ESTIMATED_SURFACE"])[0]??null;return t&&!this.loggedFirstHit&&(this.loggedFirstHit=!0,Ae("AR placement hit test found first surface",{type:t.type,position:t.position})),t}catch(t){return this.hitTestErrorCount+=1,this.hitTestErrorCount<=3&&Re("XR8 hitTest threw; tap placement skipped",t),null}}shouldSkipTap(t,e){return"none"===this.tapMode||!!this.shouldIgnoreTap?.(t,e)}handleClick(t){"single"===this.tapMode&&(Date.now()-this.lastTouchHandledAt<500||this.placeAtClientPoint(t.clientX,t.clientY))}handleDblClick(t){"double"===this.tapMode&&(Date.now()-this.lastTouchHandledAt<500||this.placeAtClientPoint(t.clientX,t.clientY))}handleTouchStart(t){t.touches.length>1?(this.wasMultiTouch=!0,2===t.touches.length&&(this.isPlaced&&t.preventDefault(),this.isTwoFingerGesture=!0,this.lastPinchDistance=this.getTouchDistance(t.touches),this.lastRotationAngle=this.getTouchAngle(t.touches))):1===t.touches.length&&(this.touchStartX=t.touches[0].clientX,this.touchStartY=t.touches[0].clientY,this.touchMoved=!1)}handleTouchMove(t){if(1===t.touches.length&&!this.isTwoFingerGesture){const e=t.touches[0].clientX-this.touchStartX,n=t.touches[0].clientY-this.touchStartY;return void(e*e+n*n>this.tapMoveThresholdPx*this.tapMoveThresholdPx&&(this.touchMoved=!0))}if(!this.isPlaced||2!==t.touches.length||!this.isTwoFingerGesture)return;t.preventDefault();const e=this.getTouchDistance(t.touches);if(this.lastPinchDistance>0){const t=e/this.lastPinchDistance;this.currentScale=Math.max(this.minScale,Math.min(this.maxScale,this.currentScale*t)),this.sceneRoot.setLocalScale(this.currentScale,this.currentScale,this.currentScale)}this.lastPinchDistance=e;const n=this.getTouchAngle(t.touches),o=n-this.lastRotationAngle;Math.abs(o)<Math.PI&&this.sceneRoot.rotateLocal(0,-o*(180/Math.PI),0),this.lastRotationAngle=n}handleTouchEnd(t){if(t.touches.length<2&&(this.isTwoFingerGesture=!1,this.lastPinchDistance=0),this.wasMultiTouch)return 0===t.touches.length&&(this.wasMultiTouch=!1),void(this.lastTapTime=0);if(1!==t.changedTouches.length||this.touchMoved)return;const e=t.changedTouches[0];if(!this.shouldSkipTap(e.clientX,e.clientY)){if("single"===this.tapMode)return t.preventDefault(),this.lastTouchHandledAt=Date.now(),void this.placeAtClientPoint(e.clientX,e.clientY);if("double"===this.tapMode){const n=Date.now();n-this.lastTapTime<this.doubleTapThresholdMs?(t.preventDefault(),this.lastTouchHandledAt=n,this.lastTapTime=0,this.placeAtClientPoint(e.clientX,e.clientY)):this.lastTapTime=n}}}placeAtClientPoint(e,n){if(this.shouldSkipTap(e,n))return;const o=this.getHitAtClientPoint(e,n);if(!o)return;this.isPlaced=!0;const i=new t.Vec3(o.position.x,o.position.y,o.position.z);this.sceneRoot.setPosition(i.x,i.y,i.z),this.sceneRoot.setLocalScale(this.currentScale,this.currentScale,this.currentScale),this.sceneRoot.enabled=!0,this.showReticleAtHit(o),this.onPlaced?.(i.clone()),Le("AR scene placed/moved",{position:{x:i.x,y:i.y,z:i.z},scale:this.currentScale,tapMode:this.tapMode})}showReticleAtHit(t){this.reticle&&this.reticleMat&&(this.reticle.setPosition(t.position.x,t.position.y,t.position.z),t.rotation?this.reticle.setRotation(t.rotation.x,t.rotation.y,t.rotation.z,t.rotation.w):this.reticle.setEulerAngles(0,0,0),this.reticleOpacity=1,this.reticleVisibleUntil=performance.now()+this.reticleHoldMs,this.reticleMat.opacity=.85,this.reticleMat.update(),this.reticle.enabled=!0)}getTouchDistance(t){const e=t[0].clientX-t[1].clientX,n=t[0].clientY-t[1].clientY;return Math.sqrt(e*e+n*n)}getTouchAngle(t){return Math.atan2(t[1].clientY-t[0].clientY,t[1].clientX-t[0].clientX)}}const $e=t=>({x:t.x,y:t.y,z:t.z}),Ue=(t,e)=>({x:t.x+e.x,y:t.y+e.y,z:t.z+e.z}),Oe=(t,e)=>({x:t.x-e.x,y:t.y-e.y,z:t.z-e.z}),Ne=(t,e)=>({x:t.x*e,y:t.y*e,z:t.z*e});function We(t){return!0===t||"loop"===t}function He(t,e){return t.length<2?0:We(e)?t.length:t.length-1}function Ge(t,e,n){const o=t.length,i=He(t,n);if(o<2||e<0||e>=i)return null;const s=We(n),a=t=>(t%o+o)%o,r=s?a(e):e,l=s?a(e+1):e+1;return{prevIndex:s?a(e-1):Math.max(e-1,0),startIndex:r,endIndex:l,nextIndex:s?a(e+2):Math.min(e+2,o-1)}}function je(t,e,n){const o=Ge(t,e,n);if(!o)return null;const i=t[o.prevIndex].position,s=t[o.startIndex].position,a=t[o.endIndex].position,r=t[o.nextIndex].position;return{handleOut:Ne(Oe(a,i),1/6),handleInNext:Ne(Oe(s,r),1/6)}}function Xe(t,e,n){const o=Ge(t,e,n);return!!o&&"bezier"===t[o.startIndex].curveOut?.mode}function qe(t,e,n){const o=Ge(t,e,n);if(!o)return null;const i=je(t,e,n);if(!i)return null;const s=t[o.startIndex].curveOut;return"bezier"!==s?.mode?i:{handleOut:s.handleOut?$e(s.handleOut):i.handleOut,handleInNext:s.handleInNext?$e(s.handleInNext):i.handleInNext}}function Ye(t,e,n,o,i){const s=i*i,a=s*i;return{x:.5*(2*e.x+(-t.x+n.x)*i+(2*t.x-5*e.x+4*n.x-o.x)*s+(-t.x+3*e.x-3*n.x+o.x)*a),y:.5*(2*e.y+(-t.y+n.y)*i+(2*t.y-5*e.y+4*n.y-o.y)*s+(-t.y+3*e.y-3*n.y+o.y)*a),z:.5*(2*e.z+(-t.z+n.z)*i+(2*t.z-5*e.z+4*n.z-o.z)*s+(-t.z+3*e.z-3*n.z+o.z)*a)}}function Ze(t,e,n,o,i){const s=1-i,a=s*s,r=a*s,l=i*i,c=l*i;return{x:r*t.x+3*a*i*e.x+3*s*l*n.x+c*o.x,y:r*t.y+3*a*i*e.y+3*s*l*n.y+c*o.y,z:r*t.z+3*a*i*e.z+3*s*l*n.z+c*o.z}}function Ke(t,e,n,o){const i=Ge(t,e,o);if(!i)return null;const s=t[i.prevIndex].position,a=t[i.startIndex].position,r=t[i.endIndex].position,l=t[i.nextIndex].position;if(!Xe(t,e,o))return Ye(s,a,r,l,n);const c=qe(t,e,o);return c?Ze(a,Ue(a,c.handleOut),Ue(r,c.handleInNext),r,n):null}function Qe(t,e,n){if(t.length<2)return t.map((t,e)=>({...$e(t.position),segmentIndex:e,t:0}));const o=[],i=He(t,n),s=Math.max(1,e);for(let e=0;e<i;e++)for(let i=0;i<s;i++){const a=i/s,r=Ke(t,e,a,n);r&&o.push({...r,segmentIndex:e,t:a})}const a=We(n)?0:t.length-1;return o.push({...$e(t[a].position),segmentIndex:Math.max(0,i-1),t:1}),o}class Je extends e{static scriptName="xrControllers";basePath="https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets/dist/profiles";controllers=new Map;_pendingInputSources=new Set;_visible=!0;_handlers=null;initialize(){this.app.xr?(this._handlers={onAdd:this._onInputSourceAdd.bind(this),onRemove:this._onInputSourceRemove.bind(this),onXrEnd:this._onXrEnd.bind(this)},this.app.xr.input.on("add",this._handlers.onAdd),this.app.xr.input.on("remove",this._handlers.onRemove),this.app.xr.on("end",this._handlers.onXrEnd),this.once("destroy",()=>{this._onDestroy()})):console.error("XrControllers script requires XR to be enabled on the application")}_onDestroy(){this._handlers&&this.app.xr&&(this.app.xr.input.off("add",this._handlers.onAdd),this.app.xr.input.off("remove",this._handlers.onRemove),this.app.xr.off("end",this._handlers.onXrEnd)),this._destroyAllControllers(),this._handlers=null,this._pendingInputSources.clear()}_onXrEnd(){this._destroyAllControllers(),this._pendingInputSources.clear()}_destroyController(t){const e=this.controllers.get(t);e&&(e.entity.destroy(),e.asset&&(this.app.assets.remove(e.asset),e.asset.unload()),this.controllers.delete(t),this.app.fire("xr:controller:remove",t))}_destroyAllControllers(){for(const t of this.controllers.keys())this._destroyController(t)}async _tryLoadProfiles(t,e,n=0){if(n>=e.length)return null;if(!this._pendingInputSources.has(t))return null;const o=await this._loadProfile(t,e[n]);return o||this._tryLoadProfiles(t,e,n+1)}async _onInputSourceAdd(t){if(!t.profiles?.length)return void console.warn("XrControllers: No profiles available for input source");this._pendingInputSources.add(t);const e=await this._tryLoadProfiles(t,t.profiles);if(this._pendingInputSources.has(t))if(this._pendingInputSources.delete(t),e){const{asset:n}=e,o=n.resource.instantiateRenderEntity();this.app.root.addChild(o),o.enabled=this._visible;const i=new Map;if(t.hand)for(const e of t.hand.joints){const t=o.findByName(e.id);t&&i.set(e,t)}this.controllers.set(t,{entity:o,jointMap:i,asset:n}),this.app.fire("xr:controller:add",t,o)}else console.warn("XrControllers: No compatible profiles found for input source");else e?.asset&&(this.app.assets.remove(e.asset),e.asset.unload())}async _loadProfile(t,e){const n=`${this.basePath}/${e}/profile.json`;try{const o=await fetch(n);if(!o.ok)return null;const i=await o.json(),s=i.layouts[t.handedness]?.assetPath||"",a=`${this.basePath}/${i.profileId}/${t.handedness}${s.replace(/^\/?(left|right)/,"")}`,r=await new Promise((t,e)=>{this.app.assets.loadFromUrl(a,"container",(n,o)=>{n?e(n):t(o)})});return{profileId:e,asset:r}}catch(t){return null}}_onInputSourceRemove(t){this._pendingInputSources.delete(t),this._destroyController(t)}set visible(t){if(this._visible!==t){this._visible=t;for(const[,e]of this.controllers)e.entity.enabled=t}}get visible(){return this._visible}update(t){if(this.app.xr?.active&&this._visible)for(const[t,{entity:e,jointMap:n}]of this.controllers)if(t.hand)for(const[t,e]of n)e.setPosition(t.getPosition()),e.setRotation(t.getRotation());else{const n=t.getPosition(),o=t.getRotation();n&&e.setPosition(n),o&&e.setRotation(o)}}}class tn extends e{static scriptName="xrNavigation";enableTeleport=!0;enableMove=!0;movementSpeed=1.5;rotateSpeed=45;movementThreshold=.1;rotateThreshold=.5;rotateResetThreshold=.25;maxTeleportDistance=10;teleportIndicatorRadius=.2;teleportIndicatorSegments=16;validTeleportColor=new n(0,1,0);invalidTeleportColor=new n(1,0,0);controllerRayColor=new n(1,1,1);enableSnapVertical=!0;snapVerticalHeight=.5;snapVerticalBoostHeight=2;snapVerticalThreshold=.5;snapVerticalResetThreshold=.25;inputSources=new Set;activePointers=new Map;inputHandlers=new Map;lastRotateValue=0;lastVerticalValue=0;tmpVec2A=new o;tmpVec2B=new o;tmpVec3A=new i;tmpVec3B=new i;validColor=new n;invalidColor=new n;rayColor=new n;cameraEntity=null;initialize(){if(!this.app.xr)return void console.error("XrNavigation script requires XR to be enabled on the application");const t=[];this.enableTeleport&&t.push("teleportation"),this.enableMove&&t.push("smooth movement"),this.enableSnapVertical&&t.push("snap vertical"),console.log(`XrNavigation: Enabled methods - ${t.join(", ")}`),this.enableTeleport||this.enableMove||this.enableSnapVertical||console.warn("XrNavigation: All navigation methods are disabled. Navigation will not work."),this.validColor.copy(this.validTeleportColor),this.invalidColor.copy(this.invalidTeleportColor),this.rayColor.copy(this.controllerRayColor);const e=this.entity.findComponent("camera");if(this.cameraEntity=e?e.entity:null,!this.cameraEntity){console.warn("XrNavigation: Camera entity not found. Looking for camera in children...");const t=this.entity.findByName("camera");if(this.cameraEntity=t,!this.cameraEntity)for(const t of this.entity.children){const e=t;if(e.camera){this.cameraEntity=e;break}}this.cameraEntity||console.error("XrNavigation: No camera entity found. Movement calculations may not work correctly.")}this.app.xr.input.on("add",t=>{const e=()=>{this.activePointers.set(t,!0)},n=()=>{this.activePointers.set(t,!1),this.tryTeleport(t)};t.on("selectstart",e),t.on("selectend",n),this.inputHandlers.set(t,{handleSelectStart:e,handleSelectEnd:n}),this.inputSources.add(t)}),this.app.xr.input.on("remove",t=>{const e=this.inputHandlers.get(t);e&&(t.off("selectstart",e.handleSelectStart),t.off("selectend",e.handleSelectEnd),this.inputHandlers.delete(t)),this.activePointers.delete(t),this.inputSources.delete(t)})}findPlaneIntersection(t,e){if(Math.abs(e.y)<1e-5)return null;const n=-t.y/e.y;return n<0?null:new i(t.x+e.x*n,0,t.z+e.z*n)}tryTeleport(t){const e=t.getOrigin(),n=t.getDirection(),o=this.findPlaneIntersection(e,n);if(o){if(this.cameraEntity){const t=this.cameraEntity.getLocalPosition();o.x-=t.x,o.z-=t.z}const t=this.entity.getPosition().y;o.y=t,this.entity.setPosition(o)}}update(t){this.enableMove&&this.handleSmoothLocomotion(t),this.enableSnapVertical&&this.handleSnapVertical(),this.enableTeleport&&this.handleTeleportation(),this.renderControllerRays()}handleSmoothLocomotion(t){if(this.cameraEntity)for(const e of this.inputSources)if(e.gamepad)if("left"===e.handedness){if(this.tmpVec2A.set(e.gamepad.axes[2],e.gamepad.axes[3]),this.tmpVec2A.length()>this.movementThreshold){this.tmpVec2A.normalize();const e=this.cameraEntity.forward;this.tmpVec2B.x=e.x,this.tmpVec2B.y=e.z,this.tmpVec2B.normalize();const n=Math.atan2(this.tmpVec2B.x,this.tmpVec2B.y)-Math.PI/2,o=this.tmpVec2A.x*Math.sin(n)-this.tmpVec2A.y*Math.cos(n);this.tmpVec2A.y=this.tmpVec2A.y*Math.sin(n)+this.tmpVec2A.x*Math.cos(n),this.tmpVec2A.x=o,this.tmpVec2A.mulScalar(this.movementSpeed*t),this.entity.translate(this.tmpVec2A.x,0,this.tmpVec2A.y)}}else"right"===e.handedness&&this.handleSnapTurning(e)}handleSnapTurning(t){const e=-t.gamepad.axes[2];(this.lastRotateValue>0&&e<this.rotateResetThreshold||this.lastRotateValue<0&&e>-this.rotateResetThreshold)&&(this.lastRotateValue=0),0===this.lastRotateValue&&Math.abs(e)>this.rotateThreshold&&(this.lastRotateValue=Math.sign(e),this.cameraEntity&&(this.tmpVec3A.copy(this.cameraEntity.getLocalPosition()),this.entity.translateLocal(this.tmpVec3A),this.entity.rotateLocal(0,Math.sign(e)*this.rotateSpeed,0),this.entity.translateLocal(this.tmpVec3A.mulScalar(-1))))}handleSnapVertical(){let t=null;for(const e of this.inputSources)if(e.gamepad&&"right"===e.handedness){t=e;break}if(!t||!t.gamepad)return;const e=-t.gamepad.axes[3];if((this.lastVerticalValue>0&&e<this.snapVerticalResetThreshold||this.lastVerticalValue<0&&e>-this.snapVerticalResetThreshold)&&(this.lastVerticalValue=0),0===this.lastVerticalValue&&Math.abs(e)>this.snapVerticalThreshold){this.lastVerticalValue=Math.sign(e);const n=t.gamepad.buttons[1]?.pressed,o=n?this.snapVerticalBoostHeight:this.snapVerticalHeight;this.entity.translate(0,Math.sign(e)*o,0)}}handleTeleportation(){for(const t of this.inputSources){if(!this.activePointers.get(t))continue;const e=t.getOrigin(),n=t.getDirection(),o=this.findPlaneIntersection(e,n);o&&this.isValidTeleportDistance(o)?(this.app.drawLine(e,o,this.validColor),this.drawTeleportIndicator(o)):(this.tmpVec3B.copy(n).mulScalar(this.maxTeleportDistance).add(e),this.app.drawLine(e,this.tmpVec3B,this.invalidColor))}}renderControllerRays(){if(this.enableMove)for(const t of this.inputSources){if(this.activePointers.get(t))continue;const e=t.getOrigin();this.tmpVec3B.copy(t.getDirection()).mulScalar(2).add(e),this.app.drawLine(e,this.tmpVec3B,this.rayColor)}}isValidTeleportDistance(t){return t.distance(this.entity.getPosition())<=this.maxTeleportDistance}drawTeleportIndicator(t){const e=this.teleportIndicatorSegments,n=this.teleportIndicatorRadius;for(let o=0;o<e;o++){const i=o/e*Math.PI*2,s=(o+1)/e*Math.PI*2,a=t.x+Math.cos(i)*n,r=t.z+Math.sin(i)*n,l=t.x+Math.cos(s)*n,c=t.z+Math.sin(s)*n;this.tmpVec3A.set(a,.01,r),this.tmpVec3B.set(l,.01,c),this.app.drawLine(this.tmpVec3A,this.tmpVec3B,this.validColor)}}}const en={auto:t.GSPLAT_RENDERER_AUTO??0,"raster-cpu":t.GSPLAT_RENDERER_RASTER_CPU_SORT??1,"raster-gpu":t.GSPLAT_RENDERER_RASTER_GPU_SORT??2,compute:t.GSPLAT_RENDERER_COMPUTE??3},nn=Object.entries(en).reduce((t,[e,n])=>(t[String(n)]=e,t),{});function on(t,e,n){const o=t.scene.gsplat,i=o?.currentRenderer,s=nn[String(i)]??`unknown(${i})`,a=t.graphicsDevice,r=!0===a?.isWebGPU,l=r?"webgpu":a?.isWebGL2?"webgl2":a?.isWebGL1?"webgl1":"unknown",c=!!n&&"auto"!==n&&n!==s;console.log(`[SPLAT][RENDERER] ${e}`,{requested:n??"(none — engine default)",active:s,activeRaw:i,fellBack:c,device:l,webgpu:r,gpu:a?.deviceName??a?.gpuAdapter?.name??"unknown",mobile:"undefined"!=typeof navigator&&/Mobi|Android|iPhone|iPad/.test(navigator.userAgent)})}function sn(t){if(t)return{mode:t.mode,handleOut:t.handleOut?{x:t.handleOut.x,y:t.handleOut.y,z:-t.handleOut.z}:void 0,handleInNext:t.handleInNext?{x:t.handleInNext.x,y:t.handleInNext.y,z:-t.handleInNext.z}:void 0}}function an(t,e){return t.map((t,n)=>({position:t,curveOut:sn(e?.[n]?.curveOut)}))}class rn{constructor(){this.listeners=new Map,this.internalListeners=new Map}on(t,e){this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(e)}onInternal(t,e){this.internalListeners.has(t)||this.internalListeners.set(t,new Set),this.internalListeners.get(t).add(e)}off(t,e){this.listeners.get(t)?.delete(e),this.internalListeners.get(t)?.delete(e)}emit(t,...e){this.listeners.get(t)?.forEach(t=>t(...e)),this.internalListeners.get(t)?.forEach(t=>t(...e))}listenerCount(t){return this.listeners.get(t)?.size||0}}function ln(){const t=navigator.userAgent||navigator.vendor||window.opera||"";return/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(t)}const cn={"desktop-max":{range:[0,5],lodBaseDistance:15,lodMultiplier:2},desktop:{range:[0,2],lodBaseDistance:15,lodMultiplier:2},"mobile-max":{range:[1,2],lodBaseDistance:15,lodMultiplier:2},mobile:{range:[2,5],lodBaseDistance:15,lodMultiplier:2}};function dn(t){const e=navigator.deviceMemory,n=navigator.hardwareConcurrency||4,o=window.devicePixelRatio||1;let i;if(t){i=null!=e&&e>=6&&n>=6||null==e&&n>=6&&o>=3?"mobile-max":"mobile"}else{i=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}function pn(t){const e=t.includes("lod-meta.json");return e&&console.log("[SPLAT] Detected LOD streaming format (lod-meta.json)"),e}function hn(e,n,o={}){if(console.log("[StorySplat Viewer] BUILD Apr 27, 22:53:11"),o.lazyLoad??n.uiOptions?.lazyLoad){let dl,pl=null;const hl=[],ul=o.lazyLoadThumbnail||n.uiOptions?.lazyLoadThumbnailUrl||n.thumbnailUrl,ml=o.lazyLoadButtonText||n.uiOptions?.lazyLoadButtonText||x(n.uiOptions?.buttonLabels,"startExperience"),gl=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;M(s,"minimal",void 0,t.id),t.classList.add("storysplat-viewer-container");const r=document.createElement("div");r.className="storysplat-lazy-load-container";const l=(n?j(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:ul,thumbnailType:o.lazyLoadThumbnailType||n.uiOptions?.lazyLoadThumbnailType,buttonText:ml,uiColor:gl,onStart:()=>{console.log("[StorySplat Viewer] User clicked start, initializing viewer..."),pl=hn(e,n,{...o,lazyLoad:!1}),dl&&(pl.setButtonLabels(dl),dl=void 0);for(const{event:t,callback:e,kind:n}of hl)"onInternal"===n&&pl.onInternal?pl.onInternal(t,e):pl.on(t,e);hl.length=0}});return{app:null,canvas:null,goToWaypoint:t=>pl?.goToWaypoint(t),nextWaypoint:()=>pl?.nextWaypoint(),prevWaypoint:()=>pl?.prevWaypoint(),getCurrentWaypointIndex:()=>pl?.getCurrentWaypointIndex()??0,getWaypointCount:()=>pl?.getWaypointCount()??0,setPosition:(t,e,n)=>pl?.setPosition(t,e,n),setRotation:(t,e,n)=>pl?.setRotation(t,e,n),getPosition:()=>pl?.getPosition()??{x:0,y:0,z:0},getRotation:()=>pl?.getRotation()??{x:0,y:0,z:0},capturePhoto:()=>pl?pl.capturePhoto():Promise.reject(new Error("[StorySplat Viewer] Viewer has not started yet.")),shareCurrentView:()=>pl?pl.shareCurrentView():Promise.reject(new Error("[StorySplat Viewer] Viewer has not started yet.")),play:()=>pl?.play(),pause:()=>pl?.pause(),stop:()=>pl?.stop(),isPlaying:()=>pl?.isPlaying()??!1,isFrameSequencePlaying:()=>pl?.isFrameSequencePlaying()??!1,setCameraMode:t=>pl?.setCameraMode(t),getCameraMode:()=>pl?.getCameraMode()??"tour",resumeTourAtCurrentCamera:()=>pl?.resumeTourAtCurrentCamera(),setExploreMode:t=>pl?.setExploreMode(t),setExploreModeSettings:t=>{pl?pl.setExploreModeSettings(t):(void 0!==t.enabledExploreModes&&(n.enabledExploreModes=t.enabledExploreModes??void 0),void 0!==t.initialSplatExploreMode&&(n.initialSplatExploreMode=t.initialSplatExploreMode??void 0))},goToOriginalSplat:()=>pl?.goToOriginalSplat(),goToSplat:async t=>pl?.goToSplat(t),getCurrentSplatUrl:()=>pl?.getCurrentSplatUrl()??"",isShowingOriginalSplat:()=>pl?.isShowingOriginalSplat()??!0,getAdditionalSplats:()=>pl?.getAdditionalSplats()??[],isCompareMode:()=>pl?.isCompareMode()??!1,getComparePosition:()=>pl?.getComparePosition()??.5,setComparePosition:t=>pl?.setComparePosition(t),setProgress:(t,e)=>pl?.setProgress(t,e),getProgress:()=>pl?.getProgress()??0,muteAll:()=>pl?.muteAll(),unmuteAll:()=>pl?.unmuteAll(),isMuted:()=>pl?.isMuted()??!1,getHotspots:()=>pl?.getHotspots()??[],triggerHotspot:t=>pl?.triggerHotspot(t),closeHotspot:()=>pl?.closeHotspot(),destroy:()=>{pl?pl.destroy():(e.querySelectorAll(".storysplat-lazy-load-container, .storysplat-viewer-container").forEach(t=>t.remove()),e.classList.remove("storysplat-viewer-container"))},resize:()=>pl?.resize(),navigateToScene:async t=>{if(pl)return pl.navigateToScene(t)},setButtonLabels:t=>{pl?pl.setButtonLabels(t):dl={...dl,...t}},setPanoramaMode:t=>pl?.setPanoramaMode(t),isPanoramaMode:()=>pl?.isPanoramaMode()??!1,on:(t,e)=>{pl?pl.on(t,e):hl.push({event:t,callback:e,kind:"on"})},off:(t,e)=>{if(pl)pl.off(t,e);else{const n=hl.findIndex(n=>n.event===t&&n.callback===e);-1!==n&&hl.splice(n,1)}},onInternal:(t,e)=>{pl&&pl.onInternal?pl.onInternal(t,e):hl.push({event:t,callback:e,kind:"onInternal"})}}}const i=new rn;let s=o.__sessionRecorderHandle??null;if(!o.allowParentStyles){const fl=e.style.width,yl=e.style.height;e.style.all="initial",e.style.position="relative",e.style.display="block",e.style.width=fl||"100%",e.style.height=yl||"100%",e.style.overflow="hidden",e.style.fontFamily="system-ui, -apple-system, sans-serif"}const a=!0===o.editor;a&&e.setAttribute("data-storysplat-editor","true"),i.on("error",t=>{console.error("[StorySplat Viewer] Error event:",t.message),a||function(t,e){if(t.hasAttribute("data-storysplat-editor"))return;t.querySelector(".storysplat-error-popup")?.remove();const n=t.querySelector(".storysplat-preloader");if(n&&P(n),e.includes("Failed to load splat from any URL")){const e=document.createElement("div");e.className="storysplat-outdated-banner",e.style.cssText='position:absolute;top:0;left:0;right:0;z-index:10000;display:flex;align-items:center;justify-content:center;gap:12px;padding:10px 20px;background:rgba(204,88,51,0.9);color:white;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:500;backdrop-filter:blur(8px);';const n=document.createElement("span");n.textContent="⚠️ This scene was created with an older version of StorySplat. Some features may not be available.",e.appendChild(n);const o=document.createElement("button");return o.textContent="Dismiss",o.style.cssText="background:rgba(255,255,255,0.2);border:1px solid rgba(255,255,255,0.3);color:white;padding:4px 12px;border-radius:4px;cursor:pointer;font-size:12px;font-weight:600;white-space:nowrap;",o.addEventListener("click",()=>e.remove()),e.appendChild(o),setTimeout(()=>{e.parentNode&&e.remove()},8e3),void t.appendChild(e)}const o=document.createElement("div");o.className="storysplat-error-popup";const i=document.createElement("div");i.className="storysplat-error-popup-icon",i.textContent="❌",o.appendChild(i);const s=document.createElement("h3");s.className="storysplat-error-popup-title",s.textContent="Failed to Load Scene",o.appendChild(s);const a=document.createElement("p");a.className="storysplat-error-popup-message",a.textContent=e||"An error occurred while loading this scene. Please try refreshing the page.",o.appendChild(a),t.appendChild(o)}(e,t.message)});const r=v(y(n));console.log("[StorySplat Viewer] Creating viewer with config:",r),console.log("[StorySplat Viewer] Scale config:",r.scale),console.log("[StorySplat Viewer] Raw scene data:",{splatScale:n.splatScale,scale:n.scale});let l=null;const c=a?!o.editorSkipUI&&!1!==o.showUI:!1!==o.showUI,d=r.uiColor||"#CC5833",p=r.uiOptions||{},h=o.template||p.uiType||"minimal";let u=p.buttonLabels;const m=t=>"first-person"===t?"tour":"drone"===t?"explore":t,g=r.collisionMeshesData&&r.collisionMeshesData.length>0||!!r.voxelCollisionUrl,f=Array.isArray(r.allowedCameraModes);let w=(r.allowedCameraModes||["orbit","first-person","drone"]).map(m).filter((t,e,n)=>n.indexOf(t)===e);f||!g||w.includes("walk")||w.push("walk"),!g&&w.includes("walk")&&(w=w.filter(t=>"walk"!==t));let S=m(r.defaultCameraMode||"orbit");w.includes(S)||(S=w[0]||"tour");const _=t=>{const e=(t?.length?t:["orbit","fly"]).filter(t=>"orbit"===t||"fly"===t).filter((t,e,n)=>n.indexOf(t)===e);return e.length>0?e:["orbit","fly"]};let C=_(r.enabledExploreModes);const E=t=>C.includes(t),L=t=>t&&E(t)?t:C.includes("fly")?"fly":C[0],R=!(!r.narrationTrack?.enabled||!r.narrationTrack?.audioUrl),z=!!(R||r.audioEmitters?.length||r.hotspots?.some(t=>t.audioUrl)||r.hotspots?.some(t=>"video"===t.type&&!0!==t.videoMuted)||r.waypoints?.some(t=>t.interactions?.some(t=>"audio"===t.type)));let D={};c&&(D=T(e,r,{uiColor:d,showScrollControls:!p.hideNavigator&&r.waypoints&&r.waypoints.length>0,showModeToggle:w.length>1,showFullscreenButton:!p.hideFullscreenButton,showHelpButton:!p.hideHelpButton&&!p.hideInfoButton,showMuteButton:!p.hideMuteButton&&z,showPreloader:!0,allowedCameraModes:w,enabledExploreModes:C,defaultCameraMode:S,buttonLabels:u,customPreloaderLogoUrl:p.customPreloaderLogoUrl,preloaderLogoSize:p.preloaderLogoSize,hideWatermark:p.hideWatermark,watermarkText:p.watermarkText,watermarkLink:p.watermarkLink,watermarkImageUrl:p.watermarkImageUrl,watermarkImageHeight:p.watermarkImageHeight,sceneId:n.sceneId,showWaypointList:p.showWaypointList,template:h,debugMode:p.debugMode,hideProgressText:p.hideProgressText,viewerTheme:p.viewerTheme,showRelightingToggle:!!(!1!==r.splatRelighting?.enabled&&r.splatRelighting?.allowViewerToggle&&r.lights&&r.lights.length>0),showFisheyeButton:!1,showSceneMenu:!!(r.portals&&r.portals.length>0||r.uiOptions?.sceneMenuLinks&&r.uiOptions.sceneMenuLinks.length>0),sceneMenuLinks:r.uiOptions?.sceneMenuLinks,measurementsEnabled:r.uiOptions?.measurementsEnabled,sceneScale:r.uiOptions?.sceneScale,sceneScaleUnit:r.uiOptions?.sceneScaleUnit,measurementColor:r.uiOptions?.measurementColor,showShareButton:!p.hideShareButton}),W(D));const I=document.createElement("canvas");let U;I.id="storysplat-viewer-canvas",I.style.width="100%",I.style.height="100%",I.style.display="block",e.appendChild(I);const X={antialias:!1,alpha:!1,powerPreference:"high-performance"};try{U=new t.Application(I,{graphicsDeviceOptions:X,mouse:new t.Mouse(I),touch:new t.TouchDevice(I),keyboard:new t.Keyboard(window)}),console.log("[StorySplat Viewer] Graphics initialized successfully")}catch(vl){console.warn("[StorySplat Viewer] WebGL2 initialization failed, trying WebGL1 fallback:",vl);try{U=new t.Application(I,{graphicsDeviceOptions:{...X,preferWebGl2:!1},mouse:new t.Mouse(I),touch:new t.TouchDevice(I),keyboard:new t.Keyboard(window)}),console.log("[StorySplat Viewer] WebGL1 fallback successful")}catch(bl){console.error("[StorySplat Viewer] WebGL initialization failed completely:",bl);const xl=document.createElement("div");xl.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 wl=document.createElement("h3");wl.style.cssText="margin:0 0 10px 0;",wl.textContent=x(u,"errorWebGLTitle");const Sl=document.createElement("p");throw Sl.style.cssText="margin:0;",Sl.textContent=x(u,"errorWebGLMessage"),xl.appendChild(wl),xl.appendChild(Sl),e.appendChild(xl),new Error("WebGL initialization failed - browser may not support WebGL")}}I.addEventListener("webglcontextlost",t=>{t.preventDefault(),console.error("[StorySplat Viewer] WebGL context lost"),i.emit("error",new Error("WebGL context lost"))},!1),I.addEventListener("webglcontextrestored",()=>{console.log("[StorySplat Viewer] WebGL context restored")},!1),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 _l=r.skybox?.url||r.skyboxUrl;if(_l){const Cl=new Image;Cl.crossOrigin="anonymous",Cl.src=_l,console.log("[StorySplat Viewer] Preloading skybox image in parallel with splat:",_l)}}catch(Ml){console.warn("[StorySplat Viewer] Skybox preload failed (non-fatal):",Ml)}try{const El=U.scene.layers.getLayerByName("World"),Pl=El&&Object.getPrototypeOf(El);if(Pl&&!Pl._splitLightsPatched){const kl=Object.getOwnPropertyDescriptor(Pl,"splitLights");if(kl?.get){const Tl=kl.get;Object.defineProperty(Pl,"splitLights",{get(){return this._splitLightsDirty&&(this._lights=this._lights.filter(t=>void 0!==t._type&&t._type>=0&&t._type<=2)),Tl.call(this)},configurable:!0}),Pl._splitLightsPatched=!0}}}catch(Al){console.warn("[StorySplat Viewer] Could not patch Layer.splitLights:",Al)}const q=ln(),Y=r.lodSettings;let Z,K,Q,J;if(Y&&"auto"!==Y.preset)if("custom"===Y.preset){K=[Y.lodRangeMin??0,Y.lodRangeMax??5];let Ll=15,Rl=2;Y.lodDistances&&!Y.lodBaseDistance&&(Ll=Y.lodDistances[0]||15,Y.lodDistances[1]&&Y.lodDistances[0]&&(Rl=Y.lodDistances[1]/Y.lodDistances[0])),Q=Y.lodBaseDistance??Ll,J=Y.lodMultiplier??Rl,Z="desktop",console.log("[SPLAT] Using custom LOD settings from scene")}else{Z=Y.preset;const zl=cn[Z];K=[...zl.range],Q=zl.lodBaseDistance,J=zl.lodMultiplier,console.log("[SPLAT] Using scene-configured LOD preset:",Z)}else{q&&Y?.mobilePreset?(Z=Y.mobilePreset,console.log("[SPLAT] Using scene mobile override preset:",Z)):!q&&Y?.desktopPreset?(Z=Y.desktopPreset,console.log("[SPLAT] Using scene desktop override preset:",Z)):Z=dn(q);const Dl=cn[Z];K=[...Dl.range],Q=Dl.lodBaseDistance,J=Dl.lodMultiplier}if(console.log("[SPLAT] Initializing LOD system for device:",q?"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=K[0],U.scene.gsplat.lodRangeMax=K[1],U.scene.gsplat.colorUpdateDistance=1,U.scene.gsplat.colorUpdateAngle=4,U.scene.gsplat.colorUpdateDistanceLodScale=2,U.scene.gsplat.colorUpdateAngleLodScale=2;const Il=Y?.splatBudget??0;Il>0&&(U.scene.gsplat.splatBudget=Il,console.log("[SPLAT] Global splat budget set:",Il));const Fl=Y?.renderer;Fl&&Fl in en&&(U.scene.gsplat.renderer=en[Fl]),on(U,"init",Fl),console.log("[SPLAT] LOD system configured:",{preset:Y?.preset??"auto",lodRangeMin:K[0],lodRangeMax:K[1],lodBaseDistance:Q,lodMultiplier:J,splatBudget:Il,isMobile:q})}else console.warn("[SPLAT] GSplat scene settings not available - LOD may not work optimally");let et=0,nt=!1,ot=null,it=null,st=null,ut=null;const mt=new Set;let gt=!1,ft=!1,vt=!1;const bt=t=>new Promise((e,n)=>{try{t.toBlob(t=>{t?e(t):n(new Error("[StorySplat Viewer] Canvas capture returned no image data."))},"image/jpeg",.92)}catch(t){n(t)}}),_t=async()=>{if(vt)throw new Error("[StorySplat Viewer] Cannot capture after viewer is destroyed.");const t=U.graphicsDevice;if(!t.isWebGPU&&"function"==typeof t.readPixels)try{return await new Promise((e,n)=>{const o=()=>{window.clearTimeout(i);try{const o=Math.max(1,Math.floor(t.width||I.width)),i=Math.max(1,Math.floor(t.height||I.height)),s=new Uint8Array(o*i*4);t.readPixels(0,0,o,i,s),((t,e,n)=>{const o=document.createElement("canvas");o.width=e,o.height=n;const i=o.getContext("2d");if(!i)throw new Error("[StorySplat Viewer] Could not create capture canvas.");const s=i.createImageData(e,n),a=4*e;for(let e=0;e<n;e++){const o=(n-1-e)*a,i=e*a;s.data.set(t.subarray(o,o+a),i)}return i.putImageData(s,0,0),bt(o)})(s,o,i).then(e,n)}catch(t){n(t)}},i=window.setTimeout(()=>{U.off("postrender",o),n(new Error("[StorySplat Viewer] Timed out waiting for a rendered frame."))},5e3);U.once("postrender",o),U.renderNextFrame=!0})}catch(t){console.warn("[StorySplat Viewer] readPixels capture failed, falling back to canvas capture:",t)}return await new Promise((t,e)=>{const n=()=>{window.clearTimeout(o),t()},o=window.setTimeout(()=>{U.off("postrender",n),e(new Error("[StorySplat Viewer] Timed out waiting for a rendered frame."))},5e3);U.once("postrender",n),U.renderNextFrame=!0}),await new Promise(t=>requestAnimationFrame(()=>t())),bt(I)},Et=()=>n.name||"StorySplat Scene",Tt=async()=>{const t=await _t(),n=`storysplat-${Et().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,48)||"scene"}-${(new Date).toISOString().slice(0,10)}.jpg`,o="undefined"!=typeof window?window.location.href:"",i=Et(),s="Share StorySplat",a=`Check out "${i}" on StorySplat.`,r="undefined"!=typeof navigator?navigator:null,l="undefined"!=typeof File?new File([t],n,{type:"image/jpeg"}):null,c=l?[l]:[],d=!!(r?.share&&l&&"function"==typeof r.canShare&&r.canShare({files:c}));!function(t,e){t.querySelector(".storysplat-photo-share-backdrop")?.remove();const{blob:n,fileName:o,shareUrl:i,shareText:s,title:a,buttonLabels:r,sharePhoto:l}=e,c=a||"Share StorySplat",d=s||"Check out this StorySplat.",p=encodeURIComponent(i),h=encodeURIComponent(d),u=encodeURIComponent(c),m=encodeURIComponent(`${d}\n\n${i}`),g=URL.createObjectURL(n);let f=g;const y=document.createElement("div");y.className="storysplat-photo-share-backdrop";const v=document.createElement("div");v.className="storysplat-photo-share-panel",v.setAttribute("role","dialog"),v.setAttribute("aria-modal","true"),v.setAttribute("aria-label",c);const b=document.createElement("div");b.className="storysplat-photo-share-header";const w=document.createElement("span");w.className="storysplat-photo-share-title",w.textContent=c;const S=document.createElement("button");S.className="storysplat-photo-share-close",S.type="button",S.setAttribute("aria-label",x(r,"close")),S.textContent="×",b.appendChild(w),b.appendChild(S);const _=document.createElement("img");_.className="storysplat-photo-share-preview",_.src=g,_.alt=c,_.addEventListener("error",()=>{const t=new FileReader;t.onload=()=>{"string"==typeof t.result&&(_.src=t.result,f&&(URL.revokeObjectURL(f),f=null))},t.readAsDataURL(n)},{once:!0});const C=document.createElement("div");C.className="storysplat-photo-share-socials",[{label:"X / Twitter",url:`https://twitter.com/intent/tweet?text=${h}&url=${p}`},{label:"Email",url:`mailto:?subject=${u}&body=${m}`}].forEach(({label:t,url:e})=>{const n=document.createElement("a");n.className="storysplat-photo-share-social",n.href=e,n.textContent=t,e.startsWith("mailto:")||(n.target="_blank",n.rel="noopener noreferrer"),C.appendChild(n)});const M=document.createElement("div");if(M.className="storysplat-photo-share-actions",l){const e=document.createElement("button");e.className="storysplat-photo-share-action primary",e.type="button",e.textContent=x(r,"share"),e.addEventListener("click",async()=>{e.disabled=!0;try{await l()}catch(e){console.warn("[StorySplat Viewer] Native photo share failed:",e),k(t,x(r,"shareFailed"))}finally{e.disabled=!1}}),M.appendChild(e)}const E=document.createElement("button");E.className="storysplat-photo-share-action"+(l?"":" primary"),E.type="button",E.textContent=x(r,"downloadPhoto");const P=document.createElement("button");P.className="storysplat-photo-share-action",P.type="button",P.textContent=x(r,"copyLink"),M.appendChild(E),M.appendChild(P),v.appendChild(b),v.appendChild(_),v.appendChild(C),v.appendChild(M),y.appendChild(v),t.appendChild(y);const T=()=>{f&&(URL.revokeObjectURL(f),f=null),y.remove()};S.addEventListener("click",T),y.addEventListener("click",t=>{t.target===y&&T()}),E.addEventListener("click",()=>{const e=document.createElement("a");e.href=g,e.download=o,e.rel="noopener",document.body.appendChild(e),e.click(),e.remove(),k(t,x(r,"photoDownloaded"))}),P.addEventListener("click",async()=>{try{await navigator.clipboard.writeText(i),k(t,x(r,"linkCopied"))}catch{window.prompt("Copy this link:",i)}})}(e,{blob:t,fileName:n,shareUrl:o,shareText:a,title:s,buttonLabels:u,sharePhoto:d?async()=>{try{await r.share({title:s,text:a,url:o,files:c})}catch(t){if((t=>"object"==typeof t&&null!==t&&"name"in t&&"AbortError"===t.name)(t))return;await r.share({title:s,text:a,files:c})}}:void 0})};let At=null;let zt=r.additionalSplats||[];const Dt=r.keepMeshesInMemory??!1;let It=L(r.initialSplatExploreMode),Ft=r.refocusTapMode||"single",Bt=null,Vt=null,$t=!1;const Ut=new Map;let Ot=null,Nt=-1,Wt=-1,Ht=0,Gt="compare"===r.splatSwapMode&&zt.length>0,jt=null,Xt=(r.compareSettings?.initialPosition??50)/100,Yt=null,Zt=null,Kt=null,Jt=null;const te=r.compareSettings||{};let ee=0,ne=!1,oe=!1,ae=null,le=null,ce=null,de=null;const he=new t.Entity("camera");let me=new t.Color(.1,.1,.1);if(r.backgroundColor){const Bl=r.backgroundColor.replace("#","");if(6===Bl.length){const Vl=parseInt(Bl.substring(0,2),16)/255,$l=parseInt(Bl.substring(2,4),16)/255,Ul=parseInt(Bl.substring(4,6),16)/255;me=new t.Color(Vl,$l,Ul),console.log("[StorySplat Viewer] Background color set from config:",r.backgroundColor)}}he.addComponent("camera",{clearColor:me,fov:r.fov||60,nearClip:r.nearClip||.1,farClip:r.farClip||1e3}),he.addComponent("audiolistener");class fe extends t.PostEffect{constructor(e){super(e),this.fxaaShader=t.ShaderUtils.createShader(e,{uniqueName:"StorySplatFxaa",attributes:{aPosition:t.SEMANTIC_POSITION},vertexGLSL:t.PostEffect.quadVertexShader,fragmentGLSL:"\n uniform sampler2D uColorBuffer;\n uniform vec2 uResolution;\n\n #define FXAA_REDUCE_MIN (1.0/128.0)\n #define FXAA_REDUCE_MUL (1.0/8.0)\n #define FXAA_SPAN_MAX 8.0\n\n void main() {\n vec2 fragCoord = gl_FragCoord.xy * uResolution;\n vec4 rgbaM = texture2D(uColorBuffer, fragCoord);\n vec3 rgbM = rgbaM.rgb;\n float opacity = rgbaM.a;\n\n vec3 rgbNW = textureOffset(uColorBuffer, fragCoord, ivec2(-1, -1)).rgb;\n vec3 rgbNE = textureOffset(uColorBuffer, fragCoord, ivec2( 1, -1)).rgb;\n vec3 rgbSW = textureOffset(uColorBuffer, fragCoord, ivec2(-1, 1)).rgb;\n vec3 rgbSE = textureOffset(uColorBuffer, fragCoord, ivec2( 1, 1)).rgb;\n\n vec3 luma = vec3(0.299, 0.587, 0.114);\n float lumaNW = dot(rgbNW, luma);\n float lumaNE = dot(rgbNE, luma);\n float lumaSW = dot(rgbSW, luma);\n float lumaSE = dot(rgbSE, luma);\n float lumaM = dot(rgbM, luma);\n float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));\n float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));\n\n vec2 dir;\n dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));\n dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));\n float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);\n float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);\n dir = min(vec2(FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX), dir * rcpDirMin)) * uResolution;\n\n vec3 rgbA = 0.5 * (\n texture2D(uColorBuffer, fragCoord + dir * (1.0/3.0 - 0.5)).rgb +\n texture2D(uColorBuffer, fragCoord + dir * (2.0/3.0 - 0.5)).rgb);\n vec3 rgbB = rgbA * 0.5 + 0.25 * (\n texture2D(uColorBuffer, fragCoord + dir * -0.5).rgb +\n texture2D(uColorBuffer, fragCoord + dir * 0.5).rgb);\n float lumaB = dot(rgbB, luma);\n gl_FragColor = (lumaB < lumaMin || lumaB > lumaMax)\n ? vec4(rgbA, opacity)\n : vec4(rgbB, opacity);\n }\n "}),this.resolution=new Float32Array(2)}render(t,e,n){this.resolution[0]=1/t.width,this.resolution[1]=1/t.height,this.device.scope.resolve("uResolution").setValue(this.resolution),this.device.scope.resolve("uColorBuffer").setValue(t.colorBuffer),this.drawQuad(e,this.fxaaShader,n)}}let ye=null;const ve=t=>({r:parseInt(t.slice(1,3),16)/255,g:parseInt(t.slice(3,5),16)/255,b:parseInt(t.slice(5,7),16)/255}),be={lighting:t.SSAOTYPE_LIGHTING??1,combine:t.SSAOTYPE_COMBINE??2},xe={linear:t.TONEMAP_LINEAR??0,filmic:t.TONEMAP_FILMIC??1,hejl:t.TONEMAP_HEJL??2,aces:t.TONEMAP_ACES??3,aces2:t.TONEMAP_ACES2??4,neutral:t.TONEMAP_NEUTRAL??5},we={linear:t.FOG_LINEAR??"linear",exp:t.FOG_EXP??"exp",exp2:t.FOG_EXP2??"exp2"},Se=t.FOG_NONE??"none";void 0===t.SSAOTYPE_LIGHTING&&console.warn("[StorySplat Viewer] PlayCanvas SSAO type constants not found, using fallback values"),void 0===t.TONEMAP_LINEAR&&console.warn("[StorySplat Viewer] PlayCanvas tone mapping constants not found, using fallback values"),void 0===t.FOG_LINEAR&&console.warn("[StorySplat Viewer] PlayCanvas fog type constants not found, using fallback values");const _e=(e,n)=>{const o=n.colorEnhance,i=n.vignette,s=n.bloom,a=n.ssao,r=n.dof,l=n.fringing,c=n.grading,d=n.taa;if(o?.enabled?(e.colorEnhance.enabled=!0,e.colorEnhance.shadows=o.shadows??0,e.colorEnhance.highlights=o.highlights??0,e.colorEnhance.vibrance=o.vibrance??0,e.colorEnhance.midtones=o.midtones??0,e.colorEnhance.dehaze=o.dehaze??0):e.colorEnhance.enabled=!1,i?.enabled){if(e.vignette.intensity=i.intensity??.4,e.vignette.inner=i.inner??.5,e.vignette.outer=i.outer??1,e.vignette.curvature=i.curvature??1.5,i.color){const t=ve(i.color);e.vignette.color.set(t.r,t.g,t.b)}}else e.vignette.intensity=0;if(s?.enabled?(e.bloom.intensity=s.intensity??.02,e.bloom.blurLevel=s.blurLevel??16):e.bloom.intensity=0,a?.enabled?(e.ssao.type=be[a.type]??be.lighting,e.ssao.intensity=a.intensity??.5,e.ssao.radius=a.radius??30,e.ssao.power=a.power??6,e.ssao.samples=a.samples??12,e.ssao.minAngle=a.minAngle??10,e.ssao.blurEnabled=!1!==a.blurEnabled):e.ssao.type=t.SSAOTYPE_NONE??0,r?.enabled?(e.dof.enabled=!0,e.dof.focusDistance=r.focusDistance??100,e.dof.focusRange=r.focusRange??10,e.dof.blurRadius=r.blurRadius??3,e.dof.blurRings=r.blurRings??4,e.dof.nearBlur=r.nearBlur??!1,e.dof.highQuality=!1!==r.highQuality):e.dof.enabled=!1,e.fringing.intensity=l?.enabled?l.intensity??10:0,c?.enabled){if(e.grading.enabled=!0,e.grading.brightness=c.brightness??1,e.grading.contrast=c.contrast??1,e.grading.saturation=c.saturation??1,c.tint){const t=ve(c.tint);e.grading.tint.set(t.r,t.g,t.b)}}else e.grading.enabled=!1;d?.enabled?(e.taa.enabled=!0,e.taa.jitter=d.jitter??1):e.taa.enabled=!1;const p=n.toneMapping;e.rendering.toneMapping=p?.enabled?xe[p.type]??xe.linear:xe.linear;const h=n.sharpness;e.rendering.sharpness=h?.enabled?h.value??.5:0;const u=n.renderScale;e.rendering.renderTargetScale=u?.enabled?u.value??1:1,e.update()},Ce=t=>{if(t?.enabled){U.scene.fog.type=we[t.type]??we.linear;const e=ve(t.color??"#b8c4d0");U.scene.fog.color.set(e.r,e.g,e.b),U.scene.fog.density=t.density??.05,U.scene.fog.start=t.start??10,U.scene.fog.end=t.end??100,U.scene.gsplat&&(U.scene.gsplat.useFog=!0)}else U.scene.fog.type=Se,U.scene.gsplat&&(U.scene.gsplat.useFog=!1)},Me=t=>{U.scene.exposure=t?.enabled?t.value??1:1};let Ee,Pe=0;const ke=t=>{const e=Math.max(0,Math.min(1,t||0));if(e===Pe)return;const n=Pe>0,o=!n&&e>0,i=n&&0===e;Pe=e,U.scene.gsplat&&(U.scene.gsplat.fisheye=e,o?(Ee=U.scene.gsplat.radialSorting,U.scene.gsplat.radialSorting=!0):i&&void 0!==Ee&&(U.scene.gsplat.radialSorting=Ee,Ee=void 0));const s=U.scene.sky;s&&(s.fisheye=e),console.log("[StorySplat Viewer] Fisheye set to",e)};let Te=null,De=null;let Fe=null,Be=null;const $e=()=>{Be=null;const t=Fe;if(Fe=null,t&&!vt){Te&&(Te.destroy(),Te=null,De=null);try{Te=new Pt(U,t.opts),U.root.addChild(Te.entity),De=t.key,ne&&(Te.entity.enabled=!1,Te.entity.__panoramaHidden=!0)}catch(t){console.warn("[StorySplat Viewer] Weather setup failed:",t)}}},Ue=t=>{if(!t?.enabled)return Be&&(clearTimeout(Be),Be=null,Fe=null),void(Te&&(Te.destroy(),Te=null,De=null));const e="custom"===t.preset?"snow":t.preset,n=kt[e]??kt.snow,o=(t=>{if(!t)return;let e=t.replace("#","");if(3===e.length&&(e=e[0]+e[0]+e[1]+e[1]+e[2]+e[2]),6!==e.length)return;const n=parseInt(e,16);return Number.isNaN(n)?void 0:[(n>>16&255)/255,(n>>8&255)/255,(255&n)/255]})(t.color)??n.color,i={...n,speed:t.speed??n.speed,drift:t.drift??n.drift,opacity:t.opacity??n.opacity,color:o,particleMinSize:t.particleMinSize??n.particleMinSize,particleMaxSize:t.particleMaxSize??n.particleMaxSize,elongate:t.elongate??n.elongate,density:t.density??n.density,extents:t.extents,followEntity:he},s=t.extents??{x:12,y:12,z:12},a=`${s.x},${s.y},${s.z}|${i.density}`;Te&&De===a?Te.applyOptions(i):(Te&&Te.applyOptions(i),Fe={opts:i,key:a},Be&&clearTimeout(Be),Be=setTimeout($e,150))},Oe=t=>{Te&&!ne&&Te.update(t)};U.on("update",Oe);const Ne=t=>t?.colorEnhance?.enabled||t?.vignette?.enabled||t?.bloom?.enabled||t?.ssao?.enabled||t?.dof?.enabled||t?.fringing?.enabled||t?.grading?.enabled||t?.taa?.enabled||t?.toneMapping?.enabled||t?.sharpness?.enabled||t?.renderScale?.enabled;let We=null;const He=r.postProcessing;if(Ne(He)&&he.camera)try{We=new t.CameraFrame(U,he.camera),We.enabled=!0,_e(We,He),console.log("[StorySplat Viewer] Post-processing enabled:",Object.keys(He).filter(t=>He[t]?.enabled))}catch(Ol){console.warn("[StorySplat Viewer] CameraFrame setup failed (WebGPU may be required):",Ol)}if(!1!==He?.antialiasing?.enabled&&he.camera)if(We)We.rendering.sharpness=Math.max(We.rendering.sharpness,.5),console.log("[StorySplat Viewer] CameraFrame active, using sharpness for edge enhancement");else try{ye=new fe(U.graphicsDevice),he.camera.postEffects.addEffect(ye),console.log("[StorySplat Viewer] FXAA antialiasing enabled")}catch(Nl){console.warn("[StorySplat Viewer] FXAA setup failed:",Nl)}Ce(He?.fog),Me(He?.exposure),ke(0),r.weather?.enabled&&Ue(r.weather),console.log("[StorySplat Viewer] Camera settings:",{fov:r.fov,nearClip:r.nearClip,farClip:r.farClip,playerHeight:r.playerHeight});const je=r.playerHeight||1.6;if(r.waypoints&&r.waypoints.length>0){const Wl=r.waypoints[0];if(console.log("[StorySplat Viewer] First waypoint raw:",Wl),Wl.position){const Hl=Wl.position;he.setPosition(Hl.x,Hl.y,-Hl.z),console.log("[StorySplat Viewer] Camera position (Z negated):",{x:Hl.x,y:Hl.y,z:-Hl.z})}else he.setPosition(0,je,5);if(Wl.rotation){const Gl=Jo(Wl.rotation);he.setRotation(Gl),console.log("[StorySplat Viewer] Camera rotation set from waypoint")}}else n.frameSequence&&n.frameSequence.frameUrls&&n.frameSequence.frameUrls.length>0?(he.setPosition(0,je,5),he.lookAt(new t.Vec3(0,1,0)),console.log("[StorySplat Viewer] Camera set for frame sequence (orbit around origin)")):(he.setPosition(0,je,5),he.lookAt(new t.Vec3(0,0,0)),console.log("[StorySplat Viewer] Camera set to default position (0, playerHeight, 5)"));let Xe=r.orbitCameraSettings;const qe=()=>{if(!Xe?.cameraPosition||!Xe.pivotPoint)return null;const e=Xe.cameraPosition,n=Xe.pivotPoint,o=new t.Vec3(n.x,n.y,n.z),i=new t.Vec3(e.x,e.y,e.z),s=i.distance(o);return{cameraPosition:i,pivot:o,radius:Math.max(s,0)}},Ye=()=>{const t=qe();return t?(he.setPosition(t.cameraPosition),he.lookAt(t.pivot),t):null};if(Xe?.cameraPosition&&Xe?.pivotPoint&&"orbit"===It){const jl=Ye();console.log("[StorySplat Viewer] Camera set from orbitCameraSettings:",jl?.cameraPosition,"pivot:",jl?.pivot,"radius:",jl?.radius)}if("walk"===S&&r.walkSpawnOverride&&r.walkCameraSettings?.cameraPosition){const Xl=r.walkCameraSettings.cameraPosition;if(he.setPosition(Xl.x,Xl.y,Xl.z),r.walkCameraSettings.rotation){const ql=r.walkCameraSettings.rotation;he.setEulerAngles(ql.x,ql.y,ql.z)}console.log("[StorySplat Viewer] Initial camera set from walkCameraSettings:",Xl)}const Ze=new t.Entity("camera-rig");U.root.addChild(Ze),Ze.addChild(he);const nn=new t.Entity("scene-content");U.root.addChild(nn);const sn=5*(r.cameraMovementSpeed??1),un=new tt(he,U,{moveSpeed:sn,moveFastSpeed:2.5*sn,moveSlowSpeed:.5*sn,rotateSpeed:800/(r.cameraRotationSensitivity||4e3),enableOrbit:E("orbit"),enableFly:E("fly"),enablePan:!0,invertRotation:r.invertCameraRotation,moveDamping:r.cameraDamping??.75,rotateDamping:r.cameraDamping??.75,zoomDamping:.8});if(Xe?.rotationLimits){const Yl=qe();Yl&&(un.syncFromCamera(Yl.pivot,!0,!0),un.setFocusDistance(Yl.pivot,Yl.radius,!0)),un.setOrbitRotationLimits(Xe.rotationLimits,!0)}const gn=()=>{const e=Xe?.zoomLimits;if(e?.enabled){const n=Number.isFinite(e.min)?Math.max(.01,e.min):.01,o=Number.isFinite(e.max)&&e.max>n?e.max:1/0;un.zoomRange=new t.Vec2(n,o)}else un.zoomRange=new t.Vec2(.01,1/0)};gn();const fn=e=>{Xe=e??void 0,r.orbitCameraSettings=Xe,(()=>{const e=Xe?.rotationLimits??null;if(e?.enabled&&Xe?.cameraPosition&&Xe?.pivotPoint){const e=Xe.cameraPosition,n=Xe.pivotPoint;un.setOrbitRotationLimitAnchor(new t.Vec3(e.x,e.y,e.z),new t.Vec3(n.x,n.y,n.z))}un.setOrbitRotationLimits(e,!(Xe?.cameraPosition&&Xe?.pivotPoint)),gn()})()},yn=()=>{if(!Xe?.cameraPosition||!Xe.pivotPoint)return!1;un.setOrbitRotationLimits(null,!1);const t=Ye();return!!t&&("orbit"===un.mode?(un.syncFromCamera(t.pivot,!0,!0),un.setFocusDistance(t.pivot,t.radius,!0)):un.setOrbitModeFromFocus(t.pivot,t.radius,!0),un.setOrbitRotationLimits(Xe.rotationLimits??null,!0),gn(),!0)};let vn=null;const bn=r.collisionMeshesData&&r.collisionMeshesData.length>0;if(bn||!!r.voxelCollisionUrl){const Zl=null!=r.walkSpeed?r.walkSpeed:sn;vn=new at(he,U,{moveSpeed:Zl,sprintMultiplier:2,lookSensitivity:1/(r.cameraRotationSensitivity||4e3)*10,playerHeight:r.playerHeight||1.6,gravity:20,jumpVelocity:8,collisionRadius:.3,stepHeight:.3}),bn&&vn.createCollisionMeshes(r.collisionMeshesData).then(()=>{console.log("[StorySplat Viewer] Collision meshes loaded for walk mode"),un.setCollisionEntities(vn.collisionMeshEntities)}).catch(t=>{console.error("[StorySplat Viewer] Failed to load collision meshes:",t)}),r.voxelCollisionUrl&&vn.initVoxelCollision(r.voxelCollisionUrl).then(()=>{const t=vn.voxelCollisionInstance;t&&un.setVoxelCollision(t,ot)}).catch(t=>{console.warn("[StorySplat Viewer] Voxel collision init failed:",t)})}let xn=null,wn=null;if(r.segmentDataUrl&&r.enableSegmentHover){let Kl=0;const Ql=()=>{if(!ot?.gsplat)return++Kl>50?void console.warn("[StorySplat Viewer] Gave up waiting for splat entity for segments"):void setTimeout(Ql,200);ge(ot,r.segmentDataUrl,r.segmentMetaUrl).then(t=>{xn=t}).catch(t=>{console.warn("[StorySplat Viewer] Segment loading failed:",t)})};Ql()}vn&&null!=r.headBobEnabled&&(vn.headBobEnabled=r.headBobEnabled),null!=r.doubleTapMoveSpeed&&(un.autoMoveSpeedFactor=r.doubleTapMoveSpeed,vn&&(vn.autoMoveSpeedFactor=r.doubleTapMoveSpeed));let Sn=S,_n=!0;"tour"===S&&un.disable();const Cn=new t.Picker(U,1,1,!0),Mn=new t.Picker(U,1,1,!0),En=new t.Layer({name:"Particles",clearDepthBuffer:!0});U.scene.layers.push(En);const Pn=new t.Layer({name:"VoxelDebug",clearDepthBuffer:!0});U.scene.layers.push(Pn);const kn=new t.Layer({name:"Reticle"});U.scene.layers.push(kn);const Tn=he.camera.layers;he.camera.layers=[...Tn,En.id,Pn.id,kn.id];const An=new t.Entity("reticle"),Ln=new t.StandardMaterial;Ln.emissive=new t.Color(1,1,1),Ln.diffuse=new t.Color(0,0,0),Ln.useLighting=!1,Ln.blendType=t.BLEND_NORMAL,Ln.opacity=1,Ln.depthTest=!1,Ln.depthWrite=!1,Ln.cull=t.CULLFACE_NONE,Ln.update();const Rn=new t.TorusGeometry({tubeRadius:.048,ringRadius:.193,segments:24,sides:10}),zn=t.Mesh.fromGeometry(U.graphicsDevice,Rn),Dn=new t.MeshInstance(zn,Ln);Dn.drawOrder=9999,An.addComponent("render",{meshInstances:[Dn],castShadows:!1,layers:[kn.id]}),An.enabled=!1,U.root.addChild(An);let In=0,Fn=0,Bn=new t.Vec3,Vn=new t.Vec3,$n=!1,Un=0,On=!1,Nn=new t.Vec3(0,1,0),Wn=new t.Vec3(0,1,0);const Hn=new t.Quat,Gn=new t.Vec3,jn=new t.Vec3,Xn=new t.Vec3,qn=new t.Vec3,Yn=new t.Vec3;let Zn=!0,Kn=!1,Qn=0,Jn=null;let to,eo=!1,no=null,oo=!1,io=!1,so=0,ao=!1,ro=null,lo=null,co="",po=!1,ho=!1,uo=null,mo=null;function go(t){if("function"==typeof t?.isRunning)try{return!!t.isRunning()}catch(t){Re("XR8.isRunning() threw; falling back to internal AR session state",t)}return ao}function fo(){return io||oo||"ar"===no||e.classList.contains("storysplat-ar-mode")}function yo(){const t=lo;t&&requestAnimationFrame(()=>{vt||fo()||lo!==t||(lo=null,t())})}function vo(){if(!ho)return;const t=window;try{po?t.pc=to:delete t.pc}catch(t){Re("Failed to restore previous window.pc after 8th Wall AR exit",t)}finally{to=void 0,po=!1,ho=!1}}function bo(t){if(t.PlayCanvas?.stop)try{return void t.PlayCanvas.stop()}catch(t){Re("XR8.PlayCanvas.stop() failed; falling back to XR8.stop()",t)}t.stop()}function xo(){"ar"===no&&eo?Le("Ignoring duplicate AR enter signal"):(eo=!0,oo=!0,no="ar",D.arButton?.classList.add("active"),D.arButton.textContent=x(u,"exitAr"),e.classList.add("storysplat-ar-mode"),e.querySelector(".storysplat-photo-share-backdrop")?.remove(),D.skinsDropdown?.classList.remove("open"),function(){if(!mo)return;U.scene.setSkybox(void 0),U.scene.envAtlas=null;const t=mo.fallbackSkyboxEntity??U.root.findByName("skybox-fallback");t&&(mo.fallbackSkyboxEntity||(mo.fallbackSkyboxEntity=t,mo.fallbackSkyboxEnabled=t.enabled),t.enabled=!1)}(),un.disable(),vn&&vn.disable(),uo=U.autoRender,U.autoRender=!0,Le("Entered 8th Wall AR state",{autoRenderBeforeAR:uo,canvas:{width:I.width,height:I.height,clientWidth:I.clientWidth,clientHeight:I.clientHeight}}),i.emit("xrStart",{type:"ar"}))}function wo(){const t="ar"===no||oo||e.classList.contains("storysplat-ar-mode");t||null!==mo?(eo=!1,oo=!1,ko(),D.arButton?.classList.remove("active"),D.arButton&&(D.arButton.textContent=x(u,"ar")),e.classList.remove("storysplat-ar-mode"),no=null,"explore"===Sn?un.enable():"walk"===Sn&&vn&&vn.enable(),function(t){if(!mo)return;const e=mo;mo=null,Ze.setPosition(e.cameraRigPosition),Ze.setRotation(e.cameraRigRotation),he.setPosition(e.cameraPosition),he.setRotation(e.cameraRotation),nn.setLocalPosition(e.scenePosition),nn.setLocalRotation(e.sceneRotation),nn.setLocalScale(e.sceneScale);const n=he.camera;n.clearColor=e.clearColor,n.clearColorBuffer=e.clearColorBuffer,n.fov=e.fov,n.nearClip=e.nearClip,n.farClip=e.farClip,n.aspectRatio=e.aspectRatio,n.horizontalFov=e.horizontalFov,n.calculateProjection=e.calculateProjection,U.scene.skybox=e.skybox,U.scene.envAtlas=e.envAtlas,U.scene.skyboxMip=e.skyboxMip,U.scene.skyboxIntensity=e.skyboxIntensity,U.scene.skyboxRotation=e.skyboxRotation,e.fallbackSkyboxEntity&&(e.fallbackSkyboxEntity.enabled=e.fallbackSkyboxEnabled),un.syncFromCamera(),U.renderNextFrame=!0,Le("Restored pre-AR viewer state",{reason:t,cameraPosition:e.cameraPosition,scenePosition:e.scenePosition,sceneScale:e.sceneScale})}("exitARState"),null!==uo&&(U.autoRender=uo,uo=null),U.renderNextFrame=!0,Le("Exited 8th Wall AR state",{autoRender:U.autoRender}),yo(),vo(),t&&i.emit("xrEnd",{})):Le("Ignoring duplicate AR exit signal")}function So(t){e.querySelector(".storysplat-ar-error")?.remove();const n=document.createElement("div");n.className="storysplat-ar-error",n.style.cssText='position:absolute;bottom:60px;left:50%;transform:translateX(-50%);z-index:10001;background:rgba(200,40,40,0.92);color:white;padding:12px 20px;border-radius:10px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;max-width:90%;text-align:center;backdrop-filter:blur(8px);box-shadow:0 4px 12px rgba(0,0,0,0.3);cursor:pointer;',n.textContent=t,n.addEventListener("click",()=>n.remove()),e.appendChild(n),setTimeout(()=>{n.parentNode&&n.remove()},8e3)}async function _o(){if(io||oo||"ar"===no)return void Re("Ignoring AR start request because AR is already starting or active",{isStarting8thWallAR:io,isIn8thWallAR:oo,xrSessionType:no});io=!0;const n=++so;co="";try{Le("Starting 8th Wall AR",{mobile:ln(),devicePixelRatio:window.devicePixelRatio,canvas:{width:I.width,height:I.height,clientWidth:I.clientWidth,clientHeight:I.clientHeight}});const o=await Ie(e,r.eighthWallBaseUrl);if(vt||n!==so)return void Le("Ignoring stale 8th Wall AR start after async load",{isDestroyed:vt,startGeneration:n,currentGeneration:so});if(!o.PlayCanvas?.run)throw new Error("8th Wall PlayCanvas wrapper is not available on XR8.PlayCanvas.run");const i=function(t){const{app:e,camera:n,onDisableControls:o,onRestoreControls:i,onARStart:s,onAREnd:a,onTrackingStatus:r,onException:l}=t;let c=!1,d=0,p=!1,h="",u="";const m=(t,i)=>{c?Ae("StorySplat XR8 pipeline enter ignored because it is already attached",{source:t}):(c=!0,d=0,p=!1,h="",u="",Le("StorySplat XR8 pipeline attached",{source:t,canvas:i?.canvas instanceof HTMLCanvasElement?{width:i.canvas.width,height:i.canvas.height,clientWidth:i.canvas.clientWidth,clientHeight:i.canvas.clientHeight}:void 0,appAutoRender:e.autoRender,cameraPosition:n.getPosition()}),o(),s?.())},g=t=>{c?(c=!1,Le("StorySplat XR8 pipeline detached",{source:t,frames:d,lastTrackingStatus:h,lastTrackingReason:u}),i(),a?.()):Ae("StorySplat XR8 pipeline leave ignored because it is already detached",{source:t})};return{name:"storysplat-ar",onAttach(t){m("onAttach",t)},onStart({canvas:t,GLctx:e}){m("onStart",{canvas:t})},onUpdate({processCpuResult:t}){d+=1;const e=t?.reality;e?(p||(p=!0,Le("XR8 reality frame received",{frameCount:d,hasPosition:!!e.position,hasRotation:!!e.rotation,hasIntrinsics:!!e.intrinsics,hasRealityTexture:!!e.realityTexture,trackingStatus:e.trackingStatus,trackingReason:e.trackingReason})),!e.trackingStatus||e.trackingStatus===h&&(e.trackingReason??"")===u||(h=e.trackingStatus,u=e.trackingReason??"",Le("XR8 tracking status changed",{status:h,reason:u||void 0,frameCount:d}),r?.(e.trackingStatus,e.trackingReason))):d<=5&&Ae("XR8 update without reality payload",{frameCount:d})},onDetach(){g("onDetach")},onStop(){g("onStop")},onException(t){ze("XR8 runtime exception",t),l?.(t)},listeners:[{event:"reality.trackingstatus",process:t=>{const e=t.status,n=t.reason;Le("XR8 tracking status event",{status:e,reason:n}),r?.(e,n)}}]}}({app:U,camera:he,sceneRoot:nn,onDisableControls:()=>{un.disable(),vn&&vn.disable()},onRestoreControls:()=>{"explore"===Sn?un.enable():"walk"===Sn&&vn&&vn.enable()},onARStart:()=>{xo()},onAREnd:()=>{wo(),ro&&(ro.stop(),ro=null)},onTrackingStatus:(t,e)=>{co=t,Le("Tracking status callback",{status:t,reason:e}),ro?.setTrackingStatus(t)},onException:t=>{So(`AR error: ${t.message}`),Co()}});go(o)&&(Re("XR8 was already running before AR start; stopping stale session first"),bo(o),ao=!1),function(){if(mo)return;const t=he.camera,e=U.root.findByName("skybox-fallback");mo={cameraPosition:he.getPosition().clone(),cameraRotation:he.getRotation().clone(),cameraRigPosition:Ze.getPosition().clone(),cameraRigRotation:Ze.getRotation().clone(),scenePosition:nn.getLocalPosition().clone(),sceneRotation:nn.getLocalRotation().clone(),sceneScale:nn.getLocalScale().clone(),clearColor:t.clearColor.clone(),clearColorBuffer:t.clearColorBuffer,fov:t.fov,nearClip:t.nearClip,farClip:t.farClip,aspectRatio:t.aspectRatio,horizontalFov:t.horizontalFov,calculateProjection:t.calculateProjection,skybox:U.scene.skybox,envAtlas:U.scene.envAtlas,skyboxMip:U.scene.skyboxMip,skyboxIntensity:U.scene.skyboxIntensity,skyboxRotation:U.scene.skyboxRotation.clone(),fallbackSkyboxEntity:e,fallbackSkyboxEnabled:e?.enabled??!1},Le("Captured pre-AR viewer state",{cameraPosition:mo.cameraPosition,scenePosition:mo.scenePosition,sceneScale:mo.sceneScale,autoRender:U.autoRender})}(),o.XrController.configure({scale:"absolute"});const s=U.graphicsDevice.canvas,a=U.graphicsDevice,l={canvas:s,ownRunLoop:!1,webgl2:!(!a.webgl2&&!a.isWebGL2),cameraConfig:o.XrConfig?.camera?{direction:o.XrConfig.camera().BACK}:void 0};if(Le("Running XR8.PlayCanvas integration",{runConfig:{ownRunLoop:l.ownRunLoop,webgl2:l.webgl2,hasCameraConfig:!!l.cameraConfig},modules:["reality","storysplat-ar"]}),vt||n!==so)return void Le("Ignoring stale 8th Wall AR start before runtime run",{isDestroyed:vt,startGeneration:n,currentGeneration:so});if(function(){const e=window;ho||(po=Object.prototype.hasOwnProperty.call(e,"pc"),to=e.pc);try{e.pc=t,ho=!0}catch(t){throw new Error(`8th Wall requires window.pc to point at this viewer's PlayCanvas runtime: ${t instanceof Error?t.message:String(t)}`)}if(e.pc!==t)throw new Error("8th Wall requires window.pc to point at this viewer's PlayCanvas runtime")}(),o.PlayCanvas.run({pcCamera:he,pcApp:U},[o.XrController.pipelineModule(),i],l),vt||n!==so)return void Le("Ignoring stale 8th Wall AR start after runtime run",{isDestroyed:vt,startGeneration:n,currentGeneration:so});ao=!0,ro=new Ve({app:U,sceneRoot:nn,canvas:s,tapMode:Ft,shouldIgnoreTap:(t,e)=>{const n=s.getBoundingClientRect(),o=t-n.left,i=e-n.top;return!!Sr(o,i)||!!$r(o,i)},onPlaced:t=>{Le("Scene placement callback",{position:t})}}),co&&ro.setTrackingStatus(co),ro.start()}catch(t){if(vt||n!==so)return void Re("Ignoring stale 8th Wall AR start error",t);ze("Failed to start 8th Wall AR",t);So(`AR failed to start: ${t instanceof Error?t.message:String(t)}`),wo(),vo()}finally{n===so&&(io=!1)}}function Co(){so+=1,io=!1;const t=go(window.XR8);if(!(t||oo||"ar"===no||io||mo))return yo(),void vo();Le("Stopping 8th Wall AR",{isIn8thWallAR:oo,xrSessionType:no,xr8Running:t});try{t&&window.XR8&&bo(window.XR8)}catch(t){ze("Error stopping 8th Wall AR",t)}finally{ao=!1}ro&&(ro.stop(),ro=null),wo()}let Mo=null,Eo=null,Po=!1;function ko(){Mo&&(Mo.enabled=!1,Po=!1)}let To=0;U.on("update",e=>{var n,o;D.fpsCounter&&(To+=e,To>=.5&&(To=0,o=1/e,(n=D).fpsCounter&&(n.fpsCounter.textContent=`${Math.round(o)} FPS`))),"walk"===Sn&&vn?vn.update(e):un.update(e),_n||function(){const t=he.getPosition();Cs.forEach(e=>{const n=e.hotspotData;if(!n)return;const o=e.mediaTriggerMode||"click",i="proximity"===o,s="proximity"===(n.audioTriggerMode||("proximity"===o?"proximity":"click"))&&e.hotspotAudioSlotId;if(!i&&!s)return;const a=e.getPosition(),r=t.distance(a)<=(e.proximityDistance||5),l=e.wasInProximity||!1;if(r&&!l){if(i&&("video"===n.type&&e.videoElement?vr(e,n):"gif"===n.type&&(e.enabled=!0)),s&&e.hotspotAudioReady){const t=e.sound?.slot(e.hotspotAudioSlotId);t&&!t.isPlaying&&(t.play(),e.hotspotAudioPlaying=!0,console.log("[Audio] Hotspot audio started (proximity):",n.title))}e.wasInProximity=!0}else if(!r&&l){if(i&&("video"===n.type&&e.videoElement?!1!==n.pauseOnLeaveProximity&&br(e):"gif"===n.type&&(e.enabled=!1)),s){if(n.audioStopOnExit??!0){const t=e.sound?.slot(e.hotspotAudioSlotId);t&&t.isPlaying&&(t.stop(),e.hotspotAudioPlaying=!1,console.log("[Audio] Hotspot audio stopped (proximity):",n.title))}}e.wasInProximity=!1}})}(),function(){if(0===Zs.size)return;const t=he.getPosition();Zs.forEach((e,n)=>{const{entity:o,config:i,slotId:s,assetReady:a}=e;if(!a)return;const r=o.sound?.slot(s);if(!r)return;if(!i.triggerMode||"proximity"!==i.triggerMode)return;const l=e.playing||r.isPlaying,c=o.getPosition(),d=t.distance(c),p=i.proximityRadius??i.maxDistance??1,h=i.stopOnExit??!0;d<=p&&!l?(console.log(`[Audio] Proximity play: ${n}, distance=${d.toFixed(2)}, radius=${p}`),r.play(),e.playing=!0):d>p&&l&&h&&(console.log(`[Audio] Proximity stop: ${n}, distance=${d.toFixed(2)}`),r.stop(),e.playing=!1)})}(),function(){if(0===rr.size)return;const t=he.getPosition();rr.forEach((e,n)=>{const{entity:o,config:i,slotId:s,assetReady:a}=e;if(!a)return;const r=o.sound?.slot(s);if(!r)return;const l=e.playing||r.isPlaying,c=o.getPosition(),d=t.distance(c),p=i.triggerMode||(!1!==i.autoplay?"autoplay":"proximity"),h=i.proximityRadius??i.maxDistance??10;if(d<=h&&!l&&("proximity"!==p||r.isPlaying||(r.play(),e.playing=!0)),d>h&&l&&"proximity"===p){(i.stopOnExit??!0)&&(r.stop(),e.playing=!1)}})}(),function(){if(0===sa.size)return;const t=he.getPosition();sa.forEach(e=>{const{entity:n,config:o,audioSlotId:i}=e,s=o.interaction;if(!s?.playAudio||!i||!e.audioAssetReady)return;if("proximity"!==(s.audioTriggerMode||s.activationMode||"click"))return;const a=n.sound?.slot(i);if(!a)return;const r=e.audioPlaying||a.isPlaying,l=t.distance(n.getPosition()),c=s.audioProximityRadius??s.audioMaxDistance??1,d=s.audioStopOnExit??!0;l<=c&&!r?(a.play(),e.audioPlaying=!0):l>c&&r&&d&&(a.stop(),e.audioPlaying=!1)})}(),function(){if(!r.waypoints?.length)return;const e=he.getPosition();r.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?lr.has(o)||(lr.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=>{})}(n)):lr.has(o)&&(lr.delete(o),console.log(`[StorySplat] Waypoint ${o} exited`),function(t){if(!t.interactions?.length)return;t.interactions.forEach(t=>{})}(n))})}(),eo&&Po&&function(){if(Mo?.enabled&&he){const t=he.forward.clone(),e=he.getPosition(),n=e.clone().add(t.mulScalar(1.5));n.y-=.1,Mo.setPosition(n),Mo.lookAt(e),Mo.rotateLocal(90,0,0)}}(),function(t){if(0===Wa.length)return;const e=[];for(let n=0;n<Wa.length;n++){const o=Wa[n];if(o.age+=t,o.age>=o.lifetime){e.push(n);continue}o.velocity.y-=ja*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=tr(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*Xa,Math.abs(o.velocity.y)<.3&&(o.velocity.y=0))}er(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--)nr(Wa[e[t]]),Wa.splice(e[t],1)}(e),il&&!a&&il.update(e,ki),eo?($n=!1,Qn=0,An.enabled=!1):(!Zn||q||"explore"!==Sn&&"walk"!==Sn)&&($n=!1),Kn&&($n=!1),Ir&&($n=!1);const i=$n?1:0;if(Qn+=(i-Qn)*Math.min(1,8*e),Math.abs(Qn-i)<.01&&(Qn=i),Qn>0){Vn.distance(Bn)<.001?Vn.copy(Bn):Vn.lerp(Vn,Bn,Math.min(1,15*e)),Wn.lerp(Wn,Nn,Math.min(1,10*e)),Wn.normalize();const n=he.getPosition().distance(Vn),o=Jn?.035:.12,i=Jn?.08:.24,s=Math.max(i,n*o);Yn.copy(Vn).addScaled(Wn,.01),An.setPosition(Yn),An.setLocalScale(s,.45*s,s),Ln.opacity=Qn,Jn?Ln.emissive.copy(Jn):Ln.emissive.set(1,1,1),Ln.update(),Hn.setFromDirections(t.Vec3.UP,Wn),An.setRotation(Hn),An.enabled=!0}else An.enabled=!1;gt&&mt.size>0&&(!function(){if(0===mt.size)return;Ra.length=0;for(const t of Ca){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;Ia||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;Ra.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})}!Ia&&Ra.length>0&&(Ia=!0,console.log("[Relighting] Total lights synced:",Ra.length));for(const t of mt)t.enabled&&t.updateLights(Ra)}(),!ft&&ut&&(ft=!0,console.log("[StorySplat Viewer] Relighting sync active, lights:",Ra.length,"fade:",ut.relightFade,"ambient:",[ut.ambientR,ut.ambientG,ut.ambientB],"material:",!!ut.material,"totalScripts:",mt.size)))}),U.on("postrender",()=>{if(eo)return;const e=Zn&&!q&&("explore"===Sn||"walk"===Sn),n=U.graphicsDevice.canvas;if(!e||On||!n)return;if(Un++,Un<4)return;Un=0,On=!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(In*o),l=a?Math.floor(.5*n.clientHeight*o):Math.floor(Fn*o);try{Mn.resize(i,s);const e=U.scene.layers.getLayerByName("World");if(!e)return void(On=!1);Mn.prepare(he.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([Mn.getWorldPointAsync(r,l),Mn.getWorldPointAsync(o,l),Mn.getWorldPointAsync(r,c),Mn.getWorldPointAsync(a,l),Mn.getWorldPointAsync(r,d)]).then(e=>{if(On=!1,vt)return;const n=he.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){Bn.copy(i);let s=!1;const a=e.filter((t,e)=>t!==i&&o(t));a.length>=2&&(Gn.sub2(a[0],i),jn.sub2(a[1],i),Gn.lengthSq()>1e-8&&jn.lengthSq()>1e-8&&(Xn.cross(Gn,jn).normalize(),qn.sub2(n,i),Xn.dot(qn)<0&&Xn.mulScalar(-1),Xn.lengthSq()>.5&&(Nn.copy(Xn),s=!0))),!s&&Nn.lengthSq()<.5&&Nn.copy(t.Vec3.UP),$n=!0}}).catch(()=>{On=!1})}catch(t){On=!1}});const Ao=60;U.on("joystick:left",(t,e,n,o)=>{if(t<0||e<0)V(D,!1,0,0,Ao),"walk"===Sn&&vn&&vn.setJoystickMove(0,0);else{const i=n-t,s=o-e;V(D,!0,i,s,Ao),"walk"===Sn&&vn&&vn.setJoystickMove(i/Ao,-s/Ao)}}),U.on("joystick:right",(t,e,n,o)=>{t<0||e<0?($(D,!1),"walk"===Sn&&vn&&vn.setJoystickLook(0,0)):($(D,!0),"walk"===Sn&&vn&&vn.setJoystickLook((n-t)/Ao,(o-e)/Ao))});const Lo=D.scrollControls?.querySelectorAll(".storysplat-explore-btn");let Ro=It||"fly",zo=null,Do=!0,Io=!1;const Fo=t=>{Ro=t,Lo?.forEach(e=>{const n=e.getAttribute("data-explore-mode");e.classList.toggle("selected",n===t)})},Bo=t=>{E(t)?("walk"===Sn&&Vo("explore"),"orbit"===t&&yn()||un.setMode(t),Fo(t),q?B(D,"fly"===t):(O(D,"fly"===t),N(D,"orbit"===t)),H(D)):console.warn(`[StorySplat Viewer] Explore ${t} mode is disabled for this scene.`)};function Vo(e){ne&&!oe&&qo(),"tour"===Sn&&"tour"!==e&&nt&&Ss(),An.enabled=!1,$n=!1,Qn=0,"walk"===Sn&&vn?(vn.disable(),ln()&&(un.mobileInputLayout="joystick-touch")):"explore"===Sn&&un.disable(),Sn=e,console.log("[StorySplat Viewer] Switching to mode:",e);const n=ln();if("walk"===e&&vn){_n=!1,un.disable(),n?(un.detachInputSources(),un.reattachMobileInput(),un.mobileInputLayout="joystick-joystick"):un.detachInputSources();const t=r.walkCameraSettings;if(r.walkSpawnOverride&&t?.cameraPosition){const e=t.cameraPosition;if(he.setPosition(e.x,e.y,e.z),t.rotation){const e=t.rotation;he.setEulerAngles(e.x,e.y,e.z)}console.log("[StorySplat Viewer] Walk mode: positioned from walkCameraSettings",e)}vn.enable(),Fo("walk"),n&&B(D,!0),O(D,!1),N(D,!1),H(D)}else if("explore"===e){vn&&vn.disable(),qi=0,Yi=0,Zi=!1,_n=!1,un.disable(),un.enable(),un.enableOrbit=E("orbit"),un.enableFly=E("fly"),un.enablePan=!0;const e=It||"fly",o=Do;Do&&"orbit"===e&&(Xe?.cameraPosition&&Xe?.pivotPoint?(Ye(),console.log("[StorySplat Viewer] Orbit first entry: positioned from saved settings")):ts.length>0&&(he.setPosition(ts[0]),he.setRotation(es[0]),console.log("[StorySplat Viewer] Orbit first entry: positioned at waypoint[0]")));const i=he.getPosition().clone(),s=he.getRotation().clone();un.syncFromPose(i,s);const a=async()=>{try{const t=.25;Cn.resize(Math.floor(Mr.clientWidth*t),Math.floor(Mr.clientHeight*t));const e=U.scene.layers.getLayerByName("World");if(e){Cn.prepare(he.camera,U.scene,[e]);const n=Math.floor(.5*Mr.clientWidth*t),o=Math.floor(.5*Mr.clientHeight*t),i=await Cn.getWorldPointAsync(n,o);if(i){const t=he.getPosition().distance(i);t>.5&&t<500&&(un.setFocusDistance(i,t),console.log("[StorySplat Viewer] Updated focus distance:",t.toFixed(2)))}}}catch(t){}};At||Do||Xe?.pivotPoint||a();const r=Do?e:L(zo??("walk"===Ro?void 0:Ro));if(zo=null,Do=!1,"orbit"===r&&yn()?console.log("[StorySplat Viewer] Restored saved orbit view"):un.setMode(r),!Xe?.cameraPosition&&o&&"orbit"===r&&Xe?.pivotPoint){const e=Xe.pivotPoint;un.syncFromCamera(new t.Vec3(e.x,e.y,e.z),!0,!0),un.setOrbitRotationLimits(Xe.rotationLimits??null,!0),gn(),console.log("[StorySplat Viewer] Re-applied saved orbit pivot after setMode:",e)}Fo(r),n?B(D,"fly"===r):(O(D,"fly"===r),N(D,"orbit"===r))}else un.enable(),un.disable(),vn&&vn.disable(),_n=!0,0===ts.length&&r.waypoints&&r.waypoints.length>0&&(r.waypoints.forEach(e=>{const n=e.position?new t.Vec3(e.position.x,e.position.y,-e.position.z):new t.Vec3(0,r.playerHeight||1.6,0);ts.push(n);const o=e.rotation?Jo(e.rotation):new t.Quat;es.push(o);let i=e.fov||os;i<3.5&&(i*=180/Math.PI),ns.push(i)}),console.log("[StorySplat Viewer] Rebuilt waypoint arrays from config:",ts.length)),as(ki),Gi&&ji&&(he.setPosition(Gi),he.setRotation(ji),he.camera&&ns.length>0&&(he.camera.fov=is)),B(D,!1),O(D,!1),N(D,!1),H(D);i.emit("modeChange",{mode:e}),Gs&&Xs&&"tour"!==e&&Gs.pause()}function $o(e){if(e!==ne&&!oe)if(oe=!0,console.log("[StorySplat Viewer] Panorama mode:",e?"entering":"exiting"),e){ae=Sn,le=Ro,ce=un.pitchRange.clone(),de=un.zoomRange.clone(),"walk"===Sn&&vn&&vn.disable(),_n=!1,qi=0,Yi=0,Zi=!1;const e=he.getPosition().clone(),n=he.getRotation().clone();un.disable(),un.enable(),un.enableOrbit=!0,un.setMode("orbit"),un.syncFromPose(e,n),un.setFocusDistance(e,.001,!0),un.enablePan=!1,un.enableFly=!1,un.zoomRange=new t.Vec2(.001,.001),un.pitchRange=new t.Vec2(-85,85),Sn="explore",B(D,!1),O(D,!1),N(D,!1),H(D);for(let t=0;t<nn.children.length;t++){const e=nn.children[t];e!==ot&&e!==fa&&e.enabled&&(e.enabled=!1,e.__panoramaHidden=!0)}for(const[,t]of Ut)t.enabled&&(t.enabled=!1,t.__panoramaHidden=!0);Te&&Te.entity.enabled&&(Te.entity.enabled=!1,Te.entity.__panoramaHidden=!0),ot&&ai(ot,-1,()=>{ot&&(ot.enabled=!1)}),ne=!0,oe=!1,i.emit("panoramaModeChange",{enabled:!0}),console.log("[StorySplat Viewer] Panorama mode active")}else{ot&&(ot.enabled=!0);for(let t=0;t<nn.children.length;t++){const e=nn.children[t];e.__panoramaHidden&&(e.enabled=!0,delete e.__panoramaHidden)}for(const[,t]of Ut)t.__panoramaHidden&&(t.enabled=!0,delete t.__panoramaHidden);if(Te&&Te.entity.__panoramaHidden&&(Te.entity.enabled=!0,delete Te.entity.__panoramaHidden),ot&&ai(ot,1),un.enablePan=!0,un.enableOrbit=E("orbit"),un.enableFly=E("fly"),un.pitchRange=ce||new t.Vec2(-89,89),un.zoomRange=de||new t.Vec2(.01,1/0),ce=null,de=null,ae&&"explore"!==ae)Vo(ae);else{const t=le||"fly";if("walk"===t)Vo("walk");else{const e=L(t);un.setMode(e),Fo(e);ln()?B(D,"fly"===e):(O(D,"fly"===e),N(D,"orbit"===e))}}ne=!1,oe=!1,ae=null,le=null,i.emit("panoramaModeChange",{enabled:!1})}}Lo?.forEach(t=>{t.addEventListener("click",()=>{if("explore"!==Sn&&"walk"!==Sn)return;const e=t.getAttribute("data-explore-mode");"walk"===e?("walk"!==Sn&&Vo("walk"),Fo("walk")):("walk"===Sn&&Vo("explore"),Bo(e))})}),U.on("cameracontrols:modechange",e=>{if("orbit"!==e&&"fly"!==e||(Fo(e),"explore"!==Sn&&"walk"!==Sn||(q?B(D,"fly"===e):(O(D,"fly"===e),N(D,"orbit"===e)),H(D))),!("orbit"!==e||Xe?.cameraPosition&&Xe.pivotPoint)){let e=!1;if($n&&Bn){const n=he.getPosition(),o=n.distance(Bn);if(o>.3&&o<500){const i=he.forward,s=new t.Vec3(n.x+i.x*o,n.y+i.y*o,n.z+i.z*o);un.syncFromCamera(s),e=!0}}if(!e&&!At)try{const t=.25,e=Math.floor(.5*Mr.clientWidth*t),n=Math.floor(.5*Mr.clientHeight*t);Cn.resize(Math.floor(Mr.clientWidth*t),Math.floor(Mr.clientHeight*t));const o=U.scene.layers.getLayerByName("World");o&&(Cn.prepare(he.camera,U.scene,[o]),Cn.getWorldPointAsync(e,n).then(t=>{if(!vt&&t&&isFinite(t.x)&&isFinite(t.y)&&isFinite(t.z)){const e=he.getPosition().distance(t);e>.3&&e<500&&un.syncFromCamera(t)}}).catch(()=>{}))}catch(t){}}});let Uo=null,Oo=null,No=null,Wo=!1,Ho=null;const Go=new Map;function jo(t){const e=Go.get(t);if(e)return Promise.resolve(e);const n=new Image;n.crossOrigin="anonymous",n.src=t;const o=(n.decode?n.decode():new Promise((t,e)=>{n.complete&&n.naturalWidth>0?t():(n.onload=()=>t(),n.onerror=e)})).then(()=>(Go.set(t,n),n)).catch(e=>{throw Go.delete(t),e});return Go.set(t,o),o}function Xo(t){if(ne||oe||Wo)return void console.warn("[Portal] Panorama activation skipped — already active or in-flight",{panoramaModeEnabled:ne,panoramaModeTransitioning:oe,panoramaLoadPending:Wo});const n=t.panoramaUrl;if(!n)return void console.warn("[Portal] Panorama portal has no panoramaUrl",t);console.log("[StorySplat Viewer] Panorama portal activated:",t.title||t.id,n),Uo=t,Wo=!0,Ho&&(clearTimeout(Ho),Ho=null);const o=Go.get(n);if(!(o instanceof HTMLImageElement&&o.complete&&o.naturalWidth>0)){const t=document.createElement("div");t.className="storysplat-panorama-loading";const n=document.createElement("div");n.className="storysplat-panorama-loading-spinner",t.appendChild(n),e.appendChild(t),No=t}const i=()=>{if(!No)return;const t=No;No=null,t.classList.add("storysplat-panorama-loading--hiding"),setTimeout(()=>t.remove(),200)};jo(n).then(o=>{if(Wo=!1,vt||Uo!==t)return;var s,a;s=n,a=o,r.skybox={url:s,rotation:0,intensity:1,enableIBL:!1},r.skyboxUrl=s,r.skyboxRotation=0,xa(a),$o(!0),i();const l=document.createElement("button");l.className="storysplat-panorama-exit-btn",l.textContent=x(u,"exitPanorama"),l.setAttribute("aria-label",x(u,"exitPanorama")),l.addEventListener("click",()=>qo()),e.appendChild(l),Oo=l;const c=t=>{"Escape"===t.key&&qo()};document.addEventListener("keydown",c),l._escHandler=c}).catch(t=>{Wo=!1,console.warn("[Portal] Failed to load panorama image",n,t),i(),Uo=null})}function qo(){if(!ne&&!oe)return;$o(!1);const t=()=>{Ho=null,va?_a(va,ba):wa()};if(Ho&&(clearTimeout(Ho),Ho=null),ot?Ho=setTimeout(t,1200):t(),Oo){const t=Oo._escHandler;t&&document.removeEventListener("keydown",t),Oo.remove(),Oo=null}Uo=null,console.log("[StorySplat Viewer] Exited panorama portal mode")}const Yo=(t,e)=>{D.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||b.loading} ${Math.round(a)}%`)}(D.preloader,t,e,x(u,"loading")),i.emit("progress",{progress:t,text:e})};function Zo(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 Ko(e,n,o){const i=new t.Entity("splat");i.addComponent("gsplat",{asset:e,unified:!0});const s=i.gsplat;s&&pn(n)&&(s.lodBaseDistance=Q,s.lodMultiplier=J);const a=r.scale||{x:1,y:1,z:1},l=r.invertXScale||!1,c=r.invertYScale||!1,d={x:l?-a.x:a.x,y:c?a.y:-a.y,z:l!==c?a.z:-a.z};i.setLocalScale(d.x,d.y,d.z);const p=r.position||[0,0,0];i.setPosition(p[0],p[1],-p[2]);const h=r.rotation||[0,0,0];return fi(i,[h[0]||0,h[1]||0,h[2]||0],o),nn.addChild(i),i.gsplat?.material&&i.gsplat.material.setParameter("alphaClip",.01),i}function Qo(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 Jo(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}Lt(o.revealEffect||n.revealEffect||r.revealEffect||"none",o.revealStyle||n.revealStyle||r.revealStyle||"bloom");const ti=.75,ei=1.5,ni=50,oi=-50,ii=1;function si(e,n,o,i){e.script||e.addComponent("script");let s=e.script?.gsplatWipeTransition;if(!s){const n=function(){if(wt)return wt;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 mirrorClipSplat(modifiedCenter, 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 mirrorClipSplat(modifiedCenter, scale);\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?ht:pt;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}else o=("wgsl"===n?lt:rt)+o;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()}}),wt=e,e}();s=e.script?.create?.(n)??void 0}s?(s.enabled=!1,s.mode=n,s.duration=ti,s.bandWidth=ei,s.wipeTop=o?ni:oi,s.wipeBottom=o?oi:ni,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 ai(e,n,o){e.script||e.addComponent("script");let i=e.script?.gsplatDissolveTransition;if(!i){const n=function(){if(St)return St;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 mirrorClipSplat(modifiedCenter, 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 mirrorClipSplat(modifiedCenter, scale);\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?ht:pt;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}else o=("wgsl"===n?lt:rt)+o;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()}}),St=e,e}();i=e.script?.create?.(n)??void 0}i?(i.enabled=!1,i.mode=n,i.duration=ii,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 ri=r.swapTransitionType||"dissolve";let li=!1!==r.enableSwapTransition;function ci(t,e,n){li?"scanline"===ri?function(t,e,n=!0){si(t,-1,n,()=>{t.enabled=!1,console.log("[WipeTransition] Old splat hidden after wipe")}),si(e,1,n),console.log(`[WipeTransition] Dual wipe swap started (${n?"forward":"backward"})`)}(t,e,n):function(t,e){ai(t,-1,()=>{t.enabled=!1}),ai(e,1),console.log("[DissolveTransition] Cross-dissolve swap started")}(t,e):t.enabled=!1}function di(t,e){li&&("scanline"===ri?si(t,1,e):ai(t,1))}function pi(t){t.enabled=!1,console.log("[SplatSwap] Splat hidden")}function hi(t){const e=Ut.get(t);if(e){const n=e.script?.gsplatRelighting;n&&mt.delete(n),e.destroy(),Ut.delete(t),console.log("[SplatSwap] Splat disposed:",t)}}function ui(t){const e=Ut.get(t);return!!e&&(e.enabled=!0,Bt=t,console.log("[SplatSwap] Splat shown:",t),!0)}function mi(){if(Bt){const t=Ut.get(Bt);if(t&&t.enabled)return t}return ot&&ot.enabled?ot:null}async function gi(t){if(!pn(t))return null;try{const e=await fetch(t);if(!e.ok)return null;const n=(await e.json()).storySplatCoordinateCorrection;if(n&&Array.isArray(n.eulerDegrees)&&3===n.eulerDegrees.length&&n.eulerDegrees.every(t=>"number"==typeof t&&Number.isFinite(t)))return n}catch(t){console.warn("[SPLAT] Could not read LOD coordinate correction metadata:",t)}return null}function fi(e,n,o){const i=[n[0]*(180/Math.PI),n[1]*(180/Math.PI),-n[2]*(180/Math.PI)];if(o?.eulerDegrees){const n=(new t.Quat).setFromEulerAngles(i[0],i[1],i[2]),s=(new t.Quat).setFromEulerAngles(o.eulerDegrees[0],o.eulerDegrees[1],o.eulerDegrees[2]);return e.setRotation((new t.Quat).mul2(n,s)),void console.log("[SPLAT] Applied coordinate correction:",o)}e.setEulerAngles(i[0],i[1],i[2])}async function yi(e,n){if(!Ut.has(e)&&e!==Bt&&!vt){console.log("[SplatSwap] Preloading splat:",e);try{const o=new t.Asset("splat-preload-"+Date.now(),"gsplat",{url:e});await new Promise((i,s)=>{o.ready(()=>{if(vt)return void s(new Error("Viewer destroyed"));const a=new t.Entity("splat-preload");a.addComponent("gsplat",{asset:o});const l=n?.scale||r.scale||{x:1,y:1,z:1},c=r.invertXScale||!1,d=r.invertYScale||!1,p={x:c?-l.x:l.x,y:d?l.y:-l.y,z:c!==d?l.z:-l.z};a.setLocalScale(p.x,p.y,p.z);const h=n?.position||r.position||[0,0,0];a.setPosition(h[0],h[1],-h[2]);const u=n?.rotation||r.rotation||[0,0,0],m=[u[0]*(180/Math.PI),u[1]*(180/Math.PI),-u[2]*(180/Math.PI)];a.setEulerAngles(m[0],m[1],m[2]),a.enabled=!1,nn.addChild(a),Da(a),Ut.set(e,a),console.log("[SplatSwap] Preload complete:",e),i()}),o.on("error",t=>{console.error("[SplatSwap] Preload error:",t),s(t)}),U.assets.add(o),U.assets.load(o)})}catch(t){console.error("[SplatSwap] Error preloading:",e,t)}}}function vi(t,e=!1){if("explore"!==Sn)return;const n=L(e?It:t||It);if(n&&un){un.mode!==n&&(un.setMode(n),console.log(`[SplatSwap] Switching explore sub-mode to: ${n}`),Fo(n),q?B(D,"fly"===n):(O(D,"fly"===n),N(D,"orbit"===n)),H(D))}}async function bi(e,n=!0,o){if(e===Bt)return;if($t)return;if(vt)return;let s=!1;$t=!0,console.log(`[SplatSwap] Switching to splat (${n?"fwd":"bwd"}):`,e);try{const a=mi();if(Ut.has(e)){ui(e),i.emit("splatChange",{url:e,isOriginal:!1});const t=Ut.get(e);a&&a!==t?ci(a,t,n):di(t,n)}else{const s=new t.Asset("splat-swap-"+Date.now(),"gsplat",{url:e});await new Promise((l,c)=>{s.ready(()=>{if(vt)return void c(new Error("Viewer destroyed"));const d=new t.Entity("splat-swap");d.addComponent("gsplat",{asset:s});const p=o?.scale||r.scale||{x:1,y:1,z:1},h=r.invertXScale||!1,u=r.invertYScale||!1,m={x:h?-p.x:p.x,y:u?p.y:-p.y,z:h!==u?p.z:-p.z};d.setLocalScale(m.x,m.y,m.z);const g=o?.position||r.position||[0,0,0];d.setPosition(g[0],g[1],-g[2]);const f=o?.rotation||r.rotation||[0,0,0],y=[f[0]*(180/Math.PI),f[1]*(180/Math.PI),-f[2]*(180/Math.PI)];d.setEulerAngles(y[0],y[1],y[2]),nn.addChild(d),Da(d),Ut.set(e,d),Bt=e;const v=100*ki,b=$i(ki),x=zt.find(t=>t.url===e);!x||(-1!==x.waypointIndex?b>=x.waypointIndex:-1===x.percentage||v>=x.percentage)?(a?ci(a,d,n):di(d,n),i.emit("splatChange",{url:e,isOriginal:!1}),console.log("[SplatSwap] New splat loaded and shown:",e)):(d.enabled=!1,console.log("[SplatSwap] Load completed but user already past swap point - skipping forward transition:",e)),l()}),s.on("error",t=>{console.error("[SplatSwap] Load error:",t),c(t)}),U.assets.add(s),U.assets.load(s)})}const l=zt.findIndex(t=>t.url===e);-1!==l&&async function(t){if(!zt||0===zt.length)return;const e=(t+1)%zt.length,n=zt[e];n&&n.url&&await yi(n.url,n)}(l),Ot=null,s=!0}catch(t){Ot=e,console.error("[SplatSwap] Error switching splat:",t)}finally{$t=!1,Nt=-1,Wt=-1,s&&wi()}}function xi(){if(Vt)return Vt;const t=ln()||!0===r.forceMobilePreview,e=Boolean(r.mobileLodMetaUrl||r.mobileSogUrl||r.mobileSplatUrl||r.mobileCompressedPlyUrl),n=t&&e,o=n?r.mobileLodMetaUrl:r.lodMetaUrl,i=n?r.mobileSogUrl:r.sogUrl,s=n?r.mobileCompressedPlyUrl:void 0,a=n?r.mobileSplatUrl:r.splatUrl;return o||(i||(s||(a||(r.fallbackUrls&&r.fallbackUrls.length>0?r.fallbackUrls[0]:""))))}function wi(){if(Gt)return;if(!zt||0===zt.length)return;const t=100*ki,e=$i(ki);if(Math.abs(t-Nt)<.1&&e===Wt)return;Nt=t,Wt=e;let n=null,o=null,s=-1/0,a=-1/0;for(const i of zt)-1!==i.waypointIndex?e>=i.waypointIndex&&i.waypointIndex>s&&(s=i.waypointIndex,n=i):-1!==i.percentage&&t>=i.percentage&&i.percentage>a&&(a=i.percentage,o=i);const r=o||n,l=r&&"__ORIGINAL__"===r.url,c=xi(),d=r?l?c:r.url:c;if(Ot&&d!==Ot&&(Ot=null),(!d||d!==Ot||d===Bt)&&d&&d!==Bt){const e=t>=Ht;if(Ht=t,d===c&&ot&&!Bt)Bt=c;else if(d===c&&ot){const t=mi();ot.enabled=!0,Bt=c,t&&t!==ot?(ci(t,ot,e),Ut.forEach((e,n)=>{n!==c&&e!==t&&(Dt?pi(e):hi(n))})):(di(ot,e),Ut.forEach((t,e)=>{e!==c&&(Dt?pi(t):hi(e))})),i.emit("splatChange",{url:c,isOriginal:!0}),console.log("[SplatSwap] Returned to primary splat"),vi(void 0,!0)}else{bi(d,e,r&&(r.position||r.rotation||r.scale)?{position:r.position,rotation:r.rotation,scale:r.scale}:void 0),r&&vi(r.defaultExploreMode,!1)}r&&r.skyboxUrl&&!l?Pi(r.skyboxUrl,r.skyboxRotation||0):l&&va&&Pi(va,ba)}}function Si(e,n){e.script||e.addComponent("script");let o=e.script?.gsplatCompareClip;if(!o){const n=function(){if(Mt)return Mt;const e=t.createScript("gsplatCompareClip");return Object.assign(e.prototype,{material:null,_effectInitialized:!1,_shadersNeedApplication:!1,side:-1,position:.5,softness:.001,orientation:0,initialize(){this.material=null,this._effectInitialized=!1,this._shadersNeedApplication=!1,this.on("enable",()=>{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.enabled){let t=!1;const e=this.entity.gsplat;if(e){const n=!0===e.unified?this.app.scene.gsplat?.material??null:e.material;if(n&&n!==this.material)this.material=null,t=!0;else if(this.material){const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl",o=this.material.getShaderChunks(n).get("gsplatModifyVS");o&&o.includes("uComparePosition")||(t=!0)}}t&&(this._shadersNeedApplication=!0,this._applyShaders(),this.material&&(this._shadersNeedApplication=!1))}this.material&&(this._updateUniforms(),this.material.update())}else this.entity.gsplat&&(this._effectInitialized=!0,this.enabled&&this._shadersNeedApplication&&(this._applyShaders(),this.material&&(this._shadersNeedApplication=!1)))},_updateUniforms(){this._setUniform("uComparePosition",this.position),this._setUniform("uCompareSide",this.side),this._setUniform("uCompareSoftness",this.softness),this._setUniform("uCompareOrientation",this.orientation),this._cameraEntity||(this._cameraEntity=this.app.root.findByName("camera")??null);const e=this._cameraEntity?.camera;if(e)if(this._mvpMat||(this._mvpMat=new t.Mat4,this._vpMat=new t.Mat4),this._vpMat.mul2(e.projectionMatrix,e.viewMatrix),!0===this.entity.gsplat?.unified)this._setUniform("uCompareVP",this._vpMat.data);else{const t=this.entity.getWorldTransform();this._mvpMat.mul2(this._vpMat,t),this._setUniform("uCompareVP",this._mvpMat.data)}},updatePosition(t){this.position=t},updateOrientation(t){this.orientation=t},getShaderGLSL:()=>"\nuniform float uComparePosition; // 0.0 to 1.0 screen-space position of the divider\nuniform float uCompareSide; // -1.0 = show left/top of line, +1.0 = show right/bottom\nuniform float uCompareSoftness; // Soft edge width in screen space (~0.001 for crisp)\nuniform float uCompareOrientation; // 0.0 = vertical (clip on X), 1.0 = horizontal (clip on Y)\nuniform mat4 uCompareVP; // model-view-projection matrix (uploaded per-frame by script)\n\nvoid modifySplatCenter(inout vec3 center) {\n // No position modification\n}\n\nvoid modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) {\n mirrorClipSplat(modifiedCenter, scale);\n\n // Project center to clip space using our own MVP uniform\n // (engine uniforms matrix_model/view/projection are not in scope for gsplatModifyVS chunks)\n vec4 clipPos = uCompareVP * vec4(modifiedCenter, 1.0);\n float ndcX = clipPos.x / clipPos.w; // -1 to +1\n float ndcY = clipPos.y / clipPos.w; // -1 to +1\n\n // Convert to 0-1 screen space\n float screenX = ndcX * 0.5 + 0.5;\n float screenY = 1.0 - (ndcY * 0.5 + 0.5); // Flip Y: 0 = top, 1 = bottom\n\n // Pick axis based on orientation\n float screenPos = mix(screenX, screenY, uCompareOrientation);\n\n // Signed distance from divider, accounting for which side this splat belongs to\n float dist = (screenPos - uComparePosition) * uCompareSide;\n\n if (dist < -uCompareSoftness) {\n // Wrong side of the divider — hide\n scale = vec3(0.0);\n } else if (dist < uCompareSoftness) {\n // Soft edge band — scale down for anti-aliasing\n float t = smoothstep(-uCompareSoftness, uCompareSoftness, dist);\n scale *= t;\n }\n // else: fully visible\n}\n\nvoid modifySplatColor(vec3 center, inout vec4 color) {\n // No color modification — clean split with no edge tint\n}\n",getShaderWGSL:()=>"\nuniform uComparePosition: f32;\nuniform uCompareSide: f32;\nuniform uCompareSoftness: f32;\nuniform uCompareOrientation: f32;\nuniform uCompareVP: mat4x4f;\n\nfn modifySplatCenter(center: ptr<function, vec3f>) {\n // No position modification\n}\n\nfn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr<function, vec4f>, scale: ptr<function, vec3f>) {\n mirrorClipSplat(modifiedCenter, scale);\n\n // Project center to clip space using our own MVP uniform\n let clipPos = uniform.uCompareVP * vec4f(modifiedCenter, 1.0);\n let ndcX = clipPos.x / clipPos.w;\n let ndcY = clipPos.y / clipPos.w;\n\n let screenX = ndcX * 0.5 + 0.5;\n let screenY = 1.0 - (ndcY * 0.5 + 0.5);\n\n let screenPos = mix(screenX, screenY, uniform.uCompareOrientation);\n let dist = (screenPos - uniform.uComparePosition) * uniform.uCompareSide;\n\n if (dist < -uniform.uCompareSoftness) {\n *scale = vec3f(0.0);\n } else if (dist < uniform.uCompareSoftness) {\n let t = smoothstep(-uniform.uCompareSoftness, uniform.uCompareSoftness, dist);\n *scale *= t;\n }\n}\n\nfn modifySplatColor(center: vec3f, color: ptr<function, vec4f>) {\n // No color modification\n}\n",_applyShaders(){const t=this.entity.gsplat;if(!t)return;const e=this.entity.script&&this.entity.script.gsplatRevealBloom,n=this.entity.script&&this.entity.script.gsplatRevealRadial;if(e&&e.enabled&&(e.enabled=!1),n&&n.enabled&&(n.enabled=!1),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?ht:pt;o="wgsl"===n?t+"\n"+o.replace(/fn modifySplatColor\(center: vec3f, color: ptr<function, vec4f>\) \{\s*\/\/ No color modification\s*\}/,"fn modifySplatColor(center: vec3f, color: ptr<function, vec4f>) {\n applyRelighting(center, color);\n}"):t+"\n"+o.replace(/void modifySplatColor\(vec3 center, inout vec4 color\) \{\s*\/\/ No color modification[\s\S]*?\}/,"void modifySplatColor(vec3 center, inout vec4 color) {\n applyRelighting(center, color);\n}"),i._shaderManagedExternally=!0}else o=("wgsl"===n?lt:rt)+o;t.getShaderChunks(n).set("gsplatModifyVS",o),t.update(),t.setParameter("uComparePosition",this.position),t.setParameter("uCompareSide",this.side),t.setParameter("uCompareSoftness",this.softness),t.setParameter("uCompareOrientation",this.orientation),t.setParameter("uCompareVP",new Float32Array([1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]))},_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()}}),Mt=e,e}();o=e.script?.create?.(n)??void 0}return o&&(o.side=n,o.position=Xt,o.orientation="horizontal"===te.orientation?1:0,o.enabled=!0),o||null}function _i(t){Xt=Math.max(0,Math.min(1,t)),Yt&&Yt.updatePosition(Xt),Zt&&Zt.updatePosition(Xt),i.emit("comparePositionChange",{position:Math.round(100*Xt)})}async function Ci(t,n){const o=++ee;if(console.log("[CompareMode] Initializing with after splat:",t),await yi(t,n),o!==ee||vt)return void console.log("[CompareMode] Init cancelled (superseded or destroyed)");const i=Ut.get(t);if(!i)return console.error("[CompareMode] Failed to load after splat:",t),void(Gt=!1);if(i.enabled=!0,Kt=i,Jt=t,ot)Yt=Si(ot,-1);else{console.warn("[CompareMode] Primary splat entity not ready yet — will attach before-clip when available");const t=setInterval(()=>{if(vt||o!==ee)return clearInterval(t),void clearTimeout(e);ot&&!Yt&&(Yt=Si(ot,-1),console.log("[CompareMode] Attached before-clip to primary splat (deferred)"),clearInterval(t),clearTimeout(e))},100),e=setTimeout(()=>clearInterval(t),1e4)}Zt=Si(i,1),jt&&(jt.destroy(),jt=null),e.querySelectorAll(".ss-compare-overlay").forEach(t=>t.remove()),jt=new Rt({container:e,config:te,onPositionChange:t=>{_i(t)}}),console.log("[CompareMode] Initialized successfully")}function Mi(){if(ee++,Yt&&(Yt.enabled=!1,Yt=null),Zt&&(Zt.enabled=!1,Zt=null),Kt){const t=Kt.script?.gsplatRelighting;t&&mt.delete(t),Kt.destroy(),Jt&&Ut.delete(Jt),Kt=null,Jt=null}jt&&(jt.destroy(),jt=null)}function Ei(){const t=xi();if(Bt===t)return;const e=mi();ot&&(ot.enabled=!0,e&&e!==ot?(ci(e,ot,!1),Ut.forEach((n,o)=>{o!==t&&n!==e&&(Dt?pi(n):hi(o))})):(di(ot,!1),Ut.forEach((e,n)=>{n!==t&&(Dt?pi(e):hi(n))}))),Bt=t,Ht=0,i.emit("splatChange",{url:t,isOriginal:!0}),console.log("[SplatSwap] Manually returned to original splat"),vi(void 0,!0)}function Pi(t,e=0){console.log("[SplatSwap] Applying skybox:",t,"rotation:",e),_a(t,e)}let ki=0,Ti=0,Ai=!1,Li=null,Ri=null;const zi=r.waypoints?.reduce((t,e)=>t+(e.duration||2e3),0)||1,Di=r.loopMode;let Ii;Ii=!0===Di?"loop":!1===Di?"none":"loop"===Di||"pingpong"===Di||"none"===Di?Di:"loop";const Fi=(t=Ii,e=r.waypoints?.length||1)=>e<2?1:"loop"===t?e:e-1,Bi=(t,e=Ii)=>"loop"===e?(t%1+1)%1:Math.max(0,Math.min(1,t)),Vi=t=>{const e=r.waypoints?.length||1;if(e<2)return 0;return Math.max(0,Math.min(t,e-1))/Fi(Ii,e)},$i=t=>{const e=r.waypoints?.length||1;if(e<2)return 0;const n=Fi(Ii,e),o=Bi(t)*n,i=Math.round(o);return"loop"===Ii?(i%e+e)%e:Math.max(0,Math.min(e-1,i))},Ui=(t,e,n)=>{if(e===n)return Bi(t,n);const o=r.waypoints?.length||1,i=Fi(e,o),s=Fi(n,o),a=Bi(t,e)*i;return Bi(a/s,n)},Oi=r.waypoints?.length||1,Ni=Math.max(1,20*Fi(Ii,Oi));let Wi=void 0!==r.autoplaySpeed?60*r.autoplaySpeed/Ni:1e3/zi,Hi=1;console.log("[StorySplat Viewer] Playback config:",{loopMode:Ii,playbackSpeed:Wi,totalDuration:zi,autoPlay:r.autoPlay,autoplaySpeed:r.autoplaySpeed,rawLoopMode:r.loopMode});let Gi=null,ji=null;const Xi=.01+.1*(r.transitionSpeed||1);let qi=0,Yi=0;let Zi=!1,Ki=!1,Qi=0,Ji=0;const ts=[],es=[],ns=[],os=r.fov||60;let is=os;function ss(){const t=function(t){if(ts.length<2)return null;const e=an(ts,r.waypoints),n=Fi(Ii,ts.length),o=Qe(e,20,Ii);if(o.length<2)return null;let i=1/0,s=0;for(let e=0;e<o.length-1;e++){const a=o[e],r=o[e+1],l=r.x-a.x,c=r.y-a.y,d=r.z-a.z,p=l*l+c*c+d*d;if(0===p)continue;let h=((t.x-a.x)*l+(t.y-a.y)*c+(t.z-a.z)*d)/p;h<0?h=0:h>1&&(h=1);const u=t.x-(a.x+l*h),m=t.y-(a.y+c*h),g=t.z-(a.z+d*h),f=Math.sqrt(u*u+m*m+g*g);if(f<i){i=f;const t=(a.segmentIndex+a.t)/n;s=t+((r.segmentIndex+r.t)/n-t)*h}}return i===1/0?null:{progress:s,distance:i}}(he.getPosition().clone());t&&(ki=t.progress,Ti=t.progress),Vo("tour")}function as(n){if(!_n||ts.length<2)return;const o=ts.length,s="loop"===Ii,a=s?o:o-1,l=(n=s?(n%1+1)%1:Math.max(0,Math.min(1,n)))*a,c=Math.min(Math.floor(l),a-1),d=l-c,p=an(ts,r.waypoints),h=Ge(p,c,Ii),u=Ke(p,c,d,Ii);if(!h||!u)return;const m=es[h.startIndex],g=es[h.endIndex],f=ns[h.startIndex],y=ns[h.endIndex];Gi=new t.Vec3(u.x,u.y,u.z),ji=new t.Quat,ji.slerp(m,g,d),is=mn(f,y,d);const v=$i(n);if(v!==et){const n=et;et=v;const o=r.waypoints[v],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(ts[v].x,ts[v].y,ts[v].z)),i.emit("waypointChange",{index:v,waypoint:o,prevIndex:n,cameraMode:s}),function(t){if(0===Zs.size)return;Zs.forEach((e,n)=>{const{config:o,slotId:i,assetReady:s,waypointIndex:a}=e;if(!s)return;if(o.triggerMode)return;const r=e.entity.sound?.slot(i);if(r)if(a===t)e.playing||r.isPlaying||(console.log(`[Audio] Waypoint-entry play (legacy): ${n}, wp=${t}`),r.play(),e.playing=!0);else{const t=o.stopOnExit??!0;(e.playing||r.isPlaying)&&t&&(console.log(`[Audio] Waypoint-exit stop (legacy): ${n}, wp=${a}`),r.stop(),e.playing=!1)}})}(v),Ks(),Qs(e);const a=e.querySelector(".storysplat-hotspot-popup");a&&a.classList.remove("visible")}i.emit("progressUpdate",{progress:Math.max(0,Math.min(1,n)),index:et}),function(t){if(!Gs||!Xs)return;if(!nt||"tour"!==Sn)return void(Gs.paused||Gs.pause());const{time:e,shouldPlay:n,volume:o}=Ys(t);if(!n)return void(Gs.paused||Gs.pause());const i=Ws.get(Gs)??1;if(Gs.volume=Ns?0:i*o,Gs.paused)Gs.currentTime=e,Ns||Gs.play().catch(()=>{});else{Math.abs(Gs.currentTime-e)>.5&&(Gs.currentTime=e)}}(Math.max(0,Math.min(1,n))),wi()}function rs(t,e=!1){const n=Math.max(0,Math.min(1,t));Ti=n,Xs&&nt&&Gs&&(fs(),ys()),e?cs(n):(ki=n,as(ki))}r.waypoints&&r.waypoints.length>0&&(r.waypoints.forEach(e=>{const n=e.position?new t.Vec3(e.position.x,e.position.y,-e.position.z):new t.Vec3(0,r.playerHeight||1.6,0);ts.push(n);const o=e.rotation?Jo(e.rotation):new t.Quat;es.push(o);let i=e.fov||os;i<3.5&&(i*=180/Math.PI),ns.push(i)}),ts.length>0&&(Gi=ts[0].clone(),ji=es[0].clone(),is=ns[0])),U.on("update",function(){if(!_n)return;if(!Ai){let t=Ti-ki;"loop"===Ii&&(t>.5?t-=1:t<-.5&&(t+=1)),Math.abs(t)>1e-4&&(ki+=t*Xi,"loop"===Ii&&(ki=(ki%1+1)%1),as(ki))}if(!Gi||!ji)return;const e=he.getPosition(),n=new t.Vec3;n.lerp(e,Gi,Xi),he.setPosition(n.x,n.y,n.z);const o=he.camera;if(o&&ns.length>0){const t=mn(o.fov,is,Xi);o.fov=t}Zi||(qi*=.95,Yi*=.95,Math.abs(qi)<.01&&(qi=0),Math.abs(Yi)<.01&&(Yi=0));const i=new t.Quat;i.setFromEulerAngles(Yi,qi,0);const s=new t.Quat;s.mul2(ji,i);const a=he.getRotation(),r=new t.Quat;r.slerp(a,s,Xi),he.setRotation(r)});let ls=500*(r.transitionSpeed||1);function cs(t,e=ls){null!==Li&&(cancelAnimationFrame(Li),Li=null);const n=ki;let o=t-n;"loop"===Ii&&(o>.5?o-=1:o<-.5&&(o+=1));const i=performance.now();Ai=!0;const s=()=>{const t=performance.now()-i,a=Math.min(t/e,1);let r=n+o*(a<.5?2*a*a:(4-2*a)*a-1);"loop"===Ii&&(r=(r%1+1)%1),ki=r,as(ki),a<1?Li=requestAnimationFrame(s):(Li=null,Ai=!1,Ri=null)};Li=requestAnimationFrame(s)}function ds(t){if(!r.waypoints||t<0||t>=r.waypoints.length)return;if(!_n)return;const e=Vi(t);Ti=e,cs(e)}function ps(){if(!r.waypoints||0===r.waypoints.length)return;nt&&Ss();const t=r.scrollButtonMode||"waypoint";if("percentage"===t||"continuous"===t||"incremental"===t){const t=r.scrollAmount||10;let e=Ti+t/100;e>1&&(e="loop"===Ii?0:1),Ti=e,cs(e)}else{let t=(null!==Ri?Ri:et)+1;t>=r.waypoints.length&&(t="loop"===Ii?0:r.waypoints.length-1),Ri=t,ds(t)}}function hs(){if(!r.waypoints||0===r.waypoints.length)return;nt&&Ss();const t=r.scrollButtonMode||"waypoint";if("percentage"===t||"continuous"===t||"incremental"===t){const t=r.scrollAmount||10;let e=Ti-t/100;e<0&&(e="loop"===Ii?1:0),Ti=e,cs(e)}else{let t=(null!==Ri?Ri:et)-1;t<0&&(t="loop"===Ii?r.waypoints.length-1:0),Ri=t,ds(t)}}let us=!1,ms=null;const gs=1e3;function fs(){Xs&&Gs&&nt&&(us=!0,Gs.paused||Gs.pause(),ms&&(clearTimeout(ms),ms=null))}function ys(){us&&(ms&&clearTimeout(ms),ms=setTimeout(()=>{if(ms=null,us=!1,Gs&&Xs&&nt&&"tour"===Sn){const{time:t,shouldPlay:e}=Ys(ki);e&&(Gs.currentTime=t,Ns||Gs.play().catch(()=>{}))}},gs))}let vs=0,bs=null;function xs(t){if(!nt)return;0===vs&&(vs=t);const e=(t-vs)/1e3;vs=t;const n=Xs&&Gs&&!Gs.paused&&!us?js.find(t=>{const e=100*ki;return e>=t.progressStart&&e<=t.progressEnd}):null;if(n&&Gs){const t=n.audioEnd-n.audioStart;if(t>0){const e=(Gs.currentTime-n.audioStart)/t,o=(n.progressEnd-n.progressStart)/100,i=n.progressStart/100+e*o;ki=Math.max(0,Math.min(1,i)),Ti=ki}}else{const t=(Xs?function(t,e){if(!Xs||!js.length)return e;const n=100*t,o=js.find(t=>n>=t.progressStart&&n<=t.progressEnd);if(!o)return e;const i=(o.progressEnd-o.progressStart)/100,s=o.audioEnd-o.audioStart;return s<=0?e:i/s}(ki,Wi):Wi)*e*Hi;ki+=t,Ti+=t}if(ki>=1)switch(Ii){case"loop":ki%=1,Ti%=1;break;case"pingpong":ki=1,Ti=1,Hi=-1;break;default:return ki=1,Ti=1,Ss(),void i.emit("playbackComplete")}else if(ki<=0)if("pingpong"===Ii)ki=0,Ti=0,Hi=1;else ki=0,Ti=0;as(ki),bs=requestAnimationFrame(xs)}function ws(){if(At)return At.play(),void i.emit("playbackStart");nt||!r.waypoints||r.waypoints.length<2||(nt=!0,vs=0,Hi=1,i.emit("playbackStart"),bs=requestAnimationFrame(xs))}function Ss(){if(At)return At.pause(),void i.emit("playbackStop");nt=!1,bs&&(cancelAnimationFrame(bs),bs=null),us=!1,ms&&(clearTimeout(ms),ms=null),Gs&&!Gs.paused&&Gs.pause(),i.emit("playbackStop")}const _s=U.graphicsDevice.canvas;_s.addEventListener("wheel",t=>{if(!_n)return;t.preventDefault(),Xs&&nt&&Gs&&(fs(),ys());const e=r.scrollSpeed||.1,n=r.scrollAmount||100,o=r.waypoints?.length||2,i="loop"===Ii?o:o-1,s=Math.max(20,20*i),a=100*(Math.abs(t.deltaY)/100)*e*(n/100)/s,l=t.deltaY>0?a:-a;Ti="loop"===Ii?((Ti+l)%1+1)%1:Math.max(0,Math.min(1,Ti+l))},{passive:!1}),_s.addEventListener("pointerdown",t=>{_n&&!Ki&&(Zi=!0,Qi=t.clientX,Ji=t.clientY)},{capture:!0}),_s.addEventListener("pointermove",t=>{if(!_n||!Zi||Ki)return;const e=t.clientX-Qi,n=t.clientY-Ji;Qi=t.clientX,Ji=t.clientY;qi+=.3*-e,Yi+=.3*-n,Yi=Math.max(-60,Math.min(60,Yi))},{capture:!0}),_s.addEventListener("pointerup",()=>{Zi=!1},{capture:!0}),_s.addEventListener("pointerleave",()=>{Zi=!1},{capture:!0});const Cs=[],Ms=[],Es=new Map,Ps=new Float32Array([0,0,0,0]);let ks=!1,Ts=!1;U.graphicsDevice.scope.resolve("uMirrorClipPlane").setValue(Ps);const As=new t.Layer({name:"MirrorMeshLayer"}),Ls=new t.Layer({name:"HotspotMeshLayer"}),Rs=U.scene.layers.getOpaqueIndex(U.scene.layers.getLayerById(t.LAYERID_WORLD));if(Rs>=0?(U.scene.layers.insert(As,Rs+1),U.scene.layers.insert(Ls,Rs+2)):(U.scene.layers.push(As),U.scene.layers.push(Ls)),he.camera){const Jl=[...he.camera.layers],tc=Jl.indexOf(t.LAYERID_WORLD);tc>=0?Jl.splice(tc+1,0,As.id,Ls.id):Jl.push(As.id,Ls.id),he.camera.layers=Jl}function zs(e,n){const o=e.id||`mirror-${Date.now()}-${n}`,i=e.resolution??.5,s=e.intensity??1,a=e.tint||"#ffffff",r=parseInt(a.slice(1,3),16)/255,l=parseInt(a.slice(3,5),16)/255,c=parseInt(a.slice(5,7),16)/255,d=new t.Entity(o);d.setPosition(e.position.x,e.position.y,-e.position.z);const p=(e.rotation?.x??0)*(180/Math.PI),h=(e.rotation?.y??0)*(180/Math.PI),u=-(e.rotation?.z??0)*(180/Math.PI);d.setEulerAngles(p,h,u),d.setLocalScale(e.scale?.x??2,e.scale?.y??1,e.scale?.z??2);const m=new t.Entity(`${o}-mesh`);m.setLocalEulerAngles(90,0,0),m.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1,layers:[As.id]}),d.addChild(m);const g=Math.max(64,Math.floor(U.graphicsDevice.width*i)),f=Math.max(64,Math.floor(U.graphicsDevice.height*i)),y=new t.Texture(U.graphicsDevice,{width:g,height:f,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}),v=new t.RenderTarget({colorBuffer:y,depth:!0}),b=new t.Entity(`${o}-reflcam`);b.addComponent("camera",{priority:-100-n,renderTarget:v,layers:[t.LAYERID_WORLD,t.LAYERID_SKYBOX],flipFaces:!0}),U.root.addChild(b);const x=new t.ShaderMaterial({uniqueName:`MirrorShader_${o}`,vertexGLSL:"\n attribute vec3 aPosition;\n attribute vec3 aNormal;\n uniform mat4 matrix_model;\n uniform mat4 matrix_viewProjection;\n varying vec4 vScreenPos;\n varying vec3 vWorldNormal;\n varying vec3 vWorldPos;\n void main() {\n vec4 worldPos = matrix_model * vec4(aPosition, 1.0);\n vWorldPos = worldPos.xyz;\n vWorldNormal = normalize((matrix_model * vec4(aNormal, 0.0)).xyz);\n gl_Position = matrix_viewProjection * worldPos;\n vScreenPos = gl_Position;\n }\n ",fragmentGLSL:"\n precision highp float;\n varying vec4 vScreenPos;\n varying vec3 vWorldNormal;\n varying vec3 vWorldPos;\n uniform sampler2D uReflectionMap;\n uniform float uIntensity;\n uniform vec3 uTint;\n uniform vec3 uCameraPos;\n void main() {\n // Check if viewing the back face — show black (non-reflective side)\n vec3 viewDir = normalize(uCameraPos - vWorldPos);\n vec3 normal = normalize(vWorldNormal);\n float facing = dot(viewDir, normal);\n if (facing < 0.0) {\n gl_FragColor = vec4(0.02, 0.02, 0.02, 1.0);\n return;\n }\n // Screen-space UVs from clip coordinates with horizontal flip\n vec2 screenUV = vScreenPos.xy / vScreenPos.w * 0.5 + 0.5;\n screenUV.x = 1.0 - screenUV.x;\n vec4 reflColor = texture2D(uReflectionMap, screenUV);\n // Fresnel: stronger reflection at grazing angles\n float fresnel = 1.0 - abs(facing);\n fresnel = mix(0.6, 1.0, fresnel * fresnel);\n vec3 finalColor = reflColor.rgb * uTint * uIntensity * fresnel;\n gl_FragColor = vec4(finalColor, 1.0);\n }\n ",attributes:{aPosition:t.SEMANTIC_POSITION,aNormal:t.SEMANTIC_NORMAL}});x.setParameter("uReflectionMap",y),x.setParameter("uIntensity",s),x.setParameter("uTint",[r,l,c]),x.setParameter("uCameraPos",[0,0,0]),x.cull=t.CULLFACE_NONE,x.depthTest=!0,x.depthWrite=!0,x.update(),m.render&&m.render.meshInstances.forEach(t=>{t.material=x}),d._mirrorMaterial=x,d._mirrorReflCam=b,d._mirrorRenderTarget=v,d._mirrorReflTexture=y,d._mirrorData=e;const w=new t.Vec3,S=new t.Vec3,_=new t.Vec3,C=new t.Vec3,M=new t.Mat4,E=new Float32Array(4),P=()=>{if(!d.enabled)return;const t=he.camera,e=b.camera;e.fov=t.fov,e.nearClip=t.nearClip,e.farClip=t.farClip;const n=d.getPosition(),o=d.forward;w.set(-o.x,-o.y,-o.z);const i=w.dot(n);M.setReflection(w,-i);const s=he.getPosition();M.transformPoint(s,S),b.setPosition(S),_.copy(s).add(he.forward),M.transformPoint(_,_);const a=he.up;C.set(a.x,a.y,a.z),M.transformVector(C,C),b.lookAt(_,C),e.calculateProjection=t=>{const n=U.graphicsDevice.width/U.graphicsDevice.height;t.setPerspective(e.fov,n,e.nearClip,e.farClip),E[0]=w.x,E[1]=w.y,E[2]=w.z,E[3]=i,U.graphicsDevice.scope.resolve("uMirrorClipPlane").setValue(E);const o=b.camera.viewMatrix.data,s=o[0]*w.x+o[4]*w.y+o[8]*w.z,a=o[1]*w.x+o[5]*w.y+o[9]*w.z,r=o[2]*w.x+o[6]*w.y+o[10]*w.z,l=S.x*w.x+S.y*w.y+S.z*w.z-i;if(r>=0)return;const c=t.data,d=s*((Math.sign(s)+c[8])/c[0])+a*((Math.sign(a)+c[9])/c[5])+-1*r+l*((1+c[10])/c[14]);if(Math.abs(d)<1e-6)return;const p=1/d;c[2]=s*p,c[6]=a*p,c[10]=r*p,c[14]=l*p},x.setParameter("uCameraPos",[s.x,s.y,s.z]),function(){const t=U.scene.gsplat?.material;if(!t)return;const e=U.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl",o=t.getShaderChunks(n);o.has("gsplatModifyVS")||(o.set("gsplatModifyVS","wgsl"===n?dt:ct),t.update());ks||(e.scope.resolve("uMirrorClipPlane").setValue(Ps),ks=!0)}(),function(){if(Ts||!he.camera)return;Ts=!0;const t=he.camera,e=t.calculateProjection;t.calculateProjection=n=>{if(e)e(n);else{const e=U.graphicsDevice.width/U.graphicsDevice.height;n.setPerspective(t.fov,e,t.nearClip,t.farClip)}U.graphicsDevice.scope.resolve("uMirrorClipPlane").setValue(Ps)}}()};return U.on("prerender",P),Es.set(o,()=>{U.off("prerender",P)}),nn.addChild(d),Ms.push(d),console.log(`[StorySplat Viewer] Created mirror plane: ${e.name||o} (resolution: ${i}, intensity: ${s})`),d}function Ds(t){const e=Es.get(t);e&&(e(),Es.delete(t));const n=ol.get(t)||Ms.find(e=>e.name===t);if(n){const e=n._mirrorReflCam,o=n._mirrorRenderTarget,i=n._mirrorReflTexture,s=n._mirrorMaterial;e&&e.destroy(),o&&o.destroy(),i&&i.destroy(),s&&s.destroy(),n.destroy(),ol.delete(t);const a=Ms.indexOf(n);a>=0&&Ms.splice(a,1)}}const Is=[],Fs=D.portalPopup;let Bs=null;const Vs=()=>{Fs&&(Fs.classList.remove("visible"),Bs=null)};if(Fs){const ec=Fs.querySelector(".storysplat-portal-popup-confirm"),nc=Fs.querySelector(".storysplat-portal-popup-cancel");ec?.addEventListener("click",()=>{if(Bs){const t=Bs;Vs(),_r(t)}}),nc?.addEventListener("click",()=>{Vs()})}function $s(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 Us=[],Os=[];let Ns=!1;const Ws=new Map;function Hs(t,e){t&&(t.on("play",()=>e(!0)),t.on("resume",()=>e(!0)),t.on("pause",()=>e(!1)),t.on("stop",()=>e(!1)),t.on("end",()=>e(!1)))}let Gs=null,js=r.narrationTrack?.segments||[],Xs=R;function qs(t){if(Gs){Gs.pause(),Gs.src="";const t=Os.indexOf(Gs);t>=0&&Os.splice(t,1),Ws.delete(Gs),Gs=null}js=t.segments||[],Xs=t.enabled&&!!t.audioUrl,Xs&&t.audioUrl&&(Gs=new Audio(t.audioUrl),Gs.preload="auto",Gs.volume=t.volume??1,Os.push(Gs),Ws.set(Gs,Gs.volume))}function Ys(t){const e=100*t,n=js.find(t=>e>=t.progressStart&&e<=t.progressEnd);if(!n)return{time:0,shouldPlay:!1,volume:1};const o=n.progressEnd-n.progressStart;if(o<=0)return{time:n.audioStart,shouldPlay:!0,volume:n.volume??1};const i=(e-n.progressStart)/o;return{time:n.audioStart+i*(n.audioEnd-n.audioStart),shouldPlay:!0,volume:n.volume??1}}Xs&&r.narrationTrack&&qs(r.narrationTrack);const Zs=new Map;function Ks(){Cs.forEach(t=>{const e=t.hotspotAudioSlotId;if(e){const n=t.hotspotData;if("autoplay"===n?.audioTriggerMode)return;const o=t.sound?.slot(e);o&&(o.isPlaying||o.isPaused)&&(o.stop(),t.hotspotAudioPlaying=!1)}if(t.wasInProximity=!1,t._audioHoverPlaying=!1,t.videoElement)if("autoplay"===t.mediaTriggerMode);else{const e=t.videoElement;e.paused||e.pause(),t.isVideoPlaying=!1}t.wasInProximity=!1})}function Qs(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 Js=new Map,ta=new Map,ea=new Set(["flare","circle","spark","rain","smoke","snowflake"]);function na(e){const n=ta.get(e);if(n)return n;const o=128,i=document.createElement("canvas");i.width=o,i.height=o;const s=i.getContext("2d");if(!s)throw new Error("Could not get 2D canvas context for particle texture");!function(t,e,n){switch(e){case"circle":{const e=t.createRadialGradient(n/2,n/2,0,n/2,n/2,n/2);return e.addColorStop(0,"rgba(255,255,255,1)"),e.addColorStop(.5,"rgba(255,255,255,0.5)"),e.addColorStop(1,"rgba(255,255,255,0)"),t.fillStyle=e,void t.fillRect(0,0,n,n)}case"flare":{const e=t.createRadialGradient(n/2,n/2,0,n/2,n/2,n/2);return e.addColorStop(0,"rgba(255,255,255,1)"),e.addColorStop(.1,"rgba(255,250,220,0.95)"),e.addColorStop(.35,"rgba(255,200,110,0.5)"),e.addColorStop(1,"rgba(255,150,50,0)"),t.fillStyle=e,void t.fillRect(0,0,n,n)}case"spark":{t.translate(n/2,n/2);const e=t.createRadialGradient(0,0,0,0,0,n/3);e.addColorStop(0,"rgba(255,255,255,1)"),e.addColorStop(.25,"rgba(255,255,255,0.55)"),e.addColorStop(1,"rgba(255,255,255,0)"),t.fillStyle=e,t.fillRect(-n/2,-n/2,n,n),t.globalCompositeOperation="lighter";const o=(e,n,o)=>{t.save(),t.rotate(e);const i=t.createLinearGradient(-n,0,n,0);i.addColorStop(0,"rgba(255,255,255,0)"),i.addColorStop(.5,"rgba(255,255,255,1)"),i.addColorStop(1,"rgba(255,255,255,0)"),t.fillStyle=i,t.fillRect(-n,-o/2,2*n,o),t.restore()};return o(0,n/2,2),o(Math.PI/2,n/2,2),o(Math.PI/4,n/3,1.5),void o(-Math.PI/4,n/3,1.5)}case"rain":{const e=t.createLinearGradient(0,0,0,n);e.addColorStop(0,"rgba(210,230,255,0)"),e.addColorStop(.5,"rgba(225,240,255,1)"),e.addColorStop(1,"rgba(210,230,255,0)"),t.fillStyle=e;const o=Math.max(4,n/32);t.fillRect(n/2-o/2,0,o,n),t.globalCompositeOperation="destination-in";const i=t.createLinearGradient(0,0,n,0);return i.addColorStop(0,"rgba(255,255,255,0)"),i.addColorStop(.5,"rgba(255,255,255,1)"),i.addColorStop(1,"rgba(255,255,255,0)"),t.fillStyle=i,void t.fillRect(0,0,n,n)}case"smoke":{const e=t.createRadialGradient(n/2,n/2,0,n/2,n/2,n/2);e.addColorStop(0,"rgba(255,255,255,0.8)"),e.addColorStop(.3,"rgba(255,255,255,0.4)"),e.addColorStop(.7,"rgba(255,255,255,0.15)"),e.addColorStop(1,"rgba(255,255,255,0)"),t.fillStyle=e,t.fillRect(0,0,n,n);const o=[[.4*n,.45*n,.28*n,.18],[.6*n,.55*n,.24*n,.14],[.5*n,.35*n,.18*n,.1]];for(const[e,i,s,a]of o){const o=t.createRadialGradient(e,i,0,e,i,s);o.addColorStop(0,`rgba(255,255,255,${a})`),o.addColorStop(1,"rgba(255,255,255,0)"),t.fillStyle=o,t.fillRect(0,0,n,n)}return}case"snowflake":{t.translate(n/2,n/2);const e=t.createRadialGradient(0,0,0,0,0,n/2);e.addColorStop(0,"rgba(255,255,255,0.35)"),e.addColorStop(.5,"rgba(255,255,255,0.08)"),e.addColorStop(1,"rgba(255,255,255,0)"),t.fillStyle=e,t.fillRect(-n/2,-n/2,n,n),t.strokeStyle="rgba(255,255,255,0.95)",t.lineCap="round";const o=.42*n;for(let e=0;e<6;e++)t.save(),t.rotate(e*Math.PI/3),t.lineWidth=3,t.beginPath(),t.moveTo(0,0),t.lineTo(o,0),t.stroke(),t.lineWidth=2,t.beginPath(),t.moveTo(.55*o,0),t.lineTo(.75*o,.22*o),t.moveTo(.55*o,0),t.lineTo(.75*o,.22*-o),t.stroke(),t.restore();return}default:{const e=t.createRadialGradient(n/2,n/2,0,n/2,n/2,n/2);e.addColorStop(0,"rgba(255,255,255,1)"),e.addColorStop(1,"rgba(255,255,255,0)"),t.fillStyle=e,t.fillRect(0,0,n,n)}}}(s,e,o);const a=new t.Texture(U.graphicsDevice,{name:`particle-${e}`,width:o,height:o,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});return a.setSource(i),ta.set(e,a),a}function oa(e,n){return new Promise((o,i)=>{const s=ta.get(e);if(s)return void o(s);const a=new t.Asset(e,"texture",{url:n});a.on("load",()=>{if(vt)return void i(new Error("Viewer destroyed"));const t=a.resource;ta.set(e,t),o(t)}),a.on("error",t=>{console.error(`[Particle] Failed to load custom texture: ${e}`,t),i(t)}),U.assets.add(a),U.assets.load(a)})}function ia(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,b=a.y||0,x=-(a.z||0),w=v*h,S=b*h,_=x*h,C=180/Math.PI,M=(e.minAngularSpeed??0)*C,E=(e.maxAngularSpeed??e.angularSpeed??0)*C,P=(e.minInitialRotation??0)*C,k=(e.maxInitialRotation??0)*C,T=.1*(e.minSize??.1),A=.1*(e.maxSize??.5),L=e.minScaleX??1,R=e.maxScaleX??1,z=e.minScaleY??1,D=e.maxScaleY??1,I=e.minEmitPower??1,F=e.maxEmitPower??2,B=d.x,V=d.y,$=-d.z,U=p.x,O=p.y,N=-p.z,W=u===t.EMITTERSHAPE_SPHERE,H=e.emitRate||e.rate||50,G=1/H,j=Math.ceil(H*h*2);n.addComponent("particlesystem",{numParticles:e.numParticles||Math.max(j,100),lifetime:h,rate:G,emitterShape:u,emitterExtents:m,emitterRadius:e.emitterRadius||.1,startAngle:P,startAngle2:k,...W?{radialSpeedGraph:new t.Curve([0,I]),radialSpeedGraph2:new t.Curve([0,F])}:{localVelocityGraph:new t.CurveSet([[0,B*I],[0,V*I],[0,$*I]]),localVelocityGraph2:new t.CurveSet([[0,U*F],[0,O*F],[0,N*F]])},velocityGraph:new t.CurveSet([[0,0,1,w],[0,0,1,S],[0,0,1,_]]),scaleGraph:new t.Curve([0,T*Math.min(L,z)]),scaleGraph2:new t.Curve([0,A*Math.max(R,D)]),rotationSpeedGraph:new t.Curve([0,M]),...E!==M?{rotationSpeedGraph2:new t.Curve([0,E])}:{},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,layers:[En.id]}),n.particlesystem&&(n.particlesystem.localSpace=e.localSpace??!1),n.setPosition(s.x+g.x,s.y+g.y,-s.z+g.z);const X=e.renderingGroupId??3;return n.particlesystem&&void 0!==X&&(n.particlesystem.drawOrder=X),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}, ${b}, ${x}), Lifetime: ${h}s`),console.log(`[Particle] Colors: start=${JSON.stringify(r)}, mid=${JSON.stringify(l)}, dead=${JSON.stringify(c)}`),console.log(`[Particle] Angular speed: ${M.toFixed(1)} - ${E.toFixed(1)} deg/s, Initial rotation: ${P.toFixed(1)} - ${k.toFixed(1)} deg`),console.log(`[Particle] Scale: ${T}*${L} - ${A}*${D}, EmitPower: ${I}-${F}, RenderingGroupId: ${X}`),console.log(`[Particle] Rate: ${H} particles/sec → PC rate=${G.toFixed(4)}s/particle, numParticles=${e.numParticles||Math.max(j,100)}`),n}const sa=new Map;function aa(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 ra(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 la(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);o.tags.add("custom-mesh");const 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(ur(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(),a=s.startsWith("blob:")?e.modelName||e.name||"":s.split("?")[0].split("#")[0],r=a.toLowerCase(),l=[".splat",".ply",".sog",".spz",".compressed.ply"].some(t=>r.endsWith(t)),c=l?"gsplat":"container";console.log("[CustomMesh] Loading model from URL:",s,"| format:",c,"| detected from:",a);const d=new t.Asset("mesh-model-"+n,c,{url:s});U.assets.add(d);const p=e.id||`mesh-${n}`,h={entity:o,config:e,modelAsset:d,audioAssetReady:!1,isAnimPlaying:!1,audioPlaying:!1};return sa.set(p,h),l?(d.ready(()=>{sa.has(p)?(console.log("[CustomMesh] Splat loaded as custom mesh:",e.name),o.addComponent("gsplat",{asset:d,unified:!0})):console.log("[CustomMesh] Entity destroyed before splat loaded, skipping:",e.name)}),d.on("error",t=>{console.error("[CustomMesh] Failed to load splat mesh:",e.name,t),U.assets.remove(d)}),U.assets.load(d)):(d.ready(n=>{try{if(!sa.has(p))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 r=0;function l(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&&l(e)})}function c(e){e.render&&(e.render.layers=[Ls.id]),e.children&&e.children.forEach(e=>{e instanceof t.Entity&&c(e)})}if(s.forEach(()=>{r++}),console.log("[CustomMesh] Enabled all children for:",e.name,"- Total nodes:",r),l(s),c(s),"animated"===e.opacityMode&&e.opacityAnimation){const u=e.opacityAnimation.startOpacity??1;ma(o,u),console.log("[CustomMesh] Applied initial animated opacity:",u,"to",e.name)}else void 0!==e.opacity&&e.opacity<1&&(ma(o,e.opacity),console.log("[CustomMesh] Applied static opacity:",e.opacity,"to",e.name));if(e.billboard){const m=o.getEulerAngles().clone();o._billboardActive=!e.billboardRange,U.on("update",()=>{if(!o.enabled)return;if(!o._billboardActive)return void o.setEulerAngles(m.x,m.y,m.z);const t=he.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 d=(i?.animations?.length??0)>0;if(console.log("[CustomMesh] Asset resource for",e.name,":",{hasAnimations:d,animationCount:i?.animations?.length||0,animations:i?.animations?.map(t=>t?.name||t?.resource?.name||"unnamed")||[],interactionConfig:e.interaction}),d||e.interaction&&e.interaction.playModelAnimation){const g=[],f=s.anim;if(f&&(console.log("[CustomMesh] Found modelEntity.anim component - using simple approach"),g.push({type:"pc-anim",component:f,modelEntity:s})),0===g.length){function y(e,n=0){e.anim&&!g.find(t=>t.component===e.anim)&&(g.push({type:"anim",component:e.anim,node:e}),console.log("[CustomMesh] Found existing ANIM component on:",e.name)),e.animation&&(g.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&&y(e,n+1)})}y(s)}if(0===g.length&&d){const v=i?.animations||[];console.log("[CustomMesh] No anim component found - falling back to GLB skeleton approach"),console.log("[CustomMesh] GLB has",v.length,"embedded animations");try{g.push({type:"glb-skeleton",modelEntity:s,asset:n,animations:v,animationNames:v.map(t=>t.resource?.name||t.name||"Animation"),isPlaying:!1,currentTime:0}),console.log("[CustomMesh] GLB skeleton animations stored:",v.map(t=>t.resource?.name||t.name))}catch(b){console.error("[CustomMesh] Error setting up GLB animations:",b)}}if(g.length>0){h.allAnimComponents=g,h.animComponent=g[0].component,console.log("[CustomMesh] Total animation components for",e.name,":",g.length,"- Types:",g.map(t=>t.type).join(", "));const x=e.interaction?.animationAutoPlay;x&&(g.forEach(t=>{aa(t)}),h.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);if(t){t.asset=r.id,Hs(t,t=>{o.audioPlaying=t});const s=i.audioTriggerMode||i.activationMode||"click";if("autoplay"!==s||t.isPlaying||(t.play(),o.audioPlaying=!0),"proximity"===s&&!t.isPlaying){he.getPosition().distance(e.getPosition())<=(i.audioProximityRadius??i.audioMaxDistance??1)&&(t.play(),o.audioPlaying=!0,console.log("[CustomMesh] Audio started (late proximity):",n.name))}}o.audioAssetReady=!0,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,h),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=he.camera.screenToWorld(a,r,he.camera.nearClip),c=he.camera.screenToWorld(a,r,he.camera.farClip),d=(new t.Vec3).sub2(c,l).normalize(),p=e.modelEntity;if(p&&ca(p,l,d))return!0;if(ca(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,p=!!(("click"===r||"hover"===r)&&n.interaction?.triggerUIPopup||("click"===l||"hover"===l)&&n.interaction?.playAudio||("click"===c||"hover"===c)&&n.interaction?.playModelAnimation||("click"===d||"hover"===d)&&n.interaction?.triggerDirectLink);if(!p)return;let h=!1;const u=()=>{if(s.style.cursor="pointer",Wr=!0,Kn=!0,"hover"===r&&n.interaction?.triggerUIPopup&&pa(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=>aa(t)),o.isAnimPlaying=!0,console.log("[CustomMesh] Playing animation on hover for:",n.name))}"hover"===d&&n.interaction?.triggerDirectLink&&ha(n)},m=()=>{if(s.style.cursor="",Wr=!1,Kn=!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=>ra(t)),o.isAnimPlaying=!1,console.log("[CustomMesh] Paused animation on hover out for:",n.name))}},g=()=>{if(console.log("[CustomMesh] Clicked mesh:",n.name),"click"===r&&n.interaction?.triggerUIPopup&&pa(n),"click"===c&&n.interaction?.playModelAnimation){const t=o.allAnimComponents;t&&t.length>0&&(o.isAnimPlaying?(t.forEach(t=>ra(t)),o.isAnimPlaying=!1,console.log("[CustomMesh] Paused",t.length,"animations on click for:",n.name)):(t.forEach(t=>aa(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),i=n.interaction?.audioClickBehavior||"stop-restart";t&&(t.isPlaying?("stop-restart"===i?(t.stop(),console.log("[CustomMesh] Stopped audio on click for:",n.name)):(t.pause(),console.log("[CustomMesh] Paused audio on click for:",n.name)),o.audioPlaying=!1):("pause-resume"===i&&t.isPaused?(t.resume(),console.log("[CustomMesh] Resumed audio on click for:",n.name)):(t.play(),console.log("[CustomMesh] Playing audio on click for:",n.name)),o.audioPlaying=!0))}"click"===d&&n.interaction?.triggerDirectLink&&ha(n)},f=t=>{a(t.clientX,t.clientY)&&g()},y=t=>{const e=a(t.clientX,t.clientY);e&&!h?(h=!0,u()):!e&&h&&(h=!1,m())},v=()=>{h&&(h=!1,m())};s.addEventListener("click",f),s.addEventListener("mousemove",y),s.addEventListener("mouseleave",v),e.meshClickHandler=f,e.meshHoverHandler=y,e.meshLeaveHandler=v}(o,e,h),console.log("[CustomMesh] Mesh fully initialized:",e.name)}catch(w){console.error("[CustomMesh] Error processing loaded mesh:",e.name,w)}}),d.on("error",t=>{console.error("[CustomMesh] Failed to load mesh:",e.name,t),U.assets.remove(d)}),U.assets.load(d)),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},nn.addChild(o),o}function ca(e,n,o){const i=e.render;if(i&&i.meshInstances)for(const t of i.meshInstances)if(t.aabb){if(da(n,o,t.aabb))return!0}const s=e.model;if(s&&s.meshInstances)for(const t of s.meshInstances)if(t.aabb){if(da(n,o,t.aabb))return!0}const a=e.children;if(a)for(const e of a)if(e instanceof t.Entity&&ca(e,n,o))return!0;return!1}function da(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 pa(t){if(!t.interaction)return;const e={id:`mesh-content-${t.id}`,title:t.interaction.title||t.name,information:t.interaction.information,photoUrl:t.interaction.photoUrl,iframeUrl:t.interaction.iframeUrl,externalLinkUrl:t.interaction.externalLinkUrl,externalLinkText:t.interaction.externalLinkText,backgroundColor:t.interaction.backgroundColor||"#000000",textColor:t.interaction.textColor||"#ffffff"};D.showHotspotPopup?D.showHotspotPopup(e):function(t){const e=document.querySelector(".storysplat-mesh-popup");e&&e.remove();const 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=`× ${x(u,"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 ha(t){t.interaction?.triggerDirectLink&&t.interaction.directLinkUrl&&window.open(t.interaction.directLinkUrl,"_blank")}function ua(t,e,n){return e<=n?t>=e&&t<=n:"loop"===Ii&&(t>=e||t<=n)}function ma(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 ga(){const t=U.graphicsDevice.canvas;sa.forEach(e=>{const n=e.entity,o=n.meshClickHandler;o&&t.removeEventListener("click",o),n.destroy()}),sa.clear()}i.on("progressUpdate",()=>{!function(){const t=100*ki,e=$i(ki);sa.forEach(n=>{const{entity:o,config:i}=n,s=o.visibilityRange;if(s){let n=!0;"waypoint"===s.type?n=ua(e,s.start,s.end):"percentage"===s.type&&(n=ua(t,s.start,s.end)),o.enabled=n}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);ma(o,e.startOpacity+(e.endOpacity-e.startOpacity)*n)}}if(i.billboard&&i.billboardRange){const n=i.billboardRange;let s=!1;"percentage"===n.type?s=t>=n.start&&t<=n.end:"waypoint"===n.type&&(s=e>=n.start&&e<=n.end),o._billboardActive=s}else i.billboard&&(o._billboardActive=!0)})}()});let fa=null,ya=null;const va=r.skybox?.url||r.skyboxUrl||null,ba=r.skybox?.rotation??r.skyboxRotation??0;function xa(e){const n=r.skybox?.url||r.skyboxUrl;if(!n)return void console.log("[StorySplat Viewer] No skybox configured");const o=r.skybox?.rotation??r.skyboxRotation??0,i=r.skybox?.intensity??1,s=r.skybox?.enableIBL??!0;if(console.log("[StorySplat Viewer] Creating skybox:",n,"rotation:",o,"rad =",o*(180/Math.PI),"deg","IBL:",s),fo())return lo=()=>xa(e),void Le("Deferring skybox init while 8th Wall AR is active");const a=e??new Image,l=ln(),c=()=>{if(!vt){if(fo())return lo=c,void Le("Deferring skybox apply while 8th Wall AR is active");if(wa(),l)return console.log("[StorySplat Viewer] Mobile detected — using sphere skybox (native pipeline skipped)"),void Sa(n,a,o,i,s);try{const e=new t.Texture(U.graphicsDevice,{width:a.width,height:a.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(a);const n=Math.min(a.width/4,2048),r=new t.Texture(U.graphicsDevice,{width:n,height:n,cubemap:!0,mipmaps:!1,format:t.PIXELFORMAT_RGBA8,minFilter:t.FILTER_LINEAR,magFilter:t.FILTER_LINEAR});t.reprojectTexture(e,r,{numSamples:1024});const l=2*n,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=i,0!==o){const e=new t.Quat;e.setFromEulerAngles(0,-o*(180/Math.PI),0),U.scene.skyboxRotation=e}s&&(U.scene.ambientLight=new t.Color(.3*i,.3*i,.35*i),console.log("[StorySplat Viewer] IBL ambient lighting applied:",i)),console.log("[StorySplat Viewer] Native skybox applied successfully")}catch(t){console.warn("[StorySplat Viewer] Native skybox pipeline failed, using sphere fallback:",t),Sa(n,a,o,i,s)}}};e?c():(a.crossOrigin="anonymous",a.onload=c,a.onerror=t=>{console.error("[StorySplat Viewer] Failed to load skybox texture:",n,t)},a.src=n)}function wa(){if(!vt&&fo())return lo=wa,void Le("Deferring skybox cleanup while 8th Wall AR is active");lo=null,U.scene.envAtlas=null,fa&&(fa.destroy(),fa=null),ya&&(U.scene.off("prerender",ya),ya=null)}function Sa(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),ya=t=>{const e=t.entity.getPosition();a.setPosition(e.x,e.y,e.z)},U.scene.on("prerender",ya),nn.addChild(a),fa=a,s&&(U.scene.ambientLight=new t.Color(.3*i,.3*i,.35*i)),console.log("[StorySplat Viewer] Skybox fallback sphere applied")}function _a(t,e,n){t?(r.skybox={url:t,rotation:e??0},r.skyboxUrl=t,r.skyboxRotation=e??0,xa(n)):(r.skybox=void 0,r.skyboxUrl=void 0,wa())}const Ca=[],Ma=new Map;function Ea(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 Pa(e){const n=new t.Entity(e.name||"Point Light");n.addComponent("light",{type:"point",color:Ea(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 ka(e){const n=new t.Entity(e.name||"Directional Light");n.addComponent("light",{type:"directional",color:Ea(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(ur(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 Ta(e){const n=new t.Entity(e.name||"Hemispheric Light");n.addComponent("light",{type:"directional",color:Ea(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=Ea(e.groundColor),o=Ea(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 Aa(e){const n=Ea(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 La(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:Ea(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(ur(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 Ra=[];function za(){if(!ot)return;const t=r.splatRelighting,e=Ca.length>0;if(console.log("[StorySplat Viewer] initSplatRelighting:",{relightConfig:t,hasLights:e,lightCount:Ca.length,splatEntity:!!ot}),!e)return void console.log("[StorySplat Viewer] No lights, skipping splat relighting");ot.script||ot.addComponent("script");const n=yt(),o=ot.script;if(ut=o?.create?.(n)??null,ut){const e=0,n=t?.ambientColor||"#ffffff";ut.setAmbientColor(n),ut.ambientR*=e,ut.ambientG*=e,ut.ambientB*=e;let o=0;!0===t?.enabled&&(o=a?1:t?.allowViewerToggle?t?.viewerDefaultOn?1:0:1),ut.setRelightFade(o),ut.enabled=!0,gt=!0,mt.add(ut),console.log("[StorySplat Viewer] Splat relighting initialized, startFade:",o)}}function Da(t){if(!gt||!ut)return;if(t.script?.gsplatRelighting)return;t.script||t.addComponent("script");const e=yt(),n=t.script,o=n?.create?.(e)??null;o&&(o.ambientR=ut.ambientR,o.ambientG=ut.ambientG,o.ambientB=ut.ambientB,o.relightFade=ut.relightFade,o.enabled=!0,mt.add(o),console.log("[Relighting] Added relighting script to swap entity:",t.name))}let Ia=!1;let Fa=null,Ba=null;function Va(t){for(const e of Ca){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))}sa.forEach(e=>{$a(e.entity,t)})}function $a(e,n){e.render&&(e.render.castShadows=n);for(const o of e.children)o instanceof t.Entity&&$a(o,n)}function Ua(){Fa&&(Fa.destroy(),Fa=null),Ba=null}function Oa(){const e=r.splatRelighting;if(e?.shadowsEnabled){!function(e,n,o){if(Fa)return Fa.setLocalPosition(0,e,0),Fa.setLocalScale(n,1,n),void(Ba&&(Ba.opacity=o,Ba.update()));Ba=new t.StandardMaterial,Ba.shadowCatcher=!0,Ba.blendType=t.BLEND_MULTIPLICATIVE,Ba.depthWrite=!1,Ba.useSkybox=!1,Ba.diffuse.set(0,0,0),Ba.specular.set(0,0,0),Ba.opacity=o,Ba.update(),Fa=new t.Entity("ShadowCatcher"),Fa.addComponent("render",{type:"plane",material:Ba,castShadows:!1,receiveShadows:!0}),Fa.setLocalPosition(0,e,0),Fa.setLocalScale(n,1,n);const i=Fa.render?.meshInstances;if(i)for(const t of i)t.drawBucket=250;nn.addChild(Fa),console.log("[StorySplat Viewer] Shadow catcher created at Y:",e,"scale:",n)}(e.shadowGroundY??0,e.shadowPlaneScale??50,e.shadowIntensity??.4),Va(!0)}else Ua(),Va(!1)}const Na=[[1,.2,.1],[.1,.6,1],[.2,1,.3],[1,.8,.1],[.8,.2,1],[1,.4,.7],[.1,1,.9]],Wa=[],Ha=[];let Ga=!1;const ja=9.8,Xa=.6,qa=.15;function Ya(e){if(0===Ha.length)for(const[e,n,o]of Na){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(),Ha.push(i)}return Ha[e%Ha.length]}const Za=[[.85,.85,.88],[.83,.69,.22],[.72,.45,.2],[.56,.57,.58],[.66,.66,.68]],Ka=[];function Qa(e){if(0===Ka.length)for(const[e,n,o]of Za){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(),Ka.push(i)}return Ka[e%Ka.length]}function Ja(){return vn?vn.collisionMeshEntities:un._collisionEntities??[]}function tr(e,n){let o=null;for(const t of Ja()){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=vn?.voxelCollisionInstance;if(i&&ot){const s=new t.Mat4;s.copy(ot.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=ot.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 er(e,n,o){for(const t of Ja()){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),b=u+o-Math.abs(f);y>0&&v>0&&b>0&&(y<=v&&y<=b?(e.x+=Math.sign(m)*y,n.x=-n.x*Xa):b<=v?(e.z+=Math.sign(f)*b,n.z=-n.z*Xa):(e.y+=Math.sign(g)*v,n.y=g>0?Math.abs(n.y)*Xa:-Math.abs(n.y)*Xa))}const i=vn?.voxelCollisionInstance;if(i){let s,a,r,l,c,d;if(ot){const n=new t.Mat4;n.copy(ot.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(ot){const t=ot.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+Xa)*s*e,n.y-=(1+Xa)*s*o,n.z-=(1+Xa)*s*i)}}}}function nr(t){const e=Ca.indexOf(t.entity);e>=0&&Ca.splice(e,1),t.entity.destroy()}function or(){if(!Ga){if(Ga=!0,!gt&&ot){r.splatRelighting={...r.splatRelighting,enabled:!0},za();for(const t of mt)t.setRelightFade(1)}console.log("[Playground] Started — press F to spawn light balls, relightingEnabled:",gt,"splatEntity:",!!ot)}}function ir(){if(Ga){Ga=!1;for(const t of Wa)nr(t);Wa.length=0,console.log("[Playground] Stopped")}}window.__storysplat_playground_handler&&window.removeEventListener("keydown",window.__storysplat_playground_handler);const sr=e=>{const n=document.activeElement?.tagName;"INPUT"===n||"TEXTAREA"===n||document.activeElement?.isContentEditable||(e.shiftKey&&(e.ctrlKey||e.metaKey)&&"KeyP"===e.code?(e.preventDefault(),Ga?ir():or()):"KeyF"===e.code&&Ga?(console.log("[Playground] F pressed — spawning light ball"),function(){Wa.length>=16&&nr(Wa.shift());const e=Math.floor(Math.random()*Na.length),[n,o,i]=Na[e],s=he.getPosition(),a=he.forward,l=new t.Vec3(s.x+2*a.x,s.y+2*a.y,s.z+2*a.z);console.log("[Playground] Spawning ball #"+(Wa.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:",l.x.toFixed(2),l.y.toFixed(2),l.z.toFixed(2),"color:",[n.toFixed(1),o.toFixed(1),i.toFixed(1)],"collisionEntities:",Ja().length,"lightEntities:",Ca.length,"relightingEnabled:",gt);const c=new t.Entity("playground-ball");c.addComponent("render",{type:"sphere",material:Ya(e),castShadows:!!r.splatRelighting?.shadowsEnabled}),c.setLocalScale(.3,.3,.3),c.addComponent("light",{type:"point",color:new t.Color(n,o,i),intensity:2,range:8,castShadows:!1}),c.setPosition(l),nn.addChild(c),Ca.push(c);const d=new t.Vec3(8*a.x+2*(Math.random()-.5),8*a.y+3,8*a.z+2*(Math.random()-.5));Wa.push({entity:c,velocity:d,age:0,lifetime:12,baseIntensity:2,radius:qa})}()):"KeyG"===e.code&&Ga&&function(){r.splatRelighting?.shadowsEnabled||(console.log("[Playground] Auto-enabling shadows for shadow ball testing"),r.splatRelighting={...r.splatRelighting,shadowsEnabled:!0},Oa()),Wa.length>=16&&nr(Wa.shift());const e=Math.floor(Math.random()*Za.length),n=he.getPosition(),o=he.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:Qa(e),castShadows:!0}),s.setLocalScale(.3,.3,.3),s.setPosition(i),nn.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));Wa.push({entity:s,velocity:a,age:0,lifetime:12,baseIntensity:0,radius:qa})}())};function ar(t){switch(t){case"linear":return"linear";case"inverse":return"inverse";default:return"exponential"}}window.__storysplat_playground_handler=sr,window.addEventListener("keydown",sr),console.log("[Playground] Toggle registered — Ctrl+Shift+P to start/stop, F = light ball, G = shadow ball");const rr=new Map;const lr=new Set;function cr(){Ns||(Cs.forEach(t=>{const e=t.hotspotAudioSlotId;if(e){const n=t.sound?.slot(e);n&&(n._storedVolume=n.volume,n.volume=0)}}),Os.forEach(t=>{Ws.set(t,t.volume),t.volume=0}),Zs.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);e&&(e._storedVolume=e.volume,e.volume=0)}),rr.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}),Ns=!0,console.log("[Audio] All audio muted"))}function dr(){Ns&&(Cs.forEach(t=>{const e=t.hotspotAudioSlotId;if(e){const n=t.sound?.slot(e);if(n){const t=n._storedVolume;n.volume=void 0!==t?t:1}}}),Os.forEach(t=>{const e=Ws.get(t);t.volume=void 0!==e?e:1}),Zs.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);if(e){const t=e._storedVolume;e.volume=void 0!==t?t:1}}),rr.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}),Ns=!1,console.log("[Audio] All audio unmuted"))}const pr=[];function hr(e,n=!1,o=1,i){const s=new t.StandardMaterial;if(s.blendType=t.BLEND_NORMAL,s.depthTest=!0,s.depthWrite=!0,s.cull=t.CULLFACE_NONE,s.twoSidedLighting=!0,s.alphaTest=.5,n?(s.diffuse=new t.Color(1,1,1),s.specular=new t.Color(0,0,0)):(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 qt(U,e,{autoPlay:!0,onReady:()=>{if(vt)return console.log(`[Hotspot] Ignoring GIF load - viewer was destroyed: ${e}`),void o.destroy();o.texture&&(o.texture.premultiplyAlpha=!1,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()}});pr.push(o)}else{const o=t=>{n?(s.diffuseMap=t,s.opacityMap=t):(s.emissiveMap=t,s.opacityMap=t),s.opacityMapChannel="a",s.update(),console.log(`[Hotspot] Texture loaded for: ${e}, useLighting=${n}`),i&&i()},a=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()};if("function"==typeof createImageBitmap)fetch(e,{mode:"cors"}).then(t=>{if(!t.ok)throw new Error(`HTTP ${t.status} ${t.statusText}`);return t.blob()}).then(t=>createImageBitmap(t,{premultiplyAlpha:"none",colorSpaceConversion:"none"})).then(n=>{if(vt)return console.log(`[Hotspot] Ignoring texture load - viewer was destroyed: ${e}`),void n.close?.();const i=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});i.premultiplyAlpha=!1;const s=U.graphicsDevice.maxAnisotropy;i.anisotropy="number"==typeof s&&s>1?s:16,i.setSource(n),o(i)}).catch(a);else{const n=new Image;n.crossOrigin="anonymous",n.onload=()=>{if(vt)return void console.log(`[Hotspot] Ignoring texture load - viewer was destroyed: ${e}`);const i=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});i.premultiplyAlpha=!1;const s=U.graphicsDevice.maxAnisotropy;i.anisotropy="number"==typeof s&&s>1?s:16,i.setSource(n),o(i)},n.onerror=a,n.src=e}}return s}function ur(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 mr(e,n,o,i){const s=(new t.Quat).setFromEulerAngles(90,0,0),a=ur(n,o,i);e.setRotation((new t.Quat).mul2(a,s))}function gr(e,n){if(!e.leaderLineEnabled||!e.leaderLineTarget)return null;const o=e.leaderLineTarget,i=new t.Vec3(o.x??0,o.y??0,-(o.z??0)),s=$s(e.leaderLineColor||"#ffffff"),a=.005*(e.leaderLineWidth??2),r=(new t.Vec3).add2(n,i).mulScalar(.5),l=(new t.Vec3).sub2(i,n),c=l.length();if(c<.001)return null;const d=new t.StandardMaterial;d.diffuse=new t.Color(0,0,0),d.emissive=s,d.useLighting=!1,d.blendType=t.BLEND_NONE,d.depthTest=!0,d.depthWrite=!0,d.cull=t.CULLFACE_NONE,d.update();const p=new t.Entity(`leader-line-${e.id||"x"}`);p.addComponent("render",{type:"cylinder",castShadows:!1,receiveShadows:!1,layers:[Ls.id]}),p.render.meshInstances[0].material=d,p._leaderMat=d,p.setPosition(r),p.setLocalScale(a,c,a);const h=new t.Vec3(0,1,0),u=l.clone().normalize(),m=new t.Quat;return h.dot(u)<-.9999?m.setFromEulerAngles(180,0,0):m.setFromDirections(h,u),p.setRotation(m),p}function fr(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(ur(p,h,u)),"sphere"===e.type){o.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1,layers:[Ls.id]});const n=new t.StandardMaterial,i=$s(e.color||"#ffffff");!0===e.useLighting?(n.diffuse=i,n.emissive=i.clone(),n.emissive.mulScalar(.3)):(n.diffuse=new t.Color(0,0,0),n.emissive=i,n.useLighting=!1);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,layers:[Ls.id]});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=hr(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),mr(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,layers:[Ls.id]});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_NORMAL,v.alphaTest=.01,d&&(v.opacityMap=y,v.blendType=t.BLEND_NORMAL,v.alphaTest=.01,console.log(`[Hotspot] WebM video with alpha enabled: ${e.title}`));let b=null,x=null;if(a){const n=m(a,!0,!1);b=n.video,x=n.texture,v.opacityMap=x,v.opacityMapChannel="r",v.blendType=t.BLEND_NORMAL,v.alphaTest=.01,f.addEventListener("play",()=>{b&&b.paused&&(b.currentTime=f.currentTime,b.play().catch(console.warn))}),f.addEventListener("pause",()=>{b&&!b.paused&&b.pause()}),f.addEventListener("seeked",()=>{b&&(b.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(),b&&x&&b.readyState===b.HAVE_ENOUGH_DATA&&x.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),mr(o,p,h,u),o.videoElement=f,o.alphaVideoElement=b,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(),b&&x&&(b.currentTime=0,x.upload()),console.log(`[Hotspot] First frame loaded for: ${e.title}`)},{once:!0}),f.load(),b&&b.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);Us.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),he&&he.getPosition){const t=he.getPosition(),e=he.forward,n=he.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,layers:[Ls.id]}),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_NORMAL,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,layers:[Ls.id]});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_NORMAL,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 qt(U,e.gifUrl,{autoPlay:!0,onReady:()=>{if(vt)a.destroy();else if(a.texture){a.texture.premultiplyAlpha=!1,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),mr(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,layers:[Ls.id]});const i=new t.StandardMaterial;i.diffuse=$s(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}});pr.push(a)}else{o.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1,layers:[Ls.id]});const n=new t.StandardMaterial,i=$s(e.color||"#CC5833");!0===e.useLighting?(n.diffuse=i,n.emissive=i.clone(),n.emissive.mulScalar(.3)):(n.diffuse=new t.Color(0,0,0),n.emissive=i,n.useLighting=!1);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(e,n){if(!n.audioUrl)return null;const o=`hotspot-audio-${n.id||Date.now()}`,i=n.audioSpatial||!1,s=ar(n.audioDistanceModel||"linear");e.sound||e.addComponent("sound",{positional:i,refDistance:n.audioRefDistance??1,maxDistance:n.audioMaxDistance??10,rollOffFactor:n.audioRolloffFactor??1,distanceModel:s,volume:n.audioVolume??1}),e.sound?.addSlot(o,{volume:n.audioVolume??1,loop:n.audioLoop||!1,autoPlay:!1,overlap:!1});const a=new t.Asset(`hotspot-audio-${o}`,"audio",{url:n.audioUrl});return a.on("load",()=>{if(vt)return;const t=e.sound?.slot(o);t&&(t.asset=a.id,Hs(t,t=>{e.hotspotAudioPlaying=t})),e.hotspotAudioReady=!0;const i=e.mediaTriggerMode||"click";"proximity"===(n.audioTriggerMode||("proximity"===i?"proximity":"click"))&&e.wasInProximity&&t&&!t.isPlaying&&(t.play(),e.hotspotAudioPlaying=!0,console.log("[Audio] Hotspot audio started (late proximity):",n.title))}),U.assets.add(a),U.assets.load(a),console.log(`[Audio] Hotspot audio setup (PlayCanvas slot): ${n.title}, spatial=${i}, refDist=${n.audioRefDistance??1}, maxDist=${n.audioMaxDistance??10}`),o}(o,e);if(m){if(o.hotspotAudioSlotId=m,console.log(`[StorySplat Viewer] Audio setup for hotspot: ${e.title||"Untitled"}, triggerMode: ${e.audioTriggerMode||"click"}`),"autoplay"===e.audioTriggerMode){let t=0;const e=()=>{if(vt)return;const n=o.sound?.slot(m);n&&void 0!==n.asset&&o.hotspotAudioReady?n.isPlaying||(n.play(),o.hotspotAudioPlaying=!0):t++<300&&setTimeout(e,100)};e()}"hover"===e.audioTriggerMode&&(o._audioHoverMode=!0,Or=!0)}if(e.billboard){const n=o.render?.meshInstances?.[0]?.material;n&&(n.cull=t.CULLFACE_BACK,n.update());const i=o.getRotation().clone(),s=void 0!==e.billboardRangeStart||void 0!==e.billboardRangeEnd;o._billboardActive=!s,o._billboardOriginalRotation=i;const a=()=>{(o.parent||nn.findByName(o.name))&&o._billboardActive&&(o.lookAt(he.getPosition()),o.rotateLocal(90,180,0))};U.on("update",a),o.once("destroy",()=>{U.off("update",a)})}e.visibilityRange&&(o.visibilityRange=e.visibilityRange,o.enabled=!1),nn.addChild(o),e.billboard&&(o.lookAt(he.getPosition()),o.rotateLocal(90,180,0));const g=gr(e,o.getPosition().clone());return g&&(nn.addChild(g),o.leaderLineEntity=g),Cs.push(o),console.log(`[StorySplat Viewer] Created hotspot: ${e.title||"Untitled"}`),o}function yr(){const e=100*ki,n=$i(ki),o=he.getPosition();Cs.forEach(i=>{const s=i.hotspotData;if(!s)return;let a=!0;if(s.alwaysVisible)a=!0;else{const t=i.visibilityRange;t&&("waypoint"===t.type?a=ua(n,t.start,t.end):"percentage"===t.type&&(a=ua(e,t.start,t.end)))}if(i.shouldBeVisible=a,s.billboard&&(void 0!==s.billboardRangeStart||void 0!==s.billboardRangeEnd)){const n=s.billboardRangeStart??0,o=s.billboardRangeEnd??100,a=e>=n&&e<=o,r=i._billboardActive;if(i._billboardActive=a,a!==r){const e=i.render?.meshInstances?.[0]?.material;e&&(e.cull=a?t.CULLFACE_FRONT:t.CULLFACE_NONE,e.update())}!a&&i._billboardOriginalRotation&&i.setRotation(i._billboardOriginalRotation)}if(i.hiddenUntilTextureLoaded?i.enabled=!1:i.enabled=a,i.leaderLineEntity&&(i.leaderLineEntity.enabled=i.enabled),i.videoElement&&"video"===s.type){const t=i.mediaTriggerMode||"click";if("proximity"===t){const t=i.getPosition(),e=o.distance(t),n=i.proximityDistance||5;e<=n&&!i.isVideoPlaying?(vr(i,s),console.log(`[Hotspot] Proximity play: ${s.title}, distance=${e.toFixed(2)}`)):e>n&&i.isVideoPlaying&&!1!==s.pauseOnLeaveProximity&&(br(i),console.log(`[Hotspot] Proximity pause: ${s.title}, distance=${e.toFixed(2)}`))}"autoplay"===t&&a&&!i.isVideoPlaying&&vr(i,s),"scroll"===t&&(a&&!i.isVideoPlaying?(vr(i,s),console.log(`[Hotspot] Scroll play: ${s.title}, scroll=${e.toFixed(1)}%`)):!a&&i.isVideoPlaying&&(br(i),console.log(`[Hotspot] Scroll pause: ${s.title}, scroll=${e.toFixed(1)}%`)))}if("animated"===s.opacityMode&&s.opacityAnimation){if("image"===s.type&&!i.textureLoaded)return;const t=s.opacityAnimation,n=t.startPercent??0,o=t.endPercent??100,a=void 0!==t.startOpacity?t.startOpacity:1,r=void 0!==t.endOpacity?t.endOpacity:1;let l;if(e<=n)l=a;else if(e>=o)l=r;else{l=a+(r-a)*((e-n)/(o-n))}if(l=Math.max(0,Math.min(1,l)),i.hotspotMaterial)i.hotspotMaterial.opacity=l,i.hotspotMaterial.update();else if(i.render&&i.render.material){const t=i.render.material;t.opacity=l,t.update?.()}}})}function vr(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 br(t){const e=t.videoElement,n=t.alphaVideoElement;e&&(e.pause(),n&&n.pause(),t.isVideoPlaying=!1)}function xr(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},r="number"==typeof s?{x:s,y:s,z:s}:s,l=Math.abs(r._x??r.x??1),c=Math.abs(r._y??r.y??1),d=r._z??r.z??1,p=e.rotation||{_x:0,_y:0,_z:0},h=p._x??p.x??0,u=p._y??p.y??0,m=p._z??p.z??0;if(o.setRotation(ur(h,u,m)),"sphere"===e.type){o.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1,layers:[Ls.id]});const n=new t.StandardMaterial,i=$s(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*l,.2*c,.2*d)}else if("image"===e.type&&e.imageUrl){o.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1,layers:[Ls.id]});const t=!0===e.useLighting,n=e.opacity??1;o.textureLoaded=!1,o.hiddenUntilTextureLoaded=!0,o.enabled=!1;const i=hr(e.imageUrl,t,n,()=>{o.textureLoaded=!0,o.visibilityRange&&!o.shouldBeVisible||(o.enabled=!0),o.hiddenUntilTextureLoaded=!1});o.render.material=i,o.setLocalScale(l,d,c),mr(o,h,u,m),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,layers:[Ls.id]});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_NORMAL,s.opacity=e.opacity??1,s.update(),o.render.material=s,o.setLocalScale(l,d,c),mr(o,h,u,m),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,layers:[Ls.id]});const t=!0===e.useLighting,n=e.opacity??1,i=hr(e.gifUrl,t,n,()=>{o.textureLoaded=!0,o.visibilityRange&&!o.shouldBeVisible||(o.enabled=!0),o.hiddenUntilTextureLoaded=!1});o.render.material=i,o.setLocalScale(l,d,c),mr(o,h,u,m),o.textureLoaded=!1,o.hiddenUntilTextureLoaded=!0,o.enabled=!1,o.portalMaterial=i,console.log(`[Portal] Created GIF portal: ${e.title||e.targetSceneName||"Untitled"}`)}else if("plane"===e.type){if(o.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1,layers:[Ls.id]}),a){const e=new t.StandardMaterial;e.diffuse=new t.Color(0,.8,.8),e.emissive=new t.Color(0,.4,.4),e.opacity=1,e.useLighting=!1,e.cull=t.CULLFACE_NONE,e.update(),o.render.material=e,o.render.enabled=!1}else{o.render.enabled=!1;const n=e.title||e.targetSceneName||"";if(n){const e=new t.Entity("portal-popup");e.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1,layers:[Ls.id]});const i=512,s=128,a=document.createElement("canvas");a.width=i,a.height=s;const r=a.getContext("2d"),l=r.createLinearGradient(0,0,i,0);l.addColorStop(0,"rgba(0, 0, 0, 0)"),l.addColorStop(.15,"rgba(0, 0, 0, 0.6)"),l.addColorStop(.5,"rgba(0, 0, 0, 0.75)"),l.addColorStop(.85,"rgba(0, 0, 0, 0.6)"),l.addColorStop(1,"rgba(0, 0, 0, 0)"),r.fillStyle=l,r.fillRect(0,0,i,s),r.fillStyle="#ffffff",r.font='300 36px "Helvetica Neue", Helvetica, Arial, sans-serif',r.textAlign="center",r.textBaseline="middle",r.fillText(n,i/2,s/2);const c=new t.Texture(U.graphicsDevice,{width:i,height:s,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});c.setSource(a);const d=new t.StandardMaterial;d.diffuse=new t.Color(0,0,0),d.emissive=new t.Color(1,1,1),d.emissiveMap=c,d.opacityMap=c,d.opacityMapChannel="a",d.useLighting=!1,d.blendType=t.BLEND_NORMAL,d.depthTest=!0,d.depthWrite=!1,d.cull=t.CULLFACE_NONE,d.opacity=0,d.update(),e.render.material=d,e.setLocalPosition(0,.3,0),e.setLocalScale(.6,1,.15);const p=()=>{e.enabled&&o.portalPopupOpacity>0&&(e.lookAt(he.getPosition()),e.rotateLocal(90,180,0))};U.on("update",p),e.once("destroy",()=>{U.off("update",p)}),o.addChild(e),o.portalPopupEntity=e,o.portalPopupMaterial=d,o.portalPopupOpacity=0}}o.setLocalScale(l,d,c),mr(o,h,u,m),console.log(`[Portal] Created plane portal: ${e.title||e.targetSceneName||"Untitled"}`)}else if("panorama"===e.type){o.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1,layers:[Ls.id]});const n=new t.StandardMaterial;e.panoramaUrl?(n.diffuse=new t.Color(0,0,0),n.emissive=new t.Color(1,1,1),n.emissiveIntensity=1,jo(e.panoramaUrl).then(e=>{if(vt)return;const i=new t.Texture(U.graphicsDevice,{width:e.width,height:e.height,format:t.PIXELFORMAT_RGBA8,mipmaps:!0,minFilter:t.FILTER_LINEAR_MIPMAP_LINEAR,magFilter:t.FILTER_LINEAR,addressU:t.ADDRESS_REPEAT,addressV:t.ADDRESS_CLAMP_TO_EDGE});i.setSource(e),n.emissiveMap=i,n.update(),o.textureLoaded=!0}).catch(()=>{})):(n.diffuse=new t.Color(.15,.55,.85),n.emissive=new t.Color(.1,.35,.55));const i=e.opacity??.9;n.opacity=i,n.blendType=i>=.95?t.BLEND_NONE:t.BLEND_ADDITIVEALPHA,n.depthTest=!0,n.depthWrite=i>=.95,n.update(),o.render.material=n,o.setLocalScale(.25*l,.25*c,.25*d)}else{o.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1,layers:[Ls.id]});const n=new t.StandardMaterial,i=$s(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*l,.2*c,.2*d)}if("plane"!==e.type&&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_BACK,e.update());const n=()=>{o.enabled&&(o.lookAt(he.getPosition()),o.rotateLocal(90,180,0))};U.on("update",n),o.once("destroy",()=>{U.off("update",n)})}e.visibilityRange&&(o.visibilityRange=e.visibilityRange,o.enabled=!1),nn.addChild(o),e.billboard&&(o.lookAt(he.getPosition()),o.rotateLocal(90,180,0));const g=gr(e,o.getPosition().clone());return g&&(nn.addChild(g),o.leaderLineEntity=g),Is.push(o),console.log(`[StorySplat Viewer] Created portal: ${e.title||e.targetSceneName||"Untitled"} -> ${e.targetSceneId}`),o}function wr(){const t=100*ki,e=$i(ki);Is.forEach(n=>{if(!n.portalData)return;let o=!0;const i=n.visibilityRange;i&&("waypoint"===i.type?o=ua(e,i.start,i.end):"percentage"===i.type&&(o=ua(t,i.start,i.end))),n.shouldBeVisible=o,n.hiddenUntilTextureLoaded?n.enabled=!1:n.enabled=o,n.leaderLineEntity&&(n.leaderLineEntity.enabled=n.enabled)})}function Sr(e,n){const o=he.camera.screenToWorld(e,n,he.camera.nearClip),i=he.camera.screenToWorld(e,n,he.camera.farClip);let s=null;Is.forEach(e=>{if(!e.enabled)return;if("plane"===e.portalData?.type)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 _r(t){if("panorama"===t.type||t.panoramaUrl&&!t.targetSceneId)return void Xo(t);if(!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;try{i.emit("portalActivated",{portalId:t.id,targetSceneId:t.targetSceneId,targetSceneName:t.targetSceneName})}catch(t){console.warn("[Portal] External portalActivated handler threw:",t)}if(n)console.log("[Portal] External portal handler detected, deferring navigation");else if(vt)console.log("[Portal] Viewer destroyed during emit, skipping internal 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=x(u,"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 ${d}`,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 i=await fetch(n);if(!i.ok)throw new Error(`Failed to fetch scene: ${i.status} ${i.statusText}`);const a=await i.json(),r=a.data||a;r.name&&function(t,e){const n=t.querySelector(".storysplat-portal-loading-text");n&&(n.textContent=x(u,"loadingScene").replace("{name}",e))}(e,r.name),s?.beginPortalTransition(),function(){console.log("[Portal] Cleaning up current scene for navigation..."),vt=!0,Ss(),Co(),Mi(),il&&(il.destroy(),il=null);Os.forEach(t=>{t.pause(),t.src=""}),Os.length=0,Ws.clear(),Us.forEach(t=>{t.close().catch(()=>{})}),Us.length=0,Zs.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);e&&e.stop(),t.entity.destroy()}),Zs.clear(),rr.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);e&&e.stop(),t.entity.destroy()}),rr.clear(),Cs.forEach(t=>{t.videoElement&&(t.videoElement.pause(),t.videoElement.src=""),t.alphaVideoElement&&(t.alphaVideoElement.pause(),t.alphaVideoElement.src="")}),pr.forEach(t=>t.destroy()),pr.length=0,ga(),Cs.forEach(t=>{const e=t.leaderLineEntity;e&&(e._leaderMat?.destroy(),e.destroy()),t.destroy()}),Cs.length=0,Is.forEach(t=>{const e=t.leaderLineEntity;e&&(e._leaderMat?.destroy(),e.destroy()),t.destroy()}),Is.length=0,Ms.forEach(t=>{Ds(t.name)}),Ms.length=0,Es.clear(),ks=!1,ot&&(ot.destroy(),ot=null);ut=null,mt.clear(),gt=!1,ir(),window.removeEventListener("keydown",sr),Ln.destroy(),zn.destroy(),An.destroy(),Mr.removeEventListener("mousemove",Er),Ua(),Ca.forEach(t=>{t.destroy()}),Ca.length=0,Js.forEach(t=>{t.destroy()}),Js.clear(),Be&&(clearTimeout(Be),Be=null,Fe=null);Te&&(Te.destroy(),Te=null,De=null);U.off("update",Oe),ta.forEach(t=>{t.destroy()}),ta.clear(),fa&&(fa.destroy(),fa=null);ya&&(U.scene.off("prerender",ya),ya=null);At&&(At.destroy(),At=null);vn&&(vn.destroy(),vn=null);const t=U.__htmlMeshManager;t&&t.destroy();const n=U.__customScriptSystem;n&&n.dispose();window.removeEventListener("resize",Zr),D.preloader&&D.preloader.remove();D.scrollControls&&D.scrollControls.remove();D.fullscreenButton&&D.fullscreenButton.remove();D.helpButton&&D.helpButton.remove();D.helpPanel&&D.helpPanel.remove();D.waypointInfo&&D.waypointInfo.remove();D.watermark&&D.watermark.remove();D.wasdHint&&D.wasdHint.remove();D.orbitHint&&D.orbitHint.remove();D.doubleTapHint&&D.doubleTapHint.remove();D.lookZone&&D.lookZone.remove();D.joystick&&D.joystick.remove();D.portalPopup&&D.portalPopup.remove();D.muteButton&&D.muteButton.remove();D.relightingButton&&D.relightingButton.remove();D.waypointListContainer&&D.waypointListContainer.remove();D.sceneMenuContainer&&D.sceneMenuContainer.remove();D.fpsCounter&&D.fpsCounter.remove();D.hotspotPopup&&D.hotspotPopup.remove();D.vrButton&&D.vrButton.remove();D.arButton&&D.arButton.remove();D.modeContainer&&D.modeContainer.remove();D.exploreControls&&D.exploreControls.remove();Ar&&(Ar(),Ar=null);kr&&(kr.destroy(),kr=null);Tr&&(document.removeEventListener("keydown",Tr),Tr=null);Lr&&(document.removeEventListener("click",Lr),Lr=null);Rr&&(zr&&(zr.removeEventListener("pointerdown",Rr),zr.removeEventListener("wheel",Rr)),window.removeEventListener("keydown",Rr),Rr=null,zr=null);Ho&&(clearTimeout(Ho),Ho=null);if(Oo){const t=Oo._escHandler;t&&document.removeEventListener("keydown",t),Oo.remove(),Oo=null}D._cleanup&&(D._cleanup(),D._cleanup=void 0);const o=document.getElementById("storysplat-viewer-styles");o&&o.remove();if(e.classList.remove("storysplat-viewer-container"),ye&&he.camera){try{he.camera.postEffects.removeEffect(ye)}catch(t){}ye=null}We&&(We.destroy(),We=null);U.destroy(),console.log("[Portal] Cleanup complete")}(),await new Promise(t=>setTimeout(t,100)),I&&I.parentNode&&I.remove();const l={lazyLoad:!1,...!0===o.disableAnalytics?{disableAnalytics:!0}:{},...o.analytics?{analytics:{...o.analytics,sceneId:t.targetSceneId}}:{},...o.googleTagId?{googleTagId:o.googleTagId}:{},...s?{__sessionRecorderHandle:s}:{}},c=await hn(e,r,l);return s?.attachViewer(c),Cr(e),void console.log(`[Portal] Successfully navigated to scene: ${t.targetSceneId}`)}catch(t){console.error("[Portal] Navigation failed:",t),Cr(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 Cr(t){const e=t.querySelector(".storysplat-portal-loading");e&&e.remove()}i.on("progressUpdate",()=>{yr()}),setTimeout(()=>{yr()},100),U.on("update",function(){const e=he.getPosition();Is.forEach(n=>{const o=n.portalData;if(!o)return;const i=!1!==n.shouldBeVisible;if("proximity"===o.activationMode&&i){const t=n.getPosition(),i=e.distance(t),s=o.proximityDistance||2;i<=s&&!n.proximityTriggered?(n.proximityTriggered=!0,console.log(`[Portal] Proximity triggered: ${o.title||o.targetSceneName}, navigating to scene ${o.targetSceneId}`),_r(o)):i>s&&(n.proximityTriggered=!1)}if("collision"===o.activationMode&&i){const i=n.getPosition(),s=n.getLocalScale(),a=n.getRotation(),r=(new t.Quat).copy(a).invert(),l=(new t.Vec3).sub2(e,i);r.transformVector(l,l);const c=Math.abs(s.x)/2,d=Math.abs(s.z)/2,p=.5,h=Math.abs(l.x)<=c+p,u=Math.abs(l.z)<=d+p,m=Math.abs(l.y)<=p,g=h&&u&&m;if(n.portalPopupMaterial&&n.portalPopupEntity){const t=e.distance(i),o=Math.max(Math.abs(s.x),Math.abs(s.z)),a=1.5*o+3,r=.5*o+.8;let l=0;t<=r?l=1:t<a&&(l=1-(t-r)/(a-r));const c=n.portalPopupOpacity||0,d=c+(l-c)*.08;n.portalPopupOpacity=d,n.portalPopupMaterial.opacity=d,n.portalPopupMaterial.update(),n.portalPopupEntity.enabled=d>.01}g&&!n.proximityTriggered?(n.proximityTriggered=!0,console.log(`[Portal] Collision triggered: ${o.title||o.targetSceneName}, navigating to scene ${o.targetSceneId}`),_r(o)):g||(n.proximityTriggered=!1)}})}),i.on("progressUpdate",()=>{wr()}),setTimeout(()=>{wr()},100);const Mr=U.graphicsDevice.canvas,Er=t=>{const e=Mr.getBoundingClientRect();In=t.clientX-e.left,Fn=t.clientY-e.top};q||Mr.addEventListener("mousemove",Er);let Pr=!1,kr=null,Tr=null,Ar=null,Lr=null,Rr=null,zr=null,Dr=!1,Ir=!1,Fr=0,Br=0;Mr.addEventListener("pointerdown",t=>{0===t.button&&(Ir=!0,Dr=!1,Fr=t.clientX,Br=t.clientY)}),Mr.addEventListener("pointermove",t=>{if(!Ir)return;const e=t.clientX-Fr,n=t.clientY-Br;e*e+n*n>25&&(Dr=!0)});const Vr=()=>{Ir=!1};function $r(e,n){const o=he.camera.screenToWorld(e,n,he.camera.nearClip),i=he.camera.screenToWorld(e,n,he.camera.farClip);let s=null;Cs.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}Mr.addEventListener("pointerup",Vr),Mr.addEventListener("pointercancel",Vr);let Ur=null,Or=!1,Nr=!1,Wr=!1;const Hr=e.querySelector(".storysplat-hotspot-popup"),Gr=e.querySelector(".storysplat-hotspot-overlay");function jr(t,e){eo||(Io||(Io=!0,H(D)),"fly"!==un.mode&&O(D,!1),"walk"!==Sn?"explore"!==Sn&&"orbit"!==Sn||async function(t,e){if("explore"!==Sn&&"orbit"!==Sn)return;if(Pr)return;console.log("[StorySplat Viewer] Double-click focus at:",t,e);const n=$r(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"===un.mode?un.flyTo(t):un.focus(t,!1))}try{const n=.25;Cn.resize(Math.floor(Mr.clientWidth*n),Math.floor(Mr.clientHeight*n));const o=U.scene.layers.getLayerByName("World");if(!o)return void console.warn("[StorySplat Viewer] World layer not found");Cn.prepare(he.camera,U.scene,[o]);const i=Math.floor(t*n),s=Math.floor(e*n),a=await Cn.getWorldPointAsync(i,s);if(a)if(isFinite(a.x)&&isFinite(a.y)&&isFinite(a.z)){const t=he.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"===un.mode?un.flyTo(a):un.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):async function(t,e){if(!vn)return;try{const n=.25;Cn.resize(Math.floor(Mr.clientWidth*n),Math.floor(Mr.clientHeight*n));const o=U.scene.layers.getLayerByName("World");if(!o)return;Cn.prepare(he.camera,U.scene,[o]);const i=Math.floor(t*n),s=Math.floor(e*n),a=await Cn.getWorldPointAsync(i,s);if(a&&isFinite(a.x)&&isFinite(a.y)&&isFinite(a.z)){const t=he.getPosition().distance(a);t>.3&&t<500&&vn.walkTo(a)}}catch(t){console.warn("[StorySplat Viewer] Walk-to pick failed:",t)}}(t,e))}Hr&&(Hr.addEventListener("mouseenter",()=>{Nr=!0}),Hr.addEventListener("mouseleave",()=>{Nr=!1,Ur&&"hover"===Ur.activationMode&&(Hr.classList.remove("visible"),Gr&&Gr.classList.remove("visible"),Ur=null)})),Mr.addEventListener("mousemove",n=>{const o=Mr.getBoundingClientRect(),i=n.clientX-o.left,s=n.clientY-o.top,r=Sr(i,s);if(r&&r.portal){const t=r.portal.activationMode||"click";return Mr.style.cursor="click"===t?"pointer":"default",void(Kn=!0)}const c=$r(i,s);if(c&&c.hotspot){const t=c.entity,n=c.hotspot,o=n.activationMode||"click";if("click"===o||"hover"===o||"video"===n.type?(Mr.style.cursor="pointer",Kn=!0):(Mr.style.cursor="default",Kn=!1),"hover"===n.activationMode&&Ur!==n){Ur=n;!(a&&l===n.id)&&"none"!==n.contentType&&(n.information||n.photoUrl||n.popupVideoUrl||n.iframeUrl||n.externalLinkUrl||n.modelUrl)&&F(e,n,u)}if(Or&&Cs.forEach(e=>{if(e!==t&&e._audioHoverPlaying&&e.hotspotAudioSlotId){const t=e.sound?.slot(e.hotspotAudioSlotId);t?.isPlaying&&t.stop(),e._audioHoverPlaying=!1}}),Or&&t._audioHoverMode&&t.hotspotAudioSlotId&&!t._audioHoverPlaying){const e=t.sound?.slot(t.hotspotAudioSlotId);e&&!e.isPlaying&&t.hotspotAudioReady&&(e.play(),t._audioHoverPlaying=!0)}}else if(Wr||(Mr.style.cursor="default",Kn=!1),Or&&Cs.forEach(t=>{if(t._audioHoverPlaying&&t.hotspotAudioSlotId){const e=t.sound?.slot(t.hotspotAudioSlotId);e?.isPlaying&&e.stop(),t._audioHoverPlaying=!1}}),Ur&&"hover"===Ur.activationMode&&!Nr){const t=e.querySelector(".storysplat-hotspot-popup"),n=e.querySelector(".storysplat-hotspot-overlay");t&&t.classList.remove("visible"),n&&n.classList.remove("visible"),Ur=null}if(xn?.metadata?.segments?.length&&!a){const a=Mr.clientWidth,r=Mr.clientHeight,l=i/a*2-1,c=1-s/r*2,d=he.getPosition(),p=he.forward,h=he.right,u=he.up,m=he.camera.fov*Math.PI/180,g=a/r,f=Math.tan(m/2),y=new t.Vec3(p.x+l*g*f*h.x+c*f*u.x,p.y+l*g*f*h.y+c*f*u.y,p.z+l*g*f*h.z+c*f*u.z).normalize();let v=0,b=1/0;const x=2;for(const t of xn.metadata.segments){const e=t.centroid_3d[0],n=t.centroid_3d[1],o=t.centroid_3d[2],i=e-d.x,s=n-d.y,a=o-d.z,r=i*y.x+s*y.y+a*y.z;if(r<0)continue;const l=d.x+r*y.x,c=d.y+r*y.y,p=d.z+r*y.z,h=Math.sqrt((l-e)**2+(c-n)**2+(p-o)**2);h<x*Math.max(1,.1*r)&&h<b&&(b=h,v=t.id)}if(xn.setHighlight(v),v>0){const t=xn.metadata.segments.find(t=>t.id===v);if(t){wn||(wn=document.createElement("div"),wn.style.cssText="position:absolute;pointer-events:none;padding:4px 10px;background:rgba(0,0,0,0.75);color:#fff;border-radius:6px;font-size:13px;font-family:sans-serif;white-space:nowrap;z-index:9999;transform:translate(-50%,-120%);transition:opacity 0.15s;",e.appendChild(wn));const[i,s,a]=t.avg_color;wn.textContent="";const r=document.createElement("span");r.style.cssText=`display:inline-block;width:10px;height:10px;border-radius:50%;background:rgb(${i},${s},${a});margin-right:6px;vertical-align:middle;`,wn.appendChild(r),wn.appendChild(document.createTextNode(`${t.name} (${t.gaussian_count.toLocaleString()} splats)`)),wn.style.left=n.clientX-o.left+"px",wn.style.top=n.clientY-o.top+"px",wn.style.opacity="1"}}else wn&&(wn.style.opacity="0")}}),Mr.addEventListener("click",n=>{if(Pr)return;if(Ki)return;if(Dr)return void(Dr=!1);const o=Mr.getBoundingClientRect(),s=n.clientX-o.left,c=n.clientY-o.top,d=Sr(s,c);if(null!==d&&d.portal){const t=d.portal;if(console.log("[StorySplat Viewer] Portal clicked:",t.title||t.targetSceneName),a)return void i.emit("portalClick",{portal:t});if("click"===(t.activationMode||"click")){!1!==t.confirmNavigation&&(t.title||t.targetSceneName||t.targetSceneId)&&Fs?(t=>{if(!Fs)return;const e=Fs.querySelector(".storysplat-portal-popup-title");e&&(t.title?e.textContent=t.title:t.targetSceneName?e.textContent=`${x(u,"switchScenes").replace("?","")} ${t.targetSceneName}?`:e.textContent=x(u,"switchScenes")),Bs=t,Fs.classList.add("visible")})(t):_r(t)}return}const p=$r(s,c);if(null!==p){const n=p.entity,o=p.hotspot;if(console.log("[StorySplat Viewer] Hotspot clicked:",o.title),i.emit("hotspotClick",{hotspot:o}),n.hotspotAudioSlotId&&n.hotspotAudioReady){if("click"===(o.audioTriggerMode||("proximity"===(n.mediaTriggerMode||"click")?"proximity":"click"))){const t=n.sound?.slot(n.hotspotAudioSlotId);if(t){const e=o.audioClickBehavior||"pause-resume",i=t.isPlaying,s=t.isPaused;i||s?s&&"pause-resume"===e?(t.resume(),n.hotspotAudioPlaying=!0,console.log("[Audio] Hotspot audio resumed (click):",o.title)):s&&"stop-restart"===e?(t.play(),n.hotspotAudioPlaying=!0,console.log("[Audio] Hotspot audio restarted (click):",o.title)):i&&("stop-restart"===e?(t.stop(),n.hotspotAudioPlaying=!1,console.log("[Audio] Hotspot audio stopped (click):",o.title)):(t.pause(),n.hotspotAudioPlaying=!1,console.log("[Audio] Hotspot audio paused (click):",o.title))):(t.play(),n.hotspotAudioPlaying=!0,console.log(`[Audio] Hotspot audio started (click/${e}):`,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?(vr(n,o),console.log("[Hotspot] Video started")):(br(n),console.log("[Hotspot] Video paused"))}const s=o.activationMode||"click",c="none"!==o.contentType&&(o.title||o.information||o.photoUrl||o.popupVideoUrl||o.iframeUrl||o.externalLinkUrl||o.modelUrl);"click"===s&&c&&(eo?Po?ko():function(e){if(!eo)return;Mo||(Mo=new t.Entity("arContentPlane"),Mo.addComponent("render",{type:"plane"}),Mo.setLocalScale(.8,1,.45),U.root.addChild(Mo));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",Eo||(Eo=new t.Texture(U.graphicsDevice,{width:n,height:o,format:t.PIXELFORMAT_RGBA8,mipmaps:!1,minFilter:t.FILTER_LINEAR,magFilter:t.FILTER_LINEAR})),Eo.setSource(i);const c=new t.StandardMaterial;c.diffuse=new t.Color(0,0,0),c.diffuseMap=Eo,c.emissive=new t.Color(1,1,1),c.emissiveMap=Eo,c.useLighting=!1,c.blendType=t.BLEND_NORMAL,c.cull=t.CULLFACE_NONE,c.depthWrite=!0,c.update(),Mo.render&&Mo.render.meshInstances[0]&&(Mo.render.meshInstances[0].material=c),Mo.enabled=!0,Po=!0,console.log("[StorySplat Viewer] AR content displayed for hotspot:",e.title)}(o):a&&l===o.id||F(e,o,u));const d=o.teleportWaypoint??o.teleportToWaypoint,h=o.teleportPercent??o.teleportToPercent,m=o.teleportMode||"animate";let g=null;if(void 0!==d&&-1!==d){const t=r.waypoints?.length||1,e=Math.max(0,Math.min(d,t-1));g=Vi(e),console.log("[Hotspot] Teleporting to waypoint:",e,"(progress:",g,", mode:",m,")")}else void 0!==h&&-1!==h&&(g=Math.max(0,Math.min(h/100,1)),console.log("[Hotspot] Teleporting to percent:",h,"(progress:",g,", mode:",m,")"));null!==g&&("instant"===m?(ki=g,Ti=g,as(ki)):(Ti=g,cs(g,800)))}null===p&&null===d&&"single"===Ft&&("explore"!==Sn&&"walk"!==Sn&&"orbit"!==Sn||jr(s,c))}),Mr.addEventListener("dblclick",t=>{if(eo)return;if("double"!==Ft)return;const e=Mr.getBoundingClientRect();jr(t.clientX-e.left,t.clientY-e.top)});let Xr=0,qr=!1;Mr.addEventListener("touchstart",t=>{eo||t.touches.length>1&&(qr=!0)}),Mr.addEventListener("touchend",t=>{if(eo)return;if(1!==t.changedTouches.length)return;if(qr)return 0===t.touches.length&&(qr=!1),void(Xr=0);const e=Date.now();if(e-Xr<300){if("double"===Ft){const e=t.changedTouches[0],n=Mr.getBoundingClientRect();jr(e.clientX-n.left,e.clientY-n.top)}Xr=0}else Xr=e}),Mr.addEventListener("touchcancel",t=>{eo||0===t.touches.length&&(qr=!1,Xr=0)}),document.addEventListener("keydown",t=>{const e=document.activeElement?.tagName;if("INPUT"===e||"TEXTAREA"===e||document.activeElement?.isContentEditable)return;if(["KeyW","KeyA","KeyS","KeyD","ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Space"].includes(t.code)&&vn&&vn.cancelWalkTo(),t.shiftKey&&t.ctrlKey&&"KeyD"===t.code&&vn){t.preventDefault();const e=!vn.collisionDebugVisible;vn.setCollisionDebug(e),console.log(`[StorySplat Viewer] Collision debug: ${e?"ON":"OFF"} (${vn.collisionMeshEntities.length} meshes)`)}}),Yo(.2,"Initializing...");const Yr=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"),Yo(.3,"Loading 4DGS frames..."),At=new se(U,{frameUrls:n.frameSequence.frameUrls,fps:n.frameSequence.fps||24,loop:!1!==n.frameSequence.loop,preloadCount:n.frameSequence.preloadCount||10,autoplay:n.frameSequence.autoplay||!1,rotation:n.frameSequence.rotation},{onFrameChange:(t,e)=>{i.emit("frameChange",t,e)},onLoadProgress:(t,e)=>{Yo(.3+t/e*.6,`Loading frames... ${t}/${e}`)},onError:t=>{console.error("[StorySplat Viewer] Frame sequence error:",t),i.emit("error",new Error(t))}}),At.on("complete",()=>{i.emit("frameComplete")}),U.on("update",t=>{At&&!vt&&At.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=[],l=ln()||!0===r.forceMobilePreview,c=Boolean(r.mobileLodMetaUrl||r.mobileSogUrl||r.mobileSplatUrl||r.mobileCompressedPlyUrl),d=l&&c,p=d?r.mobileLodMetaUrl:r.lodMetaUrl,h=d?r.mobileSogUrl:r.sogUrl,m=d?r.mobileCompressedPlyUrl:void 0,g=d?r.mobileSplatUrl:r.splatUrl;console.log("[SPLAT] Building URL priority list..."),console.log("[SPLAT] Mobile asset selection:",{onMobile:l,forceMobilePreview:!0===r.forceMobilePreview,usingMobileAsset:d}),console.log("[SPLAT] Available URLs:",{lodMetaUrl:p||"(none)",sogUrl:h||"(none)",compressedPlyUrl:m||"(none)",splatUrl:g||"(none)",fallbackUrls:r.fallbackUrls?.length||0}),p&&(a.push(p),console.log("[SPLAT] ✓ LOD streaming URL added (highest priority):",p)),h&&(a.push(h),console.log("[SPLAT] ✓ SOG URL added (second priority):",h)),m&&m!==g&&(a.push(m),console.log("[SPLAT] ✓ Compressed PLY URL added (third priority):",m)),g&&(a.push(g),console.log("[SPLAT] ✓ Original splat URL added (fourth priority):",g)),r.fallbackUrls&&(a.push(...r.fallbackUrls),console.log("[SPLAT] ✓ Fallback URLs added:",r.fallbackUrls)),console.log("[SPLAT] Final URL priority order:",a),console.log("[SPLAT] Will try URLs in order: LOD streaming > SOG > compressed PLY > PLY/Other"),Yo(.3,x(u,"loading"));for(const l of a)if(l)try{const a=decodeURIComponent(l.toLowerCase().split(/[?#]/)[0]).split("/").pop()||"";if(a.endsWith(".glb")||a.endsWith(".gltf"))return console.log("[SPLAT] Detected mesh format (GLB/GLTF), loading as container:",l),la({id:"__primary-mesh__",name:"Primary Model",modelUrl:l,position:{x:0,y:0,z:0}},0),void Yo(1,"");const c=l.split(".").pop()?.toLowerCase()||"splat",d="gsplat",p=l.includes("lod-meta.json"),h=l.includes(".sog")||p;console.log("[SPLAT] Attempting to load URL:",l),console.log("[SPLAT] Format detection:",{extension:c,isLodStreaming:p,isSogFormat:h,assetType:d});const m=p?await gi(l):null,g=new t.Asset("splat-"+Date.now(),d,{url:l});g.on("progress",(t,n)=>{if(n>0){Zo(l)&&(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)`),Yo(o,`${x(u,"loading")} ${i}%`)}}),s=l,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:l}),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 c=()=>{h&&a&&window.removeEventListener("unhandledrejection",a)};g.ready(()=>{if(vt)return console.log("[SPLAT] Ignoring load - viewer was destroyed during loading"),c(),void e(new Error("Viewer destroyed"));console.log("[SPLAT] ✓ Asset loaded and resources ready");try{if(it=m,ot=Ko(g,l,m),vn){vn.setSplatEntity(ot);const t=vn.voxelCollisionInstance;t&&un.setVoxelCollision(t,ot)}pn(l)?(console.log("[SPLAT] ✓ LOD streaming enabled for this splat"),console.log("[SPLAT] LOD configured:",{lodBaseDistance:Q,lodMultiplier:J,preset:Z})):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||r.revealEffect||"none",i=o.revealStyle||n.revealStyle||r.revealStyle||"bloom",a=Lt(e,i);if(a){ot.addComponent("script");const t="radial"===i?xt():Ct();st=ot.script?.create?.(t)??null,st&&(st.enabled=!1,st.center.set(0,0,0),st.speed=a.speed,st.acceleration=a.acceleration,st.delay=a.delay,st.oscillationIntensity=a.oscillationIntensity,st.dotTint.set(a.dotTint.r,a.dotTint.g,a.dotTint.b),st.waveTint.set(a.waveTint.r,a.waveTint.g,a.waveTint.b),st.endRadius=a.endRadius,console.log("[StorySplat Viewer] Reveal effect configured (waiting to start):",i,e))}else console.log("[StorySplat Viewer] Reveal effect disabled");p?(c(),t()):setTimeout(()=>{s||(c(),t())},100)}catch(t){console.error("[StorySplat Viewer] Error during gsplat setup:",t),c(),e(t)}}),g.on("error",t=>{const n=t;console.error("[SPLAT] ✗ Asset load error:",{url:l,assetType:d,isSogFormat:h,isLodFormat:p,error:t,message:n?.message||"Unknown error",status:n?.status||n?.statusCode||"N/A"}),c(),e(t)}),Qo(g,l)}),Vt=s;const f=Zo(s);return i.emit("loaded",{bandwidthUsed:f?e:0,isStorySplatHosted:f}),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?Z:"N/A",bandwidthUsed:`${(e/1024/1024).toFixed(2)}MB`,isStorySplatHosted:f,bandwidthCounted:f?"Yes":"No (self-hosted)"})}catch(t){console.warn("[SPLAT] ✗ Failed to load URL, trying next fallback:",l),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"))};Yr().then(()=>{if(Yo(1,"Ready!"),a||(r.hotspots&&0!==r.hotspots.length?(console.log(`[StorySplat Viewer] Creating ${r.hotspots.length} hotspots...`),r.hotspots.forEach((t,e)=>{fr(t,e)})):console.log("[StorySplat Viewer] No hotspots to create"),function(){if(!r.portals||0===r.portals.length)return void console.log("[StorySplat Viewer] No portals to create");const t=r.portals.filter(t=>!t.menuOnly);console.log(`[StorySplat Viewer] Creating ${t.length} portals (${r.portals.length-t.length} menu-only)...`),t.forEach((t,e)=>{xr(t,e)});for(const t of r.portals)"panorama"===t.type&&t.panoramaUrl&&jo(t.panoramaUrl).catch(()=>{})}(),r.mirrorPlanes&&0!==r.mirrorPlanes.length&&(console.log(`[StorySplat Viewer] Creating ${r.mirrorPlanes.length} mirror plane(s)...`),r.mirrorPlanes.forEach((t,e)=>{const n=zs(t,e);a&&t.id&&ol.set(t.id,n)})),r.waypoints&&0!==r.waypoints.length&&(r.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:ar(i.distanceModel||"exponential"),maxDistance:i.maxDistance||1e3,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,Hs(t,t=>{const e=Zs.get(s);e&&(e.playing=t)}));const e=Zs.get(s);e&&(e.assetReady=!0,"autoplay"===i.triggerMode&&t&&(t.play(),e.playing=!0)),console.log(`[Audio] Waypoint audio loaded: ${s}, spatialSound=${i.spatialSound}, maxDistance=${i.maxDistance}`)}),U.assets.load(c),nn.addChild(a),Zs.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}`)}})}),Zs.size>0&&console.log(`[StorySplat Viewer] Setup ${Zs.size} waypoint audio sources`)),function(){const e=r.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=ar(e.distanceModel||"linear");i.addComponent("sound",{positional:!1!==e.spatialSound,refDistance:e.refDistance||1,maxDistance:e.maxDistance||10,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(vt)return;const t=i.sound?.slot(n);t&&(t.asset=a.id);const o=rr.get(n);o&&(t&&Hs(t,t=>{o.playing=t}),o.assetReady=!0,"autoplay"===(e.triggerMode||(!1!==e.autoplay?"autoplay":"proximity"))&&t&&(console.log(`[Audio] Autoplay emitter: ${e.name||n}`),t.play(),o.playing=!0)),console.log(`[Audio] Emitter loaded: ${e.name||n}, triggerMode=${e.triggerMode||"autoplay"}, spatial=${!1!==e.spatialSound}, maxDistance=${e.maxDistance||10}`)}),U.assets.add(a),U.assets.load(a),nn.addChild(i),rr.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})`)}),rr.size>0&&console.log(`[StorySplat Viewer] Setup ${rr.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:",r.particles),console.log("[Particle] Type:",typeof r.particles),console.log("[Particle] Is Array:",Array.isArray(r.particles)),!r.particles||0===r.particles.length)return console.log("[Particle] ⚠️ No particle systems to create (particles array is empty or undefined)"),void console.log("═══════════════════════════════════════");console.log(`[Particle] 📋 Found ${r.particles.length} particle system(s) to create`);for(let t=0;t<r.particles.length;t++){const e=r.particles[t];console.log(`[Particle] --- Particle System ${t+1}/${r.particles.length} ---`),console.log("[Particle] Raw config:",JSON.stringify(e,null,2));try{const t=e.particleTexture||"flare";let n;if("custom"===t&&e.customTextureUrl){const t=`custom_${e.customTextureUrl}`;console.log(`[Particle] Custom texture: ${e.customTextureUrl.substring(0,60)}...`),n=await oa(t,e.customTextureUrl)}else{const e=ea.has(t)?t:"flare";console.log(`[Particle] Texture: ${e} (procedural)`),n=na(e)}console.log("[Particle] ✅ Texture ready:",n.name),console.log("[Particle] Creating entity...");const o=ia(e);console.log("[Particle] ✅ Entity created:",o.name),o.particlesystem?(o.particlesystem.colorMap=n,console.log("[Particle] ✅ Texture applied to particle system"),console.log("[Particle] Particle system properties:",{numParticles:o.particlesystem.numParticles,lifetime:o.particlesystem.lifetime,rate:o.particlesystem.rate,loop:o.particlesystem.loop,autoPlay:o.particlesystem.autoPlay})):console.warn("[Particle] ⚠️ Entity has no particlesystem component!"),nn.addChild(o),console.log("[Particle] ✅ Entity added to scene");const i=o.getPosition();console.log(`[Particle] Position: (${i.x.toFixed(2)}, ${i.y.toFixed(2)}, ${i.z.toFixed(2)})`);const s=(e.id||e.name||`particle-${Js.size}`).replace(/[^a-zA-Z0-9]/g,"_");Js.set(s,o),console.log(`[Particle] ✅ SUCCESS: Created "${e.name||s}"`)}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: ${Js.size}/${r.particles.length} particle systems created`),console.log("[Particle] Active particle systems:",Array.from(Js.keys())),console.log("═══════════════════════════════════════")}(),async function(){r.customMeshes&&0!==r.customMeshes.length?(console.log(`[StorySplat Viewer] Creating ${r.customMeshes.length} custom meshes...`),console.log("[StorySplat Viewer] All custom mesh configs:",r.customMeshes),setTimeout(()=>{let t=0,e=0;r.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{la(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] ${r.customMeshes.length} custom meshes queued for loading`)):console.log("[StorySplat Viewer] No custom meshes to create")}(),xa(),r.lights&&0!==r.lights.length?(console.log(`[StorySplat Viewer] Creating ${r.lights.length} custom lights...`),r.lights.forEach((t,e)=>{let n=null;switch(t.type){case"point":n=Pa(t);break;case"directional":n=ka(t);break;case"hemispheric":n=Ta(t);break;case"ambient":Aa(t);break;case"spot":n=La(t);break;default:return void console.warn("[StorySplat Viewer] Unknown light type:",t.type)}if(n){nn.addChild(n),Ca.push(n);const o=t.id||t.name||`light-${e}`;Ma.set(o,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")),za(),Oa(),zt.length>0&&zt[0].url&&(Gt&&!a?Ci(zt[0].url,zt[0]).then(()=>{const t=()=>{const e=!Yt||Yt.material,n=!Zt||Zt.material;e&&n?requestAnimationFrame(()=>{D.preloader&&P(D.preloader),i.emit("loaded",{url:zt[0].url})}):requestAnimationFrame(t)};requestAnimationFrame(t)}):Gt||yi(zt[0].url,zt[0])),st&&!Gt&&(st.enabled=!0,console.log("[StorySplat Viewer] Reveal effect started immediately")),Gt&&!a);else if(st||!r.lodMetaUrl&&!r.mobileLodMetaUrl)setTimeout(()=>{D.preloader&&P(D.preloader)},200);else{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(()=>{D.preloader&&P(D.preloader)})})):requestAnimationFrame(o)};requestAnimationFrame(o)}if(function(){if(!r.includeXR)return;const n=r.xrMode||"both",o=!!U.xr;if(o&&("vr"===n||"both"===n)){const e=U.xr;e.isAvailable(t.XRTYPE_VR)&&(D.vrButton?.classList.add("available"),console.log("[StorySplat Viewer] VR is available")),e.on("available:"+t.XRTYPE_VR,t=>{t?D.vrButton?.classList.add("available"):D.vrButton?.classList.remove("available")})}if("ar"===n||"both"===n){const t=!!navigator.mediaDevices?.getUserMedia,n=ln();t&&n?(D.arButton?.classList.add("available"),Le("AR is available via 8th Wall",{hasCamera:t,mobile:n}),Ie(e,r.eighthWallBaseUrl).catch(t=>{const e=t instanceof Error?t.message:String(t);Re("8th Wall pre-load failed; will retry on AR tap",{message:e}),So(`AR engine failed to load: ${e}`)})):Le("AR not available",{hasCamera:t,mobile:n})}let s=1,a=4,l=!1;const c=new t.Vec3,d=new t.Quat,p=new t.Vec3,h=new t.Quat;if(o&&("vr"===n||"both"===n)){Ze.addComponent("script"),Ze.script.create(Je),Ze.script.create(tn);const t=U.xr;t.on("start",()=>{if(oo)return;eo=!0,console.log("[StorySplat Viewer] VR session started"),D.vrButton?.classList.add("active"),D.vrButton.textContent=x(u,"exitVr"),un.disable(),vn&&vn.disable(),c.copy(Ze.getPosition()),d.copy(Ze.getRotation()),p.copy(he.getPosition()),h.copy(he.getRotation());const t=h.getEulerAngles();Ze.setPosition(p.x,0,p.z),Ze.setEulerAngles(0,t.y,0),U.autoRender=!0,he.camera.nearClip=.01,U.scene.gsplat&&(s=U.scene.gsplat.colorUpdateDistance??1,a=U.scene.gsplat.colorUpdateAngle??4,U.scene.gsplat.colorUpdateDistance=5,U.scene.gsplat.colorUpdateAngle=20),We&&(l=We.enabled,We.enabled=!1),An.enabled=!1,$n=!1,Qn=0,i.emit("xrStart",{type:"vr"})}),t.on("end",()=>{oo||(eo=!1,console.log("[StorySplat Viewer] VR session ended"),D.vrButton?.classList.remove("active"),D.vrButton&&(D.vrButton.textContent=x(u,"vr")),no=null,Ze.setPosition(c),Ze.setRotation(d),he.setPosition(p),he.setRotation(h),U.autoRender=!1,U.renderNextFrame=!0,he.camera.nearClip=r.nearClip||.1,"explore"===Sn?un.enable():"walk"===Sn&&vn&&vn.enable(),U.scene.gsplat&&(U.scene.gsplat.colorUpdateDistance=s,U.scene.gsplat.colorUpdateAngle=a),We&&(We.enabled=l),requestAnimationFrame(()=>{e.prepend(U.graphicsDevice.canvas),U.renderNextFrame=!0}),i.emit("xrEnd",{}))})}D.vrButton&&o&&D.vrButton.addEventListener("click",()=>{const e=U.xr;eo&&"vr"===no?e.end():!eo&&e.isAvailable(t.XRTYPE_VR)&&(no="vr",he.camera.startXr(t.XRTYPE_VR,t.XRSPACE_LOCALFLOOR,{optionalFeatures:["hand-tracking"],callback:t=>{t&&(console.error("[StorySplat Viewer] Failed to start VR:",t),no=null)}}))}),D.arButton&&D.arButton.addEventListener("click",()=>{"ar"===no||oo||io?Co():eo||_o()})}(),r.htmlMeshes&&r.htmlMeshes.length>0){console.log("[StorySplat Viewer] Setting up HTML meshes:",r.htmlMeshes.length);const t=Qt(U,r.htmlMeshes);U.__htmlMeshManager=t,i.on("progressUpdate",()=>{const e=100*ki,n=$i(ki);t.updateVisibility(e,n,"loop"===Ii)})}try{const t=new URLSearchParams(window.location.search),e=t.get("waypoint"),n=t.get("autoplay");if(null!==e&&r.waypoints&&r.waypoints.length>0){const t=parseInt(e,10);!isNaN(t)&&t>=0&&t<r.waypoints.length&&(console.log("[StorySplat Viewer] URL param: navigating to waypoint",t),ds(t))}"true"!==n||o.autoPlay||r.autoPlay||(console.log("[StorySplat Viewer] URL param: starting autoplay"),ws())}catch(t){console.warn("[StorySplat Viewer] Could not parse URL parameters:",t)}i.emit("ready"),console.log("[StorySplat Viewer] Ready");if((r.audioEmitters&&r.audioEmitters.length>0||r.narrationTrack&&r.narrationTrack.audioUrl||r.waypoints?.some(t=>t.interactions?.some(t=>"audio"===t.type))||r.hotspots?.some(t=>t.audioUrl)||r.customMeshes?.some(t=>t.interaction?.playAudio&&t.interaction?.audioUrl))&&D.tapForAudio){const t=U.systems.sound?.manager?.context;if(t&&"suspended"===t.state){D.tapForAudio.style.display="",D.tapForAudio.classList.add("visible");const n=()=>{t.resume(),Us.forEach(t=>{"suspended"===t.state&&t.resume()}),Cs.forEach(t=>{const e=t.hotspotAudioSlotId,n=t.hotspotData;if(e&&"autoplay"===n?.audioTriggerMode&&t.hotspotAudioReady){const n=t.sound?.slot(e);n&&!n.isPlaying&&(n.play(),t.hotspotAudioPlaying=!0)}}),rr.forEach(t=>{const{config:e,slotId:n,entity:o,assetReady:i}=t;if(!i)return;if("autoplay"===(e.triggerMode||(!1!==e.autoplay?"autoplay":"proximity"))){const e=o.sound?.slot(n);e&&!e.isPlaying&&(e.play(),t.playing=!0)}}),Zs.forEach(t=>{const{config:e,slotId:n,entity:o,assetReady:i}=t;if(!i)return;if("autoplay"===(e.triggerMode||"proximity")){const e=o.sound?.slot(n);e&&!e.isPlaying&&(e.play(),t.playing=!0)}}),sa.forEach(t=>{const{config:e,audioSlotId:n,entity:o}=t;if(!n)return;if("autoplay"===(e.interaction?.audioTriggerMode||e.interaction?.activationMode||"click")){const e=o.sound?.slot(n);e&&!e.isPlaying&&(e.play(),t.audioPlaying=!0)}}),D.tapForAudio&&(D.tapForAudio.classList.remove("visible"),setTimeout(()=>{D.tapForAudio&&(D.tapForAudio.style.display="none")},500)),e.removeEventListener("click",n),e.removeEventListener("touchstart",n)};e.addEventListener("click",n),e.addEventListener("touchstart",n)}}if(a||Vo(S),At&&un&&un.syncFromCamera(new t.Vec3(0,1,0)),c){if(A(D,{nextWaypoint:ps,prevWaypoint:hs,play:ws,pause:Ss,isPlaying:()=>nt,getCurrentWaypointIndex:()=>et,getWaypointCount:()=>r.waypoints?.length||0,getWaypoints:()=>r.waypoints||[],setCameraMode:Vo,shareCurrentView:Tt,on:(t,e)=>i.on(t,e)},S,u,{container:e,hideProgressText:p.hideProgressText}),function(t,e){if(!t.sceneMenuContainer)return;const n=t.sceneMenuContainer.querySelectorAll(".storysplat-scene-menu-item[data-portal-id]"),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")})})}(D,t=>{const e=r.portals?.find(e=>e.id===t);e&&_r(e)}),D.muteButton&&D.muteButton.addEventListener("click",()=>{const t=(Ns?dr():cr(),Ns),e=D.muteButton.querySelector(".storysplat-unmuted-icon"),n=D.muteButton.querySelector(".storysplat-muted-icon");e&&(e.style.display=t?"none":""),n&&(n.style.display=t?"":"none"),D.muteButton.setAttribute("aria-label",x(u,t?"unmute":"mute"))}),D.relightingButton&&ut){const t=r.splatRelighting?.viewerDefaultOn??!1;console.log("[StorySplat Viewer] Relighting toggle button wired up, startOn:",t);let e=t,n=t?1:0;D.relightingButton.classList.toggle("active",t);let o=null;D.relightingButton.addEventListener("click",()=>{if(st&&st.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:",mt.size),D.relightingButton.classList.toggle("active",e),null!==o&&cancelAnimationFrame(o);const t=()=>{if(0===mt.size)return;const e=ut?ut.relightFade:n,i=n-e;if(Math.abs(i)<.01){for(const t of mt)t.setRelightFade(n);return void(o=null)}const s=e+.15*i;for(const t of mt)t.setRelightFade(s);o=requestAnimationFrame(t)};o=requestAnimationFrame(t)})}D.fisheyeButton&&D.fisheyeButton.addEventListener("click",()=>{const t=Pe>0;ke(t?0:.95),D.fisheyeButton.classList.toggle("active",!t)});const t=e.querySelector(".storysplat-hotspot-popup-close");t&&t.addEventListener("click",()=>{Ks(),Qs(e)}),D.waypointListContainer&&r.waypoints&&r.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")})})}(D,t=>{console.log("[StorySplat Viewer] Waypoint list: jumping to waypoint",t),"tour"!==Sn&&Vo("tour"),ds(t)}),i.on("waypointChange",({index:t})=>{G(D,t)}),G(D,0)),i.emit("progressUpdate",{progress:ki,index:et}),r.waypoints&&r.waypoints.length>0&&i.emit("waypointChange",{index:0,waypoint:r.waypoints[0],prevIndex:-1})}(o.autoPlay||r.autoPlay)&&ws()}).catch(t=>{console.error("[StorySplat Viewer] Failed to initialize:",t),D.preloader&&P(D.preloader),i.emit("error",t)});const Zr=()=>{U.resizeCanvas()};window.addEventListener("resize",Zr);const Kr=new Map,Qr=new Map,Jr=new Map,tl=new Map,el=new Map,nl=new Map,ol=new Map;a&&(Cs.forEach(t=>{const e=t.hotspotData?.id;e&&Kr.set(e,t)}),Ca.forEach((t,e)=>{const n=r.lights?.[e],o=n?.id||n?.name||`light-${e}`;Qr.set(o,t)}),Js.forEach((t,e)=>{Jr.set(e,t)}),Is.forEach(t=>{const e=t.portalData?.id;e&&el.set(e,t)}));let il=null;const sl=(t,e)=>{switch(e){case"hotspot":return Kr.get(t)||Cs.find(e=>e.hotspotData?.id===t)||null;case"portal":return el.get(t)||Is.find(e=>e.portalData?.id===t)||null;case"light":return Qr.get(t)||Ma.get(t)||null;case"particle":{const e=t.replace(/[^a-zA-Z0-9]/g,"_");return Js.get(e)||Js.get(t)||null}case"audioEmitter":return rr.get(t)?.entity||null;case"customMesh":{const e=sa.get(t);return e?.entity||null}case"mirrorPlane":return ol.get(t)||Ms.find(e=>e.name===t)||null;default:return null}},al={fog:{apply:t=>{const e=U.scene.fog;if(!e)return;t.has("fog.density")&&(e.density=t.get("fog.density")),t.has("fog.start")&&(e.start=t.get("fog.start")),t.has("fog.end")&&(e.end=t.get("fog.end"));const n=t.get("fog.color.r"),o=t.get("fog.color.g"),i=t.get("fog.color.b");void 0===n&&void 0===o&&void 0===i||e.color.set(n??e.color.r,o??e.color.g,i??e.color.b)},readValues:()=>{const t=U.scene.fog;return t?{"fog.density":t.density??.05,"fog.start":t.start??10,"fog.end":t.end??100,"fog.color.r":t.color?.r??.72,"fog.color.g":t.color?.g??.77,"fog.color.b":t.color?.b??.82}:null}},weather:{apply:t=>{if(!Te)return;let e=!1;t.has("weather.speed")&&(Te.speed=t.get("weather.speed"),e=!0),t.has("weather.drift")&&(Te.drift=t.get("weather.drift"),e=!0),t.has("weather.opacity")&&(Te.opacity=t.get("weather.opacity"),e=!0),t.has("weather.elongate")&&(Te.elongate=t.get("weather.elongate"),e=!0),t.has("weather.particleMinSize")&&(Te.particleMinSize=t.get("weather.particleMinSize"),e=!0),t.has("weather.particleMaxSize")&&(Te.particleMaxSize=t.get("weather.particleMaxSize"),e=!0);const n=t.get("weather.color.r"),o=t.get("weather.color.g"),i=t.get("weather.color.b");void 0===n&&void 0===o&&void 0===i||(void 0!==n&&(Te.color[0]=n),void 0!==o&&(Te.color[1]=o),void 0!==i&&(Te.color[2]=i),e=!0),e&&Te.markScalarsDirty()},readValues:()=>Te?{"weather.speed":Te.speed,"weather.drift":Te.drift,"weather.opacity":Te.opacity,"weather.elongate":Te.elongate,"weather.particleMinSize":Te.particleMinSize,"weather.particleMaxSize":Te.particleMaxSize,"weather.color.r":Te.color[0],"weather.color.g":Te.color[1],"weather.color.b":Te.color[2]}:null}};if(r.entityAnimations&&r.entityAnimations.length>0&&(il=new ue(sl,r.entityAnimations,al),console.log(`[StorySplat Viewer] Entity animation system initialized with ${r.entityAnimations.length} animation(s)`)),p.measurementsEnabled&&D.measureButton&&D.measureCanvas){const oc=D.measureButton,ic=D.measureCanvas,sc=p.sceneScale??r.uiOptions?.sceneScale??1,ac=p.sceneScaleUnit??r.uiOptions?.sceneScaleUnit??"meters",rc=p.measurementColor??r.uiOptions?.measurementColor??"#FF9800",lc={meters:"m",centimeters:"cm",feet:"ft",inches:"in"};let cc=!1,dc="single";const pc=[],hc=p.includeDefaultMeasurementsInViewer??r.uiOptions?.includeDefaultMeasurementsInViewer,uc=p.defaultMeasurementsVisibleOnLoad??r.uiOptions?.defaultMeasurementsVisibleOnLoad??!1,mc=hc??uc,gc=p.alwaysShowDefaultMeasurements??r.uiOptions?.alwaysShowDefaultMeasurements??!1;function fc(){for(let t=0;t<pc.length;t++)if(pc[t].locked)return!0;return!1}function yc(){return cc||gc&&fc()}if(mc&&Array.isArray(r.measurements)&&r.measurements.length>0)for(const Oc of r.measurements)Oc&&Oc.pointA&&Oc.pointB&&pc.push({from:new t.Vec3(Oc.pointA.x,Oc.pointA.y,Oc.pointA.z),to:new t.Vec3(Oc.pointB.x,Oc.pointB.y,Oc.pointB.z),locked:!0});let vc=null,bc=null;const xc=[],wc=[];let Sc=null;const _c=D.measureList||null;function Cc(){if(!_c)return;_c.innerHTML="";const t=document.createElement("div");t.className="storysplat-measure-list-header";const e=document.createElement("span");e.className="storysplat-measure-list-title",e.textContent="Measurements",t.appendChild(e);if(pc.some(t=>!t.locked)){const e=document.createElement("button");e.className="storysplat-measure-list-clear",e.textContent="Clear all",e.addEventListener("click",()=>{Ac(),Fc()}),t.appendChild(e)}if(_c.appendChild(t),0===pc.length){const t=document.createElement("div");t.className="storysplat-measure-list-empty",t.textContent="Click two points to measure",_c.appendChild(t)}else for(let t=0;t<pc.length;t++){const e=pc[t],n=document.createElement("div");n.className="storysplat-measure-list-item";const o=document.createElement("span");o.className="storysplat-measure-list-dot",n.appendChild(o);const i=document.createElement("span");i.style.flex="1";const s=e.from.distance(e.to);if(i.textContent=Mc(s),n.appendChild(i),e.locked){const t=document.createElement("span");t.className="storysplat-measure-list-lock",t.textContent="🔒",t.title="Default measurement",t.style.fontSize="12px",t.style.padding="0 4px",t.style.opacity="0.6",n.appendChild(t)}else{const e=document.createElement("button");e.className="storysplat-measure-list-clear",e.textContent="×",e.style.fontSize="14px",e.style.padding="0 4px",e.style.lineHeight="1";const o=t;e.addEventListener("click",()=>{pc.splice(o,1),Ic(),Fc(),Rc()}),n.appendChild(e)}_c.appendChild(n)}}function Mc(t){const e=t*sc*({meters:1,centimeters:100,feet:3.28084,inches:39.3701}[ac]||1),n=lc[ac]||"m";return`${e.toFixed(2)} ${n}`}const Ec=new t.Vec3;function Pc(t){he.camera.worldToScreen(t,Ec);const e=Ec;return e.z<0?null:{x:e.x,y:e.y}}function kc(t,n){const o=document.createElement("div");return o.className="storysplat-measure-point",o.style.left=`${t}px`,o.style.top=`${n}px`,e.appendChild(o),xc.push(o),o}function Tc(t,n,o){const i=document.createElement("div");return i.className="storysplat-measure-label",i.textContent=t,i.style.left=`${n}px`,i.style.top=`${o}px`,e.appendChild(i),wc.push(i),i}function Ac(){for(let t=pc.length-1;t>=0;t--)pc[t].locked||pc.splice(t,1);vc=null,bc=null,xc.forEach(t=>t.remove()),xc.length=0,wc.forEach(t=>t.remove()),wc.length=0,Sc&&(Sc.remove(),Sc=null),Cc(),Rc()}function Lc(){const t=e.getBoundingClientRect();ic.width=t.width,ic.height=t.height}function Rc(){Lc();const t=ic.getContext("2d");if(t&&(t.clearRect(0,0,ic.width,ic.height),yc())){t.strokeStyle=rc,t.lineWidth=2,t.setLineDash([]);for(const e of pc){if(!cc&&!e.locked)continue;const n=Pc(e.from),o=Pc(e.to);n&&o&&(t.beginPath(),t.moveTo(n.x,n.y),t.lineTo(o.x,o.y),t.stroke())}if(vc&&bc){const e=Pc(vc);e&&(t.strokeStyle=rc,t.globalAlpha=.6,t.setLineDash([6,4]),t.beginPath(),t.moveTo(e.x,e.y),t.lineTo(bc.x,bc.y),t.stroke(),t.setLineDash([]),t.globalAlpha=1)}}}function zc(){let t=0,e=0;for(let n=0;n<pc.length;n++){const o=pc[n];if(!cc&&!o.locked)continue;const i=Pc(o.from);i&&xc[t]?(xc[t].style.left=`${i.x}px`,xc[t].style.top=`${i.y}px`,xc[t].style.display=""):xc[t]&&(xc[t].style.display="none"),t++;const s=Pc(o.to);s&&xc[t]?(xc[t].style.left=`${s.x}px`,xc[t].style.top=`${s.y}px`,xc[t].style.display=""):xc[t]&&(xc[t].style.display="none"),t++,wc[e]&&(i&&s?(wc[e].style.left=(i.x+s.x)/2+"px",wc[e].style.top=(i.y+s.y)/2+"px",wc[e].style.display=""):wc[e].style.display="none"),e++}if(vc&&xc[t]){const e=Pc(vc);e?(xc[t].style.left=`${e.x}px`,xc[t].style.top=`${e.y}px`,xc[t].style.display=""):xc[t].style.display="none"}}function Dc(){cc=!0,Pr=!0,oc.classList.add("active"),Mr.style.cursor="none",Jn=new t.Color(1,.596,0),_c&&(_c.style.display="block"),Fc(),dc=Ft,Ft="none"}function Ic(){vc=null,bc=null,Sc&&(Sc.remove(),Sc=null),Fc()}function Fc(){xc.forEach(t=>t.remove()),xc.length=0,wc.forEach(t=>t.remove()),wc.length=0,Sc&&(Sc.remove(),Sc=null);for(let t=0;t<pc.length;t++){const e=pc[t];if(!cc&&!e.locked)continue;const n=Pc(e.from),o=kc(n?n.x:0,n?n.y:0);!n&&o&&(o.style.display="none");const i=Pc(e.to),s=kc(i?i.x:0,i?i.y:0);!i&&s&&(s.style.display="none");const a=Tc(Mc(e.from.distance(e.to)),n&&i?(n.x+i.x)/2:0,n&&i?(n.y+i.y)/2:0);n&&i||!a||(a.style.display="none")}if(vc){const t=Pc(vc);kc(t?t.x:0,t?t.y:0)}Cc()}Ar=()=>{xc.forEach(t=>t.remove()),xc.length=0,wc.forEach(t=>t.remove()),wc.length=0,Sc&&(Sc.remove(),Sc=null),_c&&_c.remove(),ic.remove(),oc.remove()},oc.addEventListener("click",()=>{cc?(cc=!1,Pr=!1,oc.classList.remove("active"),Mr.style.cursor="",Jn=null,vc=null,bc=null,Sc&&(Sc.remove(),Sc=null),gc&&fc()?Fc():(xc.forEach(t=>t.remove()),xc.length=0,wc.forEach(t=>t.remove()),wc.length=0),Rc(),_c&&(_c.style.display="none"),Ft=dc):Dc()});const Bc=t=>{"Escape"===t.key&&cc&&vc&&(Ic(),Rc())};document.addEventListener("keydown",Bc),Tr=Bc;const Vc=new t.Picker(U,1,1,!0);async function $c(t,e){try{const n=.25;Vc.resize(Math.floor(Mr.clientWidth*n),Math.floor(Mr.clientHeight*n));const o=U.scene.layers.getLayerByName("World");if(!o)return null;Vc.prepare(he.camera,U.scene,[o]);const i=Math.floor(t*n),s=Math.floor(e*n),a=await Vc.getWorldPointAsync(i,s);if(a&&isFinite(a.x)&&isFinite(a.y)&&isFinite(a.z)){const t=he.getPosition().distance(a);if(t>.1&&t<1e3)return a}}catch(t){}return null}kr=Vc,Mr.addEventListener("click",async t=>{if(!cc)return;if(Dr)return;const e=Mr.getBoundingClientRect(),n=t.clientX-e.left,o=t.clientY-e.top,i=await $c(n,o);i&&(t.stopPropagation(),vc?(pc.push({from:vc.clone(),to:i.clone(),locked:!1}),vc=i.clone()):vc=i.clone(),Fc(),Rc())},!0);let Uc=0;Mr.addEventListener("pointermove",t=>{if(!cc||!vc)return;const n=Mr.getBoundingClientRect();bc={x:t.clientX-n.left,y:t.clientY-n.top};const o=performance.now();if(o-Uc>80){Uc=o;const t=bc.x,n=bc.y;$c(t,n).then(o=>{if(cc&&vc&&o){const i=Mc(vc.distance(o));Sc||(Sc=document.createElement("div"),Sc.className="storysplat-measure-label",Sc.style.opacity="0.8",e.appendChild(Sc)),Sc.textContent=i;const s=Pc(vc);s&&(Sc.style.left=(s.x+t)/2+"px",Sc.style.top=(s.y+n)/2+"px")}})}Rc()}),U.on("update",()=>{yc()&&(pc.length>0||vc)&&(zc(),Rc())});(p.defaultMeasurementsVisibleOnLoad??r.uiOptions?.defaultMeasurementsVisibleOnLoad??!1)&&pc.some(t=>t.locked)?Dc():gc&&pc.some(t=>t.locked)&&(Fc(),Rc())}if(r.skins&&r.skins.length>0&&D.skinsButton&&D.skinsDropdown&&D.skinOverlay&&D.skinOverlayWrapper&&D.skinOpacitySlider&&D.skinExitButton){const Nc=D.skinsButton,Wc=D.skinsDropdown,Hc=D.skinOverlayWrapper,Gc=D.skinOverlay,jc=D.skinOpacitySlider,Xc=D.skinExitButton,qc=r.skins;let Yc=null,Zc=null,Kc=0,Qc=null;const Jc=()=>{Wc.querySelectorAll(".storysplat-skin-item").forEach(t=>{const e=t.getAttribute("data-skin-id");t.classList.toggle("active",!!Yc&&e===Yc)})},td=t=>{if(Yc){if(Yc=null,Qc&&(Gc.removeEventListener("load",Qc),Qc=null),Wc.querySelectorAll(".storysplat-skin-item.loading").forEach(t=>{t.classList.remove("loading")}),Hc.classList.remove("active"),Wc.classList.contains("open")||Nc.classList.remove("active"),Xc.style.display="none",Jc(),"tour"===Zc)try{ss()}catch(t){}Zc=null}},ed=e=>{const n=qc.find(t=>t.id===e);if(!n)return;if("tour"===Sn){null===Zc&&(Zc=Sn);try{Vo("explore")}catch(t){}}const o=new t.Vec3(n.cameraPosition.x,n.cameraPosition.y,-n.cameraPosition.z),i=new t.Vec3(n.cameraPivot.x,n.cameraPivot.y,-n.cameraPivot.z);if(n.cameraRotation){const e=n.cameraRotation,s=new t.Quat(-e.x,-e.y,e.z,e.w);"function"==typeof un.syncFromPose&&un.syncFromPose(o,s,i),he.setPosition(o),he.setRotation(s)}else he.setPosition(o),he.lookAt(i),un.syncFromCamera(i);Yc=e;const s=n.canvasWidth&&n.canvasHeight?n.canvasWidth/n.canvasHeight:void 0;Hc.style.aspectRatio=s?String(s):"",Gc.style.opacity=String(parseInt(jc.value,10)/100),Nc.classList.add("active"),Xc.style.display="block",Jc(),Kc=performance.now()+400,Hc.classList.remove("active"),Qc&&(Gc.removeEventListener("load",Qc),Qc=null);const a=Wc.querySelector(`.storysplat-skin-item[data-skin-id="${CSS.escape(e)}"]`);Wc.querySelectorAll(".storysplat-skin-item.loading").forEach(t=>{t!==a&&t.classList.remove("loading")}),a&&a.classList.add("loading");const r=()=>{a&&a.classList.remove("loading"),Yc===e&&requestAnimationFrame(()=>{Yc===e&&Hc.classList.add("active")})},l=Gc.src===n.imageUrl;l&&Gc.complete&&Gc.naturalWidth>0?r():(Qc=()=>{Gc.removeEventListener("load",Qc),Qc=null,r()},Gc.addEventListener("load",Qc),l||(Gc.src=n.imageUrl))},nd=()=>{const t=Wc.classList.contains("open")||!!Yc;Nc.classList.toggle("active",t)};Nc.addEventListener("click",t=>{t.stopPropagation();const e=!Wc.classList.contains("open");Wc.classList.toggle("open",e),nd()});const od=t=>{Wc.contains(t.target)||Nc.contains(t.target)||(Wc.classList.remove("open"),nd())};document.addEventListener("click",od),Lr=od,Wc.querySelectorAll(".storysplat-skin-item").forEach(t=>{t.addEventListener("click",e=>{e.stopPropagation();const n=t.getAttribute("data-skin-id");n&&ed(n)})}),jc.addEventListener("input",()=>{Gc.style.opacity=String(parseInt(jc.value,10)/100)}),Xc.addEventListener("click",t=>{t.stopPropagation(),td()});const id=new Set(["w","a","s","d","q","e","W","A","S","D","Q","E","ArrowUp","ArrowDown","ArrowLeft","ArrowRight"]),sd=t=>{if(Yc&&!(performance.now()<Kc)){if("keydown"===t.type){const e=t.key;if(!id.has(e))return}td()}};I.addEventListener("pointerdown",sd),I.addEventListener("wheel",sd,{passive:!0}),window.addEventListener("keydown",sd),Rr=sd,zr=I}const rl={app:U,canvas:I,capturePhoto:_t,shareCurrentView:Tt,goToWaypoint:t=>{if("explore"===Sn)throw new Error('[StorySplat Viewer] goToWaypoint() requires tour mode. Call setCameraMode("tour") first.');ds(t)},nextWaypoint:()=>{if("explore"===Sn)throw new Error('[StorySplat Viewer] nextWaypoint() requires tour mode. Call setCameraMode("tour") first.');ps()},prevWaypoint:()=>{if("explore"===Sn)throw new Error('[StorySplat Viewer] prevWaypoint() requires tour mode. Call setCameraMode("tour") first.');hs()},getCurrentWaypointIndex:()=>et,getWaypointCount:()=>r.waypoints?.length||0,setPosition:(t,e,n)=>{if("explore"!==Sn)throw new Error('[StorySplat Viewer] setPosition() requires explore mode. Call setCameraMode("explore") first.');if(nt)throw new Error("[StorySplat Viewer] setPosition() cannot be used during autoplay. Call pause() or stop() first.");un.disable(),he.setPosition(t,e,n),un.syncFromCamera(),un.enable(),requestAnimationFrame(()=>{he.setPosition(t,e,n),un.syncFromCamera()})},setRotation:(t,e,n)=>{if("explore"!==Sn)throw new Error('[StorySplat Viewer] setRotation() requires explore mode. Call setCameraMode("explore") first.');if(nt)throw new Error("[StorySplat Viewer] setRotation() cannot be used during autoplay. Call pause() or stop() first.");un.disable(),he.setEulerAngles(t,e,n),un.syncFromCamera(),un.enable(),requestAnimationFrame(()=>{he.setEulerAngles(t,e,n),un.syncFromCamera()})},getPosition:()=>{const t=he.getPosition();return{x:t.x,y:t.y,z:t.z}},getRotation:()=>{const t=he.getEulerAngles();return{x:t.x,y:t.y,z:t.z}},play:()=>{if(!At&&"explore"===Sn)throw new Error('[StorySplat Viewer] play() requires tour mode. Call setCameraMode("tour") first.');ws()},pause:()=>{if(!At&&"explore"===Sn)throw new Error('[StorySplat Viewer] pause() requires tour mode. Call setCameraMode("tour") first.');Ss()},stop:()=>{if(!At&&"explore"===Sn)throw new Error('[StorySplat Viewer] stop() requires tour mode. Call setCameraMode("tour") first.');!function(){if(At)return At.stop(),void i.emit("playbackStop");Ss(),rs(0)}()},isPlaying:()=>nt,isFrameSequencePlaying:()=>At?.getIsPlaying()??!1,setFrame:t=>At?.setFrame(t),getCurrentFrame:()=>At?.getCurrentFrame()??0,getTotalFrames:()=>At?.getTotalFrames()??0,setFps:t=>At?.setFps(t),getFps:()=>At?.getFps()??24,getFrameProgress:()=>At?.getProgress()??0,setFrameProgress:t=>At?.setProgress(t),goToOriginalSplat:Ei,goToSplat:async t=>{const e=xi();if(t===e)return void Ei();const n=zt.find(e=>e.url===t);n||Ut.has(t)?n&&!Ut.has(t)&&await yi(t,n):await yi(t);const o=Bt||e;if(o===e&&ot)pi(ot);else if(o){const t=Ut.get(o);t&&(t.enabled=!1)}if(!ui(t))throw new Error(`[StorySplat Viewer] Failed to switch to splat: ${t}`);n&&vi(n.defaultExploreMode,!1),i.emit("splatChange",{url:t,isOriginal:!1})},getCurrentSplatUrl:function(){return Bt||xi()},isShowingOriginalSplat:function(){return Bt===xi()||null===Bt},getAdditionalSplats:()=>zt.map(t=>({url:t.url,name:t.name,waypointIndex:t.waypointIndex,percentage:t.percentage})),isCompareMode:()=>Gt,getComparePosition:()=>Xt,setComparePosition:t=>{_i(t),jt&&jt.setPosition(t)},destroy:()=>{vt=!0,Ss(),Co(),Mi(),il&&(il.destroy(),il=null),At&&(At.destroy(),At=null),Gs&&(Gs.pause(),Gs.src="",Gs=null),window.removeEventListener("resize",Zr),e.removeAttribute("data-storysplat-editor"),e.querySelector(".storysplat-error-popup")?.remove(),e.querySelector(".storysplat-photo-share-backdrop")?.remove(),e.querySelector(".storysplat-share-toast")?.remove(),D.preloader&&D.preloader.remove(),D.scrollControls&&D.scrollControls.remove(),D.fullscreenButton&&D.fullscreenButton.remove(),D.helpButton&&D.helpButton.remove(),D.helpPanel&&D.helpPanel.remove(),D.waypointInfo&&D.waypointInfo.remove(),D.watermark&&D.watermark.remove(),D.wasdHint&&D.wasdHint.remove(),D.orbitHint&&D.orbitHint.remove(),D.doubleTapHint&&D.doubleTapHint.remove(),D.lookZone&&D.lookZone.remove(),D.joystick&&D.joystick.remove(),D.muteButton&&D.muteButton.remove(),D.relightingButton&&D.relightingButton.remove(),D.waypointListContainer&&D.waypointListContainer.remove(),D.sceneMenuContainer&&D.sceneMenuContainer.remove(),D.shareButton&&D.shareButton.remove(),D.fpsCounter&&D.fpsCounter.remove(),D.hotspotPopup&&D.hotspotPopup.remove(),D.portalPopup&&D.portalPopup.remove(),D.vrButton&&D.vrButton.remove(),D.arButton&&D.arButton.remove(),D.modeContainer&&D.modeContainer.remove(),D.exploreControls&&D.exploreControls.remove();const t=document.getElementById("storysplat-viewer-styles");t&&t.remove(),e.classList.remove("storysplat-viewer-container"),pr.forEach(t=>t.destroy()),pr.length=0,ga(),un.setCollisionEntities([]),vn&&vn.destroy();const n=U.__customScriptSystem;n&&n.dispose();const o=U.__htmlMeshManager;if(o&&o.destroy(),Ln.destroy(),zn.destroy(),An.destroy(),Mr.removeEventListener("mousemove",Er),Mo&&(Mo.destroy(),Mo=null),Eo&&(Eo.destroy(),Eo=null),Mn.destroy(),kr&&(kr.destroy(),kr=null),Tr&&(document.removeEventListener("keydown",Tr),Tr=null),Lr&&(document.removeEventListener("click",Lr),Lr=null),Rr&&(zr&&(zr.removeEventListener("pointerdown",Rr),zr.removeEventListener("wheel",Rr)),window.removeEventListener("keydown",Rr),Rr=null,zr=null),Ho&&(clearTimeout(Ho),Ho=null),Oo){const t=Oo._escHandler;t&&document.removeEventListener("keydown",t),Oo.remove(),Oo=null}if(D._cleanup&&(D._cleanup(),D._cleanup=void 0),Ar&&(Ar(),Ar=null),ye&&he.camera){try{he.camera.postEffects.removeEffect(ye)}catch(t){}ye=null}We&&(We.destroy(),We=null),U.destroy(),I.remove()},resize:Zr,navigateToScene:async t=>{const e={targetSceneId:t,activationMode:"click"};await _r(e)},setCameraMode:t=>Vo(t),getCameraMode:()=>Sn,resumeTourAtCurrentCamera:()=>ss(),setExploreMode:t=>{if("explore"!==Sn&&"walk"!==Sn)throw new Error('[StorySplat Viewer] setExploreMode() requires explore mode. Call setCameraMode("explore") first.');"walk"===t?("walk"!==Sn&&Vo("walk"),Fo("walk")):Bo(t)},setExploreModeSettings:t=>(t=>{void 0!==t.enabledExploreModes&&(C=_(t.enabledExploreModes),r.enabledExploreModes=C,un.enableOrbit=E("orbit"),un.enableFly=E("fly")),void 0===t.initialSplatExploreMode&&E(It)||(It=L(t.initialSplatExploreMode??It),r.initialSplatExploreMode=It,zo=It),"walk"===Ro||E(Ro)||(zo=L(It));const e=zo??L(It);!t.applyCurrent||"explore"!==Sn&&"walk"!==Sn?"explore"!==Sn&&"walk"!==Sn&&(Fo(e),zo=null):(Bo(e),zo=null)})(t),setProgress:(t,e)=>{if("explore"===Sn)throw new Error('[StorySplat Viewer] setProgress() requires tour mode. Call setCameraMode("tour") first.');rs(t,e)},getProgress:()=>ki,muteAll:()=>cr(),unmuteAll:()=>dr(),isMuted:()=>Ns,getHotspots:()=>Cs.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=Cs.find(e=>e.hotspotData?.id===t);if(!n)throw new Error(`[StorySplat Viewer] Hotspot not found: ${t}`);const o=n.hotspotData;o&&F(e,o,u)},closeHotspot:()=>{const t=e.querySelector(".storysplat-hotspot-popup"),n=e.querySelector(".storysplat-hotspot-overlay");t&&t.classList.remove("visible"),n&&n.classList.remove("visible"),Ks(),Qs(e)},setPanoramaMode:t=>$o(t),isPanoramaMode:()=>ne,setButtonLabels:t=>{u={...u,...t};const n=t=>x(u,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 l=e.querySelector(".storysplat-btn-next");l&&(l.textContent=n("next"));const c=e.querySelector(".storysplat-waypoint-list-toggle");if(c){const t=c.querySelector("svg");c.textContent="",c.append(n("waypoints")),t&&c.appendChild(t),c.setAttribute("aria-label",n("waypoints"))}const d=e.querySelector(".storysplat-vr-btn");d&&!d.classList.contains("active")&&(d.textContent=n("vr"),d.setAttribute("aria-label",n("vr")));const p=e.querySelector(".storysplat-ar-btn");p&&!p.classList.contains("active")&&(p.textContent=n("ar"),p.setAttribute("aria-label",n("ar")));const h=e.querySelector(".storysplat-fullscreen-btn");h&&h.setAttribute("aria-label",n("fullscreen"));const m=e.querySelector(".storysplat-share-btn");m&&(m.setAttribute("aria-label",n("share")),m.setAttribute("title",n("share")));const g=e.querySelector(".storysplat-hotspot-popup-close");g&&(g.textContent=n("close"));const f=e.querySelector(".storysplat-portal-popup-confirm");f&&(f.textContent=n("yes"));const y=e.querySelector(".storysplat-portal-popup-cancel");y&&(y.textContent=n("cancel"));const v=e.querySelector(".storysplat-help-panel");if(v){v.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 v.appendChild(o),o},e=()=>v.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 b=e.querySelector(".storysplat-help-btn");b&&b.setAttribute("title",n("helpTitle"))},on:(t,e)=>i.on(t,e),off:(t,e)=>i.off(t,e),onInternal:(t,e)=>i.onInternal(t,e)};if(a){const ad=rl;ad.setCameraMode=t=>Vo(t),ad.getCameraMode=()=>Sn,ad.getCameraControls=()=>un,ad.setOrbitCameraSettings=t=>fn(t),ad.suppressInput=()=>{Ki=!0,Zi=!1,un.disable()},ad.resumeInput=()=>{Ki=!1,"explore"===Sn&&un.enable()},ad.setReticleEnabled=t=>{Zn=t,t||(An.enabled=!1,$n=!1,Qn=0)},ad.setReticleColor=e=>{Jn=e?new t.Color(e.r,e.g,e.b):null},ad.setEditingHotspotId=t=>{l=t},ad.setRefocusTapMode=t=>{Ft=t,ro?.setTapMode(t)},ad.getApp=()=>U,ad.getSplatEntity=()=>ot,ad.getSwapSplatEntity=t=>Ut.get(t)??null,ad.ensureSwapSplatLoaded=async(t,e)=>Ut.has(t)?Ut.get(t):(await yi(t,e),Ut.get(t)??null),ad.getAllHotspotEntities=()=>Kr,ad.getAllLightEntities=()=>Qr,ad.getAllPortalEntities=()=>el,ad.getAllMirrorPlaneEntities=()=>ol,ad.getAllCustomMeshEntities=()=>tl,ad.getAllParticleEntities=()=>Jr,ad.getAllCollisionMeshEntities=()=>nl,ad.getAllHtmlMeshEntities=()=>{const t=U.__htmlMeshManager,e=new Map;return t&&t.getAllMeshes().forEach((t,n)=>{e.set(n,t.entity)}),e},ad.getCamera=()=>he,ad.setProgress=(t,e)=>rs(t,e),ad.getProgress=()=>ki,ad.setTransitionSpeed=t=>{ls=500*(t||1)},ad.updateAdditionalSplats=t=>{zt=t,Ot=null},ad.setSwapTransitionEnabled=t=>{li=t},ad.setPrimarySplatUrl=t=>{Vt=t},ad.enableCompareMode=async(t,e,n)=>{Mi(),e&&Object.assign(te,e),Gt=!0,await Ci(t,n)},ad.disableCompareMode=()=>{Mi(),Gt=!1},ad.updateSwapSplatTransform=(t,e)=>{const n=Ut.get(t);if(n){if(e.scale){const t=r.invertXScale||!1,o=r.invertYScale||!1,i=e.scale;n.setLocalScale(t?-i.x:i.x,o?i.y:-i.y,t!==o?i.z:-i.z)}if(e.position){const t=e.position;n.setPosition(t[0],t[1],-t[2])}if(e.rotation){const t=e.rotation;n.setEulerAngles(t[0]*(180/Math.PI),t[1]*(180/Math.PI),-t[2]*(180/Math.PI))}}},ad.setCompareSettings=t=>{if(Object.assign(te,t),jt){if(t.orientation){jt.setOrientation(t.orientation);const e="horizontal"===t.orientation?1:0;Yt&&Yt.updateOrientation(e),Zt&&Zt.updateOrientation(e)}if(void 0===t.beforeLabel&&void 0===t.afterLabel||jt.setLabels(t.beforeLabel??te.beforeLabel??"Before",t.afterLabel??te.afterLabel??"After"),void 0===t.lineColor&&void 0===t.lineOpacity||jt.updateLineStyle(t.lineColor,t.lineOpacity),void 0===t.handleColor&&void 0===t.handleOpacity||jt.updateHandleColor(t.handleColor??te.handleColor??"#ffffff",t.handleOpacity??te.handleOpacity),void 0!==t.handleStyle&&jt.updateHandleStyle(t.handleStyle),void 0!==t.initialPosition){const e=t.initialPosition/100;jt.setPosition(e),_i(e)}if(void 0!==t.showLabels){const e=jt.overlay?.querySelectorAll(".ss-compare-label");e&&e.forEach(e=>{e.style.display=t.showLabels?"":"none"})}void 0!==t.enableKeyboard&&jt.setEnableKeyboard(t.enableKeyboard),void 0!==t.enableSnap&&jt.setEnableSnap(t.enableSnap)}},ad.addHotspot=t=>{const e=t.id||`hotspot-${Date.now()}`,n=fr(t,Cs.length);return Kr.set(e,n),e},ad.removeHotspot=t=>{const e=Kr.get(t);if(e){const n=e.leaderLineEntity;n&&(n._leaderMat?.destroy(),n.destroy()),e.destroy(),Kr.delete(t);const o=Cs.indexOf(e);o>=0&&Cs.splice(o,1)}},ad.updateHotspot=(t,e)=>{const n=Kr.get(t);if(!n)return;if(e.position&&n.setPosition(e.position.x,e.position.y,-e.position.z),void 0!==e.opacity){const t=n.hotspotMaterial||n.render?.meshInstances?.[0]?.material;t&&"opacity"in t&&(t.opacity=e.opacity,t.update())}const o={...n.hotspotData||{},...e};if(n.hotspotData=o,void 0!==e.position||void 0!==e.leaderLineEnabled||void 0!==e.leaderLineTarget||void 0!==e.leaderLineColor||void 0!==e.leaderLineWidth||void 0!==e.leaderLineOpacity){const t=n.leaderLineEntity;t&&(t._leaderMat?.destroy(),t.destroy());const e=gr(o,n.getPosition().clone());e?(nn.addChild(e),n.leaderLineEntity=e):n.leaderLineEntity=void 0}},ad.addLight=e=>{const n=e.id||e.name||`light-${Date.now()}`;let o=null;switch(e.type){case"point":default:o=Pa(e);break;case"directional":o=ka(e);break;case"hemispheric":o=Ta(e);break;case"ambient":Aa(e);break;case"spot":o=La(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,nn.addChild(o),Ca.push(o),Qr.set(n,o),Ma.set(n,o)}return n},ad.removeLight=t=>{const e=Qr.get(t);if(e){e.destroy(),Qr.delete(t),Ma.delete(t);const n=Ca.indexOf(e);n>=0&&Ca.splice(n,1)}},ad.updateLight=(t,e)=>{const n=Qr.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=Ea(e.color))))},ad.addCustomMesh=t=>{const e=t.id||t.name||`mesh-${Date.now()}`,n=la(t,sa.size);return n&&tl.set(e,n),e},ad.updateCustomMesh=(t,e)=>{const n=tl.get(t);n&&(e.position&&n.setPosition(e.position.x??0,e.position.y??0,-(e.position.z??0)),e.rotation&&n.setRotation(ur(e.rotation.x??0,e.rotation.y??0,e.rotation.z??0)),e.scale&&n.setLocalScale(e.scale.x??1,e.scale.y??1,e.scale.z??1))},ad.removeCustomMesh=t=>{const e=tl.get(t);e&&(sa.forEach((t,n)=>{t.entity===e&&(t.modelAsset&&(U.assets.remove(t.modelAsset),t.modelAsset.unload()),sa.delete(n))}),e.destroy(),tl.delete(t))},ad.addParticleSystem=e=>{const n=e.id||e.name||`particle-${Date.now()}`,o=ia(e),i=e.particleTexture||"flare";if("custom"===i&&e.customTextureUrl){oa(`custom_${e.customTextureUrl}`,e.customTextureUrl).then(t=>{o.particlesystem&&(o.particlesystem.colorMap=t)}).catch(t=>{console.warn("[Editor] Failed to load custom particle texture:",t)})}else{const t=ea.has(i)?i:"flare";o.particlesystem&&(o.particlesystem.colorMap=na(t))}const s=new t.Entity("particle-picker");s.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1}),s.setLocalScale(.15,.15,.15);const a=new t.StandardMaterial;a.diffuse=new t.Color(1,.5,0,1),a.opacity=.3,a.blendType=t.BLEND_NORMAL,a.depthWrite=!1,a.update(),s.render.meshInstances[0].material=a,o.addChild(s),nn.addChild(o);const r=n.replace(/[^a-zA-Z0-9]/g,"_");return Js.set(r,o),Jr.set(n,o),n},ad.removeParticleSystem=t=>{const e=t.replace(/[^a-zA-Z0-9]/g,"_"),n=Jr.get(t)||Js.get(e);n&&(n.destroy(),Jr.delete(t),Js.delete(e))},ad.addPortal=t=>{const e=t.id||`portal-${Date.now()}`,n=xr(t,Is.length);return el.set(e,n),e},ad.removePortal=t=>{const e=el.get(t);if(e){const n=e.videoElement;n&&(n.pause(),n.src="");const o=e.leaderLineEntity;o&&(o._leaderMat?.destroy(),o.destroy()),e.destroy(),el.delete(t);const i=Is.indexOf(e);i>=0&&Is.splice(i,1)}},ad.updatePortal=(t,e)=>{const n=el.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)}if(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)),void 0!==e.opacity){const t=n.portalMaterial||n.render?.meshInstances?.[0]?.material;t&&"opacity"in t&&(t.opacity=e.opacity,t.update())}const o={...n.portalData||{},...e};if(n.portalData=o,void 0!==e.position||void 0!==e.leaderLineEnabled||void 0!==e.leaderLineTarget||void 0!==e.leaderLineColor||void 0!==e.leaderLineWidth||void 0!==e.leaderLineOpacity){const t=n.leaderLineEntity;t&&(t._leaderMat?.destroy(),t.destroy());const e=gr(o,n.getPosition().clone());e?(nn.addChild(e),n.leaderLineEntity=e):n.leaderLineEntity=void 0}},ad.addMirrorPlane=t=>{const e=t.id||`mirror-${Date.now()}`,n=zs(t,Ms.length);return ol.set(e,n),e},ad.removeMirrorPlane=t=>{Ds(t)},ad.updateMirrorPlane=(t,e)=>{const n=ol.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&&n.setLocalScale(e.scale.x??1,e.scale.y??1,e.scale.z??1);const o=n._mirrorMaterial;if(o&&(void 0!==e.intensity&&o.setParameter("uIntensity",e.intensity),e.tint)){const t=parseInt(e.tint.slice(1,3),16)/255,n=parseInt(e.tint.slice(3,5),16)/255,i=parseInt(e.tint.slice(5,7),16)/255;o.setParameter("uTint",[t,n,i])}n._mirrorData={...n._mirrorData,...e}};let rd=null;return ad.showProximityRadius=(e,n,o)=>{if(rd){const t=rd._proximityUpdateFn;t&&U.off("update",t),rd.destroy(),rd=null}const i=new t.Entity("proximity-radius-preview"),s=e.getPosition();i.setPosition(s.x,s.y,s.z);const a=(e,n,o,i)=>{const s=new t.Entity(e);s.addComponent("render",{type:"cylinder",castShadows:!1,receiveShadows:!1}),s.setLocalScale(2*n,.05,2*n),s.setLocalPosition(0,0,0);const a=new t.StandardMaterial;return a.diffuse=o,a.emissive=new t.Color(.8*o.r,.8*o.g,.8*o.b),a.opacity=i,a.blendType=i<1?t.BLEND_NORMAL:t.BLEND_NONE,a.depthWrite=i>=1,a.cull=t.CULLFACE_NONE,a.useLighting=!1,a.update(),s.render.meshInstances[0].material=a,s},r=(e,n,o,i)=>{const s=new t.Entity(e);s.addComponent("render",{type:"cylinder",castShadows:!1,receiveShadows:!1}),s.setLocalScale(2*n,i,2*n),s.setLocalPosition(0,0,0);const a=new t.StandardMaterial;return a.diffuse=o,a.emissive=new t.Color(.9*o.r,.9*o.g,.9*o.b),a.opacity=1,a.blendType=t.BLEND_NONE,a.depthWrite=!0,a.cull=t.CULLFACE_NONE,a.useLighting=!1,a.update(),s.render.meshInstances[0].material=a,s};if(n>0){const e=a("trigger-disc",n,new t.Color(.15,.5,1),.5),o=r("trigger-ring",n,new t.Color(.3,.7,1),.15);i.addChild(e),i.addChild(o)}if(o){const e=o.maxDistance,n=o.refDistance;if(e&&e>0){const n=a("audio-max-disc",e,new t.Color(1,.6,.15),.12),o=r("audio-max-ring",e,new t.Color(1,.7,.2),.1);i.addChild(n),i.addChild(o)}if(n&&n>0){const e=a("audio-ref-disc",n,new t.Color(.2,.8,.3),.3),o=r("audio-ref-ring",n,new t.Color(.3,.9,.4),.12);i.addChild(e),i.addChild(o)}if(n&&e&&e>n&&o.rolloffFactor){const s=o.rolloffFactor,a=o.distanceModel||"linear",l=[.75,.5,.25];for(const o of l){let l=null;if("linear"===a?l=n+(1-o)*(e-n)/s:"inverse"===a?l=n+(n/o-n)/s:"exponential"===a&&(l=n*Math.pow(o,-1/s)),null!==l&&l>n&&l<e){const e=1-o,n=new t.Color(.6,.6,.6),s=r(`rolloff-${Math.round(100*o)}`,l,n,.04),a=s.render.meshInstances[0].material;a.opacity=.3+.4*e,a.blendType=t.BLEND_NORMAL,a.depthWrite=!1,a.update(),i.addChild(s)}}}}const l=[`trigger=${n}`];o?.maxDistance&&l.push(`max=${o.maxDistance}`),o?.refDistance&&l.push(`ref=${o.refDistance}`),o?.rolloffFactor&&l.push(`rolloff=${o.rolloffFactor}`),console.log(`[Editor] Proximity radius: entity=${e.name}, ${l.join(", ")}`);const c=e.name,d=()=>{let t=e.parent?e:null;if(!t&&c&&(t=U.root.findByName(c)),t){const e=t.getPosition();i.setPosition(e.x,e.y,e.z)}};U.on("update",d),i._proximityUpdateFn=d,U.root.addChild(i),rd=i},ad.hideProximityRadius=()=>{if(rd){const t=rd._proximityUpdateFn;t&&U.off("update",t),rd.destroy(),rd=null}},ad.addHtmlMesh=t=>{const e=t.id||`html-mesh-${Date.now()}`;let n=U.__htmlMeshManager;return n||(n=Qt(U,[]),U.__htmlMeshManager=n),n.createMesh({...t,id:e,html:t.html||t.htmlContent||""}),e},ad.removeHtmlMesh=t=>{const e=U.__htmlMeshManager;e&&e.destroyMesh(t)},ad.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,l=Array.isArray(s)?s[1]:s.y,c=Array.isArray(s)?s[2]:s.z;if(o.setPosition(a??0,l??0,-(c??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,b=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(b,e))}else o.setRotation(b)}const d=e.scaling,p=(d?Array.isArray(d)?d[0]:d.x:1)??1,h=(d?Array.isArray(d)?d[1]:d.y:1)??1,u=(d?Array.isArray(d)?d[2]:d.z:1)??1;if("plane"===i){const t=.01;o.setLocalScale(3*p,u*t,3*h)}else"cube"===i||"sphere"===i?o.setLocalScale(3*p,3*h,3*u):"floor"===i?o.setLocalScale(100*p,1*h,100*u):o.setLocalScale(p,h,u);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})}if(o.addComponent("collision",{type:"sphere"===i?"sphere":"box"}),o.enabled=!1!==e.visible,o._collisionMeshId=n,o._collisionMeshType=i,nn.addChild(o),nl.set(n,o),!vn){const t=null!=r.walkSpeed?r.walkSpeed:sn;vn=new at(he,U,{moveSpeed:t,sprintMultiplier:2,lookSensitivity:1/(r.cameraRotationSensitivity||4e3)*10,playerHeight:r.playerHeight||1.6,gravity:20,jumpVelocity:8,collisionRadius:.3,stepHeight:.3}),w.includes("walk")||w.push("walk")}return vn.addCollisionEntity(o),un.setCollisionEntities(vn.collisionMeshEntities),n},ad.removeCollisionMesh=t=>{const e=nl.get(t);e&&(vn&&(vn.removeCollisionEntity(e),un.setCollisionEntities(vn.collisionMeshEntities)),e.destroy()),nl.delete(t)},ad.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||10,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&&!1!==e.enabled){const o=new t.Asset(`audio-emitter-${n}`,"audio",{url:e.url});o.on("load",()=>{if(vt)return;const t=i.sound?.slot(n);t&&(t.asset=o.id);const s=rr.get(n);if(s){s.assetReady=!0,t&&Hs(t,t=>{s.playing=t});"autoplay"===(e.triggerMode||(!1!==e.autoplay?"autoplay":"proximity"))&&t&&(t.play(),s.playing=!0)}}),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,nn.addChild(i),rr.set(n,{entity:i,config:e,slotId:n,playing:!1,assetReady:!1}),n},ad.removeAudioEmitter=t=>{const e=rr.get(t);if(e){const n=e.entity.sound?.slot(e.slotId);n&&n.stop(),e.entity.destroy(),rr.delete(t)}},ad.updateAudioEmitter=(t,e)=>{const n=rr.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})},ad.setSplatRelighting=t=>{if(r.splatRelighting={...r.splatRelighting,...t},!1!==t.enabled){if(!ut&&Ca.length>0&&ot&&za(),ut){if(void 0!==t.ambientColor||void 0!==t.ambientIntensity){const e=t.ambientColor??r.splatRelighting?.ambientColor??"#ffffff",n=t.ambientIntensity??r.splatRelighting?.ambientIntensity??0;for(const t of mt)t.setAmbientColor(e),t.ambientR*=n,t.ambientG*=n,t.ambientB*=n}if(!0===t.enabled&&!gt){for(const t of mt)t.enabled=!0,t.setRelightFade(1);gt=!0}}void 0===t.shadowsEnabled&&void 0===t.shadowIntensity&&void 0===t.shadowGroundY&&void 0===t.shadowPlaneScale||Oa()}else{for(const t of mt)t.enabled=!1;gt=!1}},ad.setRelightFade=t=>{for(const e of mt)e.setRelightFade(t)},ad.setNarrationTrack=t=>{if(t)qs(t);else{if(Gs){Gs.pause(),Gs.src="";const t=Os.indexOf(Gs);t>=0&&Os.splice(t,1),Ws.delete(Gs),Gs=null}js=[],Xs=!1}},ad.setSkybox=(t,e)=>{_a(t,e)},ad.setSkyboxRotation=e=>{const n=new t.Quat;n.setFromEulerAngles(0,-e*(180/Math.PI),0),U.scene.skyboxRotation=n,fa&&fa.setEulerAngles(0,-e*(180/Math.PI),0),r.skybox&&(r.skybox.rotation=e),r.skyboxRotation=e},ad.setBackgroundColor=e=>{const n=new t.Color(e.r/255,e.g/255,e.b/255);he.camera&&(he.camera.clearColor=n)},ad.setFOV=t=>{he.camera&&(he.camera.fov=t)},ad.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),ot&&(console.log("[SPLAT] loadSplatByUrl: Destroying existing splat entity"),ot.destroy(),ot=null);for(const e of o)try{const n=decodeURIComponent(e.toLowerCase().split(/[?#]/)[0]).split("/").pop()||"";if(n.endsWith(".glb")||n.endsWith(".gltf")){console.log("[SPLAT] loadSplatByUrl: Detected mesh format, loading as container:",e);const t=sa.get("__primary-mesh__");return t&&(t.entity.destroy(),t.modelAsset&&U.assets.remove(t.modelAsset),sa.delete("__primary-mesh__")),void la({id:"__primary-mesh__",name:"Primary Model",modelUrl:e,position:{x:0,y:0,z:0}},0)}console.log("[SPLAT] loadSplatByUrl: Trying URL:",e);const o=e.includes("lod-meta.json")?await gi(e):null,s=new t.Asset("splat-"+Date.now(),"gsplat",{url:e});return await new Promise((t,n)=>{s.ready(()=>{if(vt)t();else try{if(it=o,ot=Ko(s,e,o),vn){vn.setSplatEntity(ot);const t=vn.voxelCollisionInstance;t&&un.setVoxelCollision(t,ot)}console.log("[SPLAT] loadSplatByUrl: New splat entity added to scene"),t()}catch(t){console.error("[SPLAT] loadSplatByUrl: Error during gsplat setup:",t),n(t)}}),s.on("error",t=>{console.error("[SPLAT] loadSplatByUrl: Asset load error:",{url:e,error:t}),n(t)}),Qo(s,e)}),Vt=e,void i.emit("loaded",{bandwidthUsed:0,isStorySplatHosted:Zo(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),ad.addWaypoint=t=>{r.waypoints||(r.waypoints=[]),r.waypoints.push(t)},ad.removeWaypoint=t=>{r.waypoints&&t>=0&&t<r.waypoints.length&&r.waypoints.splice(t,1)},ad.updateWaypoint=(t,e)=>{r.waypoints&&t>=0&&t<r.waypoints.length&&Object.assign(r.waypoints[t],e)},ad.rebuildTourPath=e=>{if(e&&(r.waypoints=e),ts.length=0,es.length=0,ns.length=0,r.waypoints&&r.waypoints.length>0&&r.waypoints.forEach(e=>{const n=e.position?new t.Vec3(e.position.x,e.position.y,-e.position.z):new t.Vec3(0,r.playerHeight||1.6,0);ts.push(n);const o=e.rotation?Jo(e.rotation):new t.Quat;es.push(o);let i=e.fov||os;i<3.5&&(i*=180/Math.PI),ns.push(i)}),ts.length>=2){const t=_n;_n=!0,as(ki),_n=t}else ts.length>0&&(Gi=ts[0].clone(),ji=es[0].clone(),is=ns[0]);const n=r.waypoints?.length||1,o=Math.max(1,20*Fi(Ii,n));void 0!==r.autoplaySpeed&&(Wi=60*r.autoplaySpeed/o),console.log("[Editor] Tour path rebuilt with",ts.length,"waypoints")},ad.setSplatScale=(t,e,n)=>{const o=e??r.invertXScale??!1,i=n??r.invertYScale??!1;if(r.invertXScale=o,r.invertYScale=i,r.scale={x:t,y:t,z:t},!ot)return;const s=o?-t:t,a=i?t:-t,l=o!==i?t:-t;ot.setLocalScale(s,a,l)},ad.setSplatPosition=(t,e,n)=>{r.position=[t,e,n],ot&&ot.setPosition(t,e,-n)},ad.setSplatRotation=(t,e,n)=>{r.rotation=[t,e,n],ot&&fi(ot,[t,e,n],it)},ad.setAutoplaySpeed=t=>{r.autoplaySpeed=t;const e=r.waypoints?.length||1,n=Math.max(1,20*Fi(Ii,e));Wi=60*t/n},ad.setLoopMode=t=>{const e=Ii;Ii=t,r.loopMode=t,ki=Ui(ki,e,t),Ti=Ui(Ti,e,t);const n=r.waypoints?.length||1,o=Math.max(1,20*Fi(Ii,n));void 0!==r.autoplaySpeed&&(Wi=60*r.autoplaySpeed/o),_n&&as(ki)},ad.setScrollSpeed=t=>{r.scrollSpeed=t},ad.startPlayground=()=>{or()},ad.stopPlayground=()=>{ir()},ad.isPlaygroundActive=()=>Ga,ad.initVoxelCollision=async t=>{if(!vn){const t=null!=r.walkSpeed?r.walkSpeed:sn;vn=new at(he,U,{moveSpeed:t,sprintMultiplier:2,lookSensitivity:1/(r.cameraRotationSensitivity||4e3)*10,playerHeight:r.playerHeight||1.6,gravity:20,jumpVelocity:8,collisionRadius:.3,stepHeight:.3}),w.includes("walk")||w.push("walk"),console.log("[StorySplat Viewer] CharacterController created lazily for voxel collision")}ot&&vn.setSplatEntity(ot),await vn.initVoxelCollision(t);const e=vn.voxelCollisionInstance;e&&un.setVoxelCollision(e,ot),console.log("[StorySplat Viewer] Voxel collision loaded via editor API")},ad.setVoxelDebug=t=>{vn&&vn.setVoxelDebug(t)},ad.getVoxelDebugVisible=()=>vn?.voxelDebugVisible??!1,ad.clearVoxelDebug=()=>{vn?.clearVoxelDebug()},ad.getVoxelDebugLayerId=()=>Pn.id,ad.getEntityAnimationSystem=()=>il,ad.setEntityAnimations=t=>{il?il.setAnimations(t):il=new ue(sl,t,al)},ad.setPostProcessing=e=>{if(Ne(e)&&he.camera)try{We||(We=new t.CameraFrame(U,he.camera)),We.enabled=!0,_e(We,e)}catch(t){console.warn("[StorySplat Viewer] CameraFrame setup failed:",t)}else We&&(_e(We,{}),We.enabled=!1);const n=!1!==e?.antialiasing?.enabled;n&&!We?.enabled&&he.camera?(ye||(ye=new fe(U.graphicsDevice)),he.camera.postEffects.effects?.some(t=>t.effect===ye)||he.camera.postEffects.addEffect(ye)):ye&&he.camera&&(he.camera.postEffects.removeEffect(ye),ye=null),n&&We?.enabled&&(We.rendering.sharpness=Math.max(We.rendering.sharpness,.5)),Ce(e?.fog),Me(e?.exposure)},ad.setFisheye=t=>{ke(t)},ad.setWeather=t=>{r.weather=t,Ue(t)},ad.setLodSettings=t=>{if(!U.scene.gsplat)return;const e={"desktop-max":{range:[0,5],lodBaseDistance:15,lodMultiplier:2},desktop:{range:[0,2],lodBaseDistance:15,lodMultiplier:2},"mobile-max":{range:[1,2],lodBaseDistance:15,lodMultiplier:2},mobile:{range:[2,5],lodBaseDistance:15,lodMultiplier:2}};let n=0,o=5,i=15,s=2;if("custom"===t.preset)n=t.lodRangeMin??0,o=t.lodRangeMax??5,i=t.lodBaseDistance??15,s=t.lodMultiplier??2;else if("auto"!==t.preset){const a=e[t.preset];a&&(n=a.range[0],o=a.range[1],i=a.lodBaseDistance,s=a.lodMultiplier)}else{let a;a=q&&t.mobilePreset&&e[t.mobilePreset]?t.mobilePreset:!q&&t.desktopPreset&&e[t.desktopPreset]?t.desktopPreset:dn(q);const r=e[a];n=r.range[0],o=r.range[1],i=r.lodBaseDistance,s=r.lodMultiplier}U.scene.gsplat.lodRangeMin=n,U.scene.gsplat.lodRangeMax=o;const a=t.splatBudget??0;U.scene.gsplat.splatBudget=a,Q=i,J=s,K=[n,o];const r=t=>{if(!t)return;const e=t.gsplat;e&&(e.lodBaseDistance=i,e.lodMultiplier=s)};r(ot),Ut.forEach(t=>r(t));const l=t.renderer;l&&l in en&&(U.scene.gsplat.renderer=en[l],on(U,"setLodSettings",l)),console.log("[StorySplat Viewer] LOD settings updated:",{preset:t.preset,rangeMin:n,rangeMax:o,baseDistance:i,multiplier:s,budget:a,renderer:t.renderer})},ad.loadSegments=async(t,e)=>{const n=async(o=20)=>{if(!ot?.gsplat)return o<=0?void console.warn("[StorySplat Viewer] Cannot load segments: splat entity never became ready"):(await new Promise(t=>setTimeout(t,500)),n(o-1));const i=await ge(ot,t,e);xn=i,r.enableSegmentHover=!0,r.segmentDataUrl=t,r.segmentMetaUrl=e};try{await n()}catch(t){console.warn("[StorySplat Viewer] Segment loading failed:",t)}},ad.getSegmentMetadata=()=>xn?.metadata??null,ad.getSegmentIds=()=>xn?.segmentIds??null,ad.selectBySegment=(t,e,n)=>{if(!xn)return;const o=xn.segmentIds;if(e){for(let n=0;n<Math.min(o.length,e.length);n++)o[n]===t?e[n]|=1:e[n]&=-2;n?.()}else console.warn("[StorySplat Viewer] selectBySegment: no editAttachmentState provided")},ad.clearSegments=()=>{xn&&(xn.destroy(),xn=null),wn&&(wn.remove(),wn=null),r.enableSegmentHover=!1},ad}if(r.customScript&&""!==r.customScript.trim()){console.log("[StorySplat Viewer] Initializing custom script system...");const ld=ie(rl,U,I,r.customScript,()=>ki,()=>et);ld&&(U.__customScriptSystem=ld,console.log("[StorySplat Viewer] Custom script system initialized"))}const ll=!0===o.disableAnalytics,cl=[];if(o.analytics&&!ll&&!s){const cd=o.analytics.baseUrl??"https://discover.storysplat.com";s=re(rl,cd,o.analytics.sceneId,o.analytics.ownerId),cl.push(s)}if(o.googleTagId&&!ll&&cl.push(pe(rl,o.googleTagId,{sceneId:o.analytics?.sceneId||"",sceneName:n.name||""})),cl.length>0){const dd=rl.destroy;rl.destroy=()=>{for(const t of cl)try{t.destroy()}catch{}dd()}}return rl}async function un(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),hn(t,i,n)}function mn(t,e,n){return t+(e-t)*n}async function gn(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 fn extends Error{constructor(t){super(`Scene not found: ${t}`),this.name="SceneNotFoundError"}}class yn extends Error{constructor(t,e){super(t),this.name="SceneApiError",this.statusCode=e}}const vn=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 bn(t,e,n={}){const o=n.baseUrl||vn;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 fn(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 yn(n,a.status)}const r=await a.json();if(!r.success||!r.data)throw new yn("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=!0===h.disableAnalytics,m=!("analytics"in n)&&!u,g=r.meta.htmlUrl||r.meta.playcanvasHtmlUrl||null,f=hn(t,c,{...h,...m&&{analytics:{sceneId:e,ownerId:l,baseUrl:o}}});if(g&&f.on("error",e=>{if(e?.message?.includes("Failed to load splat from any URL")){console.log("[StorySplat] Falling back to legacy viewer:",g);try{f.destroy()}catch(t){}const e=document.createElement("iframe");e.src=g,e.style.cssText="width:100%;height:100%;border:none;position:absolute;top:0;left:0;",e.allow="fullscreen; autoplay",e.title=r.meta.name||"StorySplat Scene";const n=t.querySelector(".storysplat-outdated-banner");t.replaceChildren(),t.style.position="relative",n&&t.appendChild(n),t.appendChild(e)}}),m){gn(o,e,l,"view");let t=!1;f.on("loaded",n=>{t||(t=!0,n&&n.bandwidthUsed>0&&n.isStorySplatHosted?gn(o,e,l,"bandwidth",n.bandwidthUsed):console.log("[StorySplat] Bandwidth not tracked (self-hosted or no data)"))})}return f}async function xn(t,e={}){const n=`${e.baseUrl||vn}/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 fn(t);throw new yn(`API error: ${i.status}`,i.status)}const s=await i.json();return{...s,userName:s.userName||"Unknown",userSlug:s.userSlug||"unknown"}}class wn{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 Sn{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")||t.tags?.has("custom-mesh")){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 _n{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 Cn;!function(t){t[t.selected=1]="selected",t[t.hidden=2]="hidden",t[t.deleted=4]="deleted"}(Cn||(Cn={}));const Mn=512,En=1536,Pn=[0,4,8,12,1,5,9,13,2,6,10,14];class kn{constructor(e){this.device=e,this.count=1,this.version=0,this.texture=new t.Texture(e,{name:"splatTransformPalette",width:En,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/Mn);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%Mn*3,o=Math.floor(t/Mn)*En*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[Pn[t]]}}getTransform(e,n){const o=n??new t.Mat4,i=e%Mn*3,s=Math.floor(e/Mn)*En*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[Pn[t]]=this.data[s+4*(i+e)+n]}return o}upload(){this._upload()}destroy(){this.texture.destroy()}_writeIdentity(t){const e=t%Mn*3,n=Math.floor(t/Mn)*En*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:En,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(En*i*4),this.data.set(n),this.version++}}const Tn=[{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"}],An=[];for(let t=0;t<45;t++)An.push(`f_rest_${t}`);const Ln=[0,9,24,45];class Rn{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]&Cn.deleted||s++;const a=Tn.filter(t=>{try{return null!==i.getProp(t.storage)}catch{return!1}});let r=[];for(const t of An)try{null!==i.getProp(t)&&r.push(t)}catch{}if(void 0!==n&&n>=0&&n<=3){const t=Ln[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),b=a.findIndex(t=>"y"===t.name),x=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),C=a.findIndex(t=>"rot_3"===t.name),M=a.findIndex(t=>"scale_0"===t.name),E=a.findIndex(t=>"scale_1"===t.name),P=a.findIndex(t=>"scale_2"===t.name),k=new Map,T=n=>{if(0===n)return null;let o=k.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},k.set(n,o)}return o},A=new t.Vec3,L=new t.Quat;let R=0;for(let t=0;t<e.numSplats;t++){if(e.state[t]&Cn.deleted)continue;const n=T(e.transform[t]);if(n){const e=f[v]?.[t]??0,o=f[b]?.[t]??0,i=f[x]?.[t]??0;A.set(e,o,i),n.mat.transformPoint(A,A),w>=0&&(L.set(f[S]?.[t]??0,f[_]?.[t]??0,f[C]?.[t]??0,f[w]?.[t]??1),L.mul2(n.rot,L));for(let e=0;e<f.length;e++){let o=f[e]?.[t]??0;e===v?o=A.x:e===b?o=A.y:e===x?o=A.z:e===w?o=L.w:e===S?o=L.x:e===_?o=L.y:e===C?o=L.z:e===M?o+=Math.log(Math.abs(n.scale.x)):e===E?o+=Math.log(Math.abs(n.scale.y)):e===P&&(o+=Math.log(Math.abs(n.scale.z))),g[R++]=o}}else for(let e=0;e<f.length;e++)g[R++]=f[e]?.[t]??0;for(let e=0;e<y.length;e++)g[R++]=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.resource??o?.resource;return i?.gsplatData?i.gsplatData:null}}class zn{constructor(e,n){this._stats={total:0,selected:0,hidden:0,deleted:0,shBands:0},this._material=null,this._originalVS=null,this._originalPS=null,this._originalCenterVS=null,this._wasUnified=!1,this._savedAssetId=null,this._savedResource=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.entity=e,this._app=n,this.device=n.graphicsDevice,this.transformPalette=new kn(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.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),console.log("[SplatEdit] Attachment init:","instance:",!!i,"placement:",!!s,"resource:",!!a,"texDims:",r.x,"x",r.y,"numSplats:",this.numSplats,"stateArrayLen:",this.texWidth*this.texHeight,"resource.textureDimensions:",a?.textureDimensions,"instance.material:",!!i?.material),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_R8,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._updateStats(),console.log("[SplatEdit] updateState:",this._stats,"stateTexture format:",this.stateTexture.format,"stateTexture size:",this.stateTexture.width,"x",this.stateTexture.height)}updateTransform(){this._uploadTransform()}destroy(){this._removeShaders(),this.stateTexture.destroy(),this.transformTexture.destroy(),this.transformPalette.destroy()}_uploadState(){this.stateTexture.lock().set(this.state),this.stateTexture.unlock()}_uploadTransform(){this.transformTexture.lock().set(this.transform),this.transformTexture.unlock()}_updateStats(){let t=0,e=0,n=0;for(let o=0;o<this.numSplats;o++){const i=this.state[o];i&Cn.selected&&t++,i&Cn.hidden&&e++,i&Cn.deleted&&n++}this._stats.selected=t,this._stats.hidden=e,this._stats.deleted=n}_applyShaders(){let t=this.entity.gsplat;if(!t)return;(t.unified||t._unified)&&(console.log("[SplatEdit] Switching from unified to non-unified for edit mode"),this._wasUnified=!0,this._savedAssetId=t.asset??null,this._savedResource=t.resource??null,this.entity.removeComponent("gsplat"),this._savedResource?this.entity.addComponent("gsplat",{resource:this._savedResource}):this._savedAssetId&&this.entity.addComponent("gsplat",{asset:this._savedAssetId}),t=this.entity.gsplat);const e=t.instance;if(this._material=e?.material?e.material:t.material??null,console.log("[SplatEdit] _applyShaders:","instance:",!!e,"instance.material:",!!e?.material,"material:",!!this._material,"wasUnified:",this._wasUnified),!this._material)return void console.warn("SplatEditAttachment: could not find gsplat material");const n=this.device?.isWebGPU?"wgsl":"glsl",o=this._material.getShaderChunks(n);this._originalVS=o.get("gsplatVS")??null,this._originalPS=o.get("gsplatPS")??null,this._originalCenterVS=o.get("gsplatCenterVS")??null,o.set("gsplatVS",'\n#include "gsplatCommonVS"\n\nuniform sampler2D splatState;\n\nuniform vec4 selectedClr;\nuniform vec4 lockedClr;\n\nuniform vec3 clrOffset;\nuniform vec4 clrScale;\n\nvarying mediump vec4 texCoord_flags; // xy: texCoord, z: selected, w: locked\nvarying mediump vec4 color;\n\n#if PICK_PASS\n uniform uint pickOp; // 0: add, 1: remove, 2: set\n uniform int pickMode; // 0: pick id, 1: depth estimation\n#endif\n\nmediump vec4 discardVec = vec4(0.0, 0.0, 2.0, 1.0);\n\nuniform float saturation;\n\nvec3 applySaturation(vec3 color) {\n vec3 grey = vec3(dot(color, vec3(0.299, 0.587, 0.114)));\n return grey + (color - grey) * saturation;\n}\n\nvoid main(void) {\n // read gaussian details\n SplatSource source;\n if (!initSource(source)) {\n gl_Position = discardVec;\n return;\n }\n\n // get per-gaussian edit state, discard if deleted\n uint vertexState = uint(texelFetch(splatState, splat.uv, 0).r * 255.0 + 0.5) & 7u;\n\n #if PICK_PASS\n if (pickOp == 0u) {\n // add: skip deleted, locked and selected splats\n if (vertexState != 0u) {\n gl_Position = discardVec;\n return;\n }\n } else if (pickOp == 1u) {\n // remove: skip deleted, locked and unselected splats\n if (vertexState != 1u) {\n gl_Position = discardVec;\n return;\n }\n } else {\n // set: skip deleted and locked splats\n if ((vertexState & 6u) != 0u) {\n gl_Position = discardVec;\n return;\n }\n }\n #else\n // skip deleted splats\n if ((vertexState & 4u) != 0u) {\n gl_Position = discardVec;\n return;\n }\n #endif\n\n // get center\n vec3 modelCenter = getCenter();\n\n SplatCenter center;\n center.modelCenterOriginal = modelCenter;\n center.modelCenterModified = modelCenter;\n if (!initCenter(modelCenter, center)) {\n gl_Position = discardVec;\n return;\n }\n\n SplatCorner corner;\n if (!initCorner(source, center, corner)) {\n gl_Position = discardVec;\n return;\n }\n\n gl_Position = center.proj + vec4(corner.offset, 0.0);\n\n // store texture coord and locked state\n texCoord_flags = vec4(\n corner.uv,\n (vertexState & 1u) != 0u ? 1.0 : 0.0, // selected\n (vertexState & 2u) != 0u ? 1.0 : 0.0 // locked\n );\n\n #if PICK_PASS\n if (pickMode == 1) {\n // depth estimation mode\n float linearDepth = -center.view.z;\n float normalizedDepth = (linearDepth - camera_params.z) / (camera_params.y - camera_params.z);\n vec4 clr = getColor();\n color = vec4(normalizedDepth, 0.0, 0.0, 1.0) * clr.a;\n } else {\n // pick id — encode splat.index as RGBA\n uvec4 bits = (uvec4(splat.index) >> uvec4(0u, 8u, 16u, 24u)) & uvec4(255u);\n color = vec4(bits) / 255.0;\n }\n #elif FORWARD_PASS\n // read color\n color = getColor();\n\n // evaluate spherical harmonics\n #if SH_BANDS > 0\n vec3 dir = normalize(center.view * mat3(center.modelView));\n vec3 sh[SH_COEFFS];\n float scale;\n readSHData(sh, scale);\n color.xyz += evalSH(sh, dir) * scale;\n #endif\n\n // apply tint/brightness\n color = color * clrScale + vec4(clrOffset, 0.0);\n\n // apply saturation\n color.xyz = applySaturation(color.xyz);\n\n // don\'t allow out-of-range alpha\n color.a = clamp(color.a, 0.0, 1.0);\n\n // apply tonemapping\n color = vec4(prepareOutputFromGamma(max(color.xyz, 0.0), -center.view.z), color.w);\n\n // apply locked/selected colors\n if ((vertexState & 2u) != 0u) {\n color *= lockedClr;\n } else if ((vertexState & 1u) != 0u) {\n color.xyz = mix(color.xyz, selectedClr.xyz, selectedClr.a);\n }\n #endif\n}\n'),o.set("gsplatPS","\nvarying mediump vec4 texCoord_flags;\nvarying mediump vec4 color;\n\nuniform bool outlineMode;\nuniform float ringSize;\n\n#if PICK_PASS\n uniform int pickMode; // 0: id, 1: depth estimation\n#endif\n\nconst float EXP4 = exp(-4.0);\nconst float INV_EXP4 = 1.0 / (1.0 - EXP4);\n\nfloat normExp(float x) {\n return (exp(x * -4.0) - EXP4) * INV_EXP4;\n}\n\nvoid main(void) {\n mediump float A = dot(texCoord_flags.xy, texCoord_flags.xy);\n\n if (A > 1.0) {\n discard;\n }\n\n #if PICK_PASS\n if (pickMode == 1) {\n // depth estimation\n mediump float alpha = normExp(A);\n if (alpha < 1.0 / 255.0) {\n discard;\n }\n gl_FragColor = color * alpha;\n } else {\n // pick id\n gl_FragColor = color;\n }\n #else\n mediump float norm = normExp(A);\n mediump float alpha = norm * color.a;\n\n if (texCoord_flags.w == 0.0 && ringSize > 0.0) {\n // rings mode — show splat edges\n if (A < 1.0 - ringSize) {\n alpha = max(0.05, alpha);\n } else {\n alpha = 0.6;\n }\n }\n\n bool selected = texCoord_flags.z != 0.0 && texCoord_flags.w == 0.0;\n\n if (outlineMode) {\n pcFragColor0 = vec4(color.xyz * alpha, alpha);\n pcFragColor1 = vec4(0.0, 0.0, 0.0, selected ? norm : 0.0);\n } else {\n if (selected) {\n pcFragColor0 = vec4(color.xyz * alpha * 0.8, alpha);\n pcFragColor1 = vec4(color.xyz * alpha * 0.2, alpha);\n } else {\n pcFragColor0 = vec4(color.xyz * alpha, alpha);\n pcFragColor1 = vec4(0.0, 0.0, 0.0, 0.0);\n }\n }\n #endif\n}\n"),o.set("gsplatCenterVS","\nuniform highp usampler2D splatTransform; // per-splat index into transform palette\nuniform sampler2D transformPalette; // palette of transform matrices\n\nmat4 applyPaletteTransform(mat4 model) {\n uint transformIndex = texelFetch(splatTransform, splat.uv, 0).r;\n if (transformIndex == 0u) {\n return model;\n }\n\n // read transform matrix\n int u = int(transformIndex % 512u) * 3;\n int v = int(transformIndex / 512u);\n\n mat4 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 t[3] = vec4(0.0, 0.0, 0.0, 1.0);\n\n return model * transpose(t);\n}\n\nuniform mat4 matrix_model;\nuniform mat4 matrix_view;\n#ifndef GSPLAT_CENTER_NOPROJ\n uniform vec4 camera_params; // 1 / far, far, near, isOrtho\n uniform mat4 matrix_projection;\n#endif\n\n// project the model space gaussian center to view and clip space\nbool initCenter(vec3 modelCenter, inout SplatCenter center) {\n mat4 modelView = matrix_view * applyPaletteTransform(matrix_model);\n vec4 centerView = modelView * vec4(modelCenter, 1.0);\n\n #ifndef GSPLAT_CENTER_NOPROJ\n if (camera_params.w != 1.0 && centerView.z > 0.0) {\n return false;\n }\n\n vec4 centerProj = matrix_projection * centerView;\n\n #if WEBGPU\n centerProj.z = clamp(centerProj.z, 0, abs(centerProj.w));\n #else\n centerProj.z = clamp(centerProj.z, -abs(centerProj.w), abs(centerProj.w));\n #endif\n\n center.proj = centerProj;\n center.projMat00 = matrix_projection[0][0];\n #endif\n\n center.view = centerView.xyz / centerView.w;\n center.modelView = modelView;\n return true;\n}\n");const i=this._detectSHBands(),s=t.resource??e?.resource,a=s?.shBands??i;this._material.setDefine?.("SH_BANDS",`${Math.min(i,a)}`),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.setParameter("outlineMode",0),this._material.setParameter("ringSize",0),this._material.update()}updateMaterialParams(){if(!this._material)return;const t=this.tintClr.r,e=this.tintClr.g,n=this.tintClr.b,o=this.temperature>0?1+.2*this.temperature:1,i=this.temperature<0?1-.2*this.temperature:1,s=(this.whitePoint-this.blackPoint)*t*o,a=(this.whitePoint-this.blackPoint)*e,r=(this.whitePoint-this.blackPoint)*n*i;this._material.setParameter("clrOffset",[this.brightness+this.blackPoint,this.brightness+this.blackPoint,this.brightness+this.blackPoint]),this._material.setParameter("clrScale",[s,a,r,this.transparency]),this._material.setParameter("saturation",this.saturation),this._material.setParameter("transformPalette",this.transformPalette.texture)}_removeShaders(){if(!this._material)return;const t=this.device?.isWebGPU?"wgsl":"glsl",e=this._material.getShaderChunks(t);null!==this._originalVS?e.set("gsplatVS",this._originalVS):e.delete("gsplatVS"),null!==this._originalPS?e.set("gsplatPS",this._originalPS):e.delete("gsplatPS"),null!==this._originalCenterVS?e.set("gsplatCenterVS",this._originalCenterVS):e.delete("gsplatCenterVS"),this._material.update(),this._material=null,this._wasUnified&&(console.log("[SplatEdit] Restoring unified mode"),this.entity.removeComponent("gsplat"),this._savedResource?this.entity.addComponent("gsplat",{resource:this._savedResource,unified:!0}):this._savedAssetId&&this.entity.addComponent("gsplat",{asset:this._savedAssetId,unified:!0}),this._wasUnified=!1,this._savedAssetId=null,this._savedResource=null)}_detectSHBands(){const t=this.entity.gsplat;if(!t)return 0;const e=t.instance??t._instance,n=t._placement,o=e??n,i=t.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 Dn=(t,e)=>{for(const n in e)t.resolve(n).setValue(e[n])};class In{constructor(e){this.shader=null,this.texture=null,this.renderTarget=null,this.data=null,this.device=e,this.dummyTexture=new t.Texture(e,{width:1,height:1,format:s})}getResources(e,n){const{device:o}=this;this.shader||(this.shader=t.ShaderUtils.createShader(o,{uniqueName:"splatEditIntersectShader",attributes:{vertex_position:a},vertexGLSL:"\n attribute vec2 vertex_position;\n void main(void) {\n gl_Position = vec4(vertex_position, 0.0, 1.0);\n }\n",fragmentGLSL:"\n uniform highp usampler2D transformA; // splat center x, y, z\n uniform highp usampler2D splatTransform; // transform palette index\n uniform sampler2D transformPalette; // palette of transforms\n uniform uvec2 splat_params; // splat texture width, num splats\n\n uniform mat4 matrix_model;\n uniform mat4 matrix_viewProjection;\n\n uniform uvec2 output_params; // output width, height\n\n // 0: mask, 1: rect, 2: sphere, 3: box\n uniform int mode;\n\n // mask params\n uniform sampler2D mask; // mask in alpha channel\n uniform vec2 mask_params; // mask width, height\n\n // rect params\n uniform vec4 rect_params; // rect x1, y1, x2, y2 in NDC\n\n // sphere params\n uniform vec4 sphere_params; // sphere x, y, z, radius\n\n // box params\n uniform vec4 box_params; // box center x, y, z\n uniform vec4 aabb_params; // half-extents x, y, z\n\n void main(void) {\n // calculate output id\n uvec2 outputUV = uvec2(gl_FragCoord);\n uint outputId = (outputUV.x + outputUV.y * output_params.x) * 4u;\n\n vec4 clr = vec4(0.0);\n\n for (uint i = 0u; i < 4u; i++) {\n uint id = outputId + i;\n\n if (id >= splat_params.y) {\n continue;\n }\n\n // calculate splatUV\n ivec2 splatUV = ivec2(\n int(id % splat_params.x),\n int(id / splat_params.x)\n );\n\n // read splat center\n vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);\n\n // apply optional per-splat transform\n uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;\n if (transformIndex > 0u) {\n // read transform matrix\n int u = int(transformIndex % 512u) * 3;\n int v = int(transformIndex / 512u);\n\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 center = vec4(center, 1.0) * t;\n }\n\n // transform to clip space and discard if outside\n vec3 world = (matrix_model * vec4(center, 1.0)).xyz;\n vec4 clip = matrix_viewProjection * vec4(world, 1.0);\n vec3 ndc = clip.xyz / clip.w;\n\n // skip offscreen fragments\n if (!any(greaterThan(abs(ndc), vec3(1.0)))) {\n if (mode == 0) {\n // select by mask\n ivec2 maskUV = ivec2((ndc.xy * vec2(0.5, -0.5) + 0.5) * mask_params);\n clr[i] = texelFetch(mask, maskUV, 0).a < 1.0 ? 0.0 : 1.0;\n } else if (mode == 1) {\n // select by rect\n clr[i] = all(greaterThan(ndc.xy * vec2(1.0, -1.0), rect_params.xy)) && all(lessThan(ndc.xy * vec2(1.0, -1.0), rect_params.zw)) ? 1.0 : 0.0;\n } else if (mode == 2) {\n // select by sphere\n clr[i] = length(world - sphere_params.xyz) < sphere_params.w ? 1.0 : 0.0;\n } else if (mode == 3) {\n // select by box\n vec3 relativePosition = world - box_params.xyz;\n bool isInsideCube = true;\n if (relativePosition.x < -aabb_params.x || relativePosition.x > aabb_params.x) {\n isInsideCube = false;\n }\n if (relativePosition.y < -aabb_params.y || relativePosition.y > aabb_params.y) {\n isInsideCube = false;\n }\n if (relativePosition.z < -aabb_params.z || relativePosition.z > aabb_params.z) {\n isInsideCube = false;\n }\n clr[i] = isInsideCube ? 1.0 : 0.0;\n }\n }\n }\n\n gl_FragColor = clr;\n }\n"}));const i=Math.max(1,Math.floor(e/2)),l=Math.ceil(n/(4*i));return this.texture&&this.texture.width===i&&this.texture.height===l||(this.texture&&(this.texture.destroy(),this.renderTarget.destroy()),this.texture=new t.Texture(o,{name:"splatEditIntersectTexture",width:i,height:l,format:s,mipmaps:!1,addressU:r,addressV:r}),this.renderTarget=new t.RenderTarget({colorBuffer:this.texture,depth:!1}),this.data=new Uint8Array(i*l*4)),{shader:this.shader,texture:this.texture,renderTarget:this.renderTarget,data:this.data}}async run(e,n,o,i,s,a,r,d){const{device:p}=this,{scope:h}=p,u=this.getResources(n.width,e);if(Dn(h,{transformA:n,splatTransform:i,transformPalette:s,splat_params:[n.width,e],matrix_model:a.data,matrix_viewProjection:"mask"===d.mode||"rect"===d.mode?d.viewProjection.data:(new t.Mat4).data,output_params:[u.texture.width,u.texture.height]}),"mask"===d.mode?Dn(h,{mode:0,mask:d.maskTexture,mask_params:[d.maskTexture.width,d.maskTexture.height]}):Dn(h,{mask:this.dummyTexture,mask_params:[0,0]}),"rect"===d.mode?Dn(h,{mode:1,rect_params:[d.rect.minX,d.rect.minY,d.rect.maxX,d.rect.maxY]}):Dn(h,{rect_params:[0,0,0,0]}),"sphere"===d.mode?Dn(h,{mode:2,sphere_params:[d.center.x,d.center.y,d.center.z,d.radius]}):Dn(h,{sphere_params:[0,0,0,0]}),"box"===d.mode){const e=(new t.Vec3).add2(d.min,d.max).mulScalar(.5),n=(new t.Vec3).sub2(d.max,d.min).mulScalar(.5);Dn(h,{mode:3,box_params:[e.x,e.y,e.z,0],aabb_params:[n.x,n.y,n.z,0]})}else Dn(h,{box_params:[0,0,0,0],aabb_params:[0,0,0,0]});p.setBlendState(l.NOBLEND),c(p,u.renderTarget,u.shader);return await u.texture.read(0,0,u.texture.width,u.texture.height,{renderTarget:u.renderTarget,data:u.data,immediate:!1})}destroy(){this.shader?.destroy(),this.texture?.destroy(),this.renderTarget?.destroy(),this.dummyTexture?.destroy(),this.shader=null,this.texture=null,this.renderTarget=null}}class Fn{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,b,x]=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(b,x)};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 Bn{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 Vn{constructor(t){this._queue=Promise.resolve(),this._centersTexture=null,this.device=t,this._intersect=new In(t),this._calcBound=new Fn(t),this._calcPositions=new Bn(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(3*e.numSplats))})}destroy(){this._intersect.destroy(),this._calcBound.destroy(),this._calcPositions.destroy(),this._centersTexture?.destroy(),this._centersTexture=null}_enqueue(t){const e=this._queue.then(t);return this._queue=e.then(()=>{},()=>{}),e}_getTransformATexture(e){if(this._centersTexture)return this._centersTexture;const n=e.entity.gsplat;if(!n)return null;const o=n.instance;if(o?.resource){const t=o.resource;if("function"==typeof t.getTexture){const e=t.getTexture("transformA");if(e)return e}if(t.streams?.textures instanceof Map){const e=t.streams.textures.get("transformA");if(e)return e}}const i=n._placement;if(i?.resource){const t=i.resource;if("function"==typeof t.getTexture){const e=t.getTexture("transformA");if(e)return e}if(t.streams?.textures instanceof Map){const e=t.streams.textures.get("transformA");if(e)return e}}const s=n.resource;if(s){if("function"==typeof s.getTexture){const t=s.getTexture("transformA");if(t)return t}if(s.streams?.textures instanceof Map){const t=s.streams.textures.get("transformA");if(t)return t}}const a=o?.resource??i?.resource??s,r=a?.centers??null;if(r&&r.length>0){const n=e.texWidth,o=e.texHeight,i=new t.Texture(this.device,{name:"splatEditCenters",width:n,height:o,format:t.PIXELFORMAT_RGBA32U,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=new Float32Array(s.buffer,s.byteOffset,s.length),l=Math.min(e.numSplats,Math.floor(r.length/3));for(let t=0;t<l;t++)a[4*t+0]=r[3*t+0],a[4*t+1]=r[3*t+1],a[4*t+2]=r[3*t+2],a[4*t+3]=1;return i.unlock(),this._centersTexture=i,console.log("[SplatEdit] Built centers texture from resource.centers:",l,"splats,",n,"x",o,"texture"),i}return console.warn("[SplatEdit] _getTransformATexture: no centers data found.","resource type:",a?.constructor?.name,"streams:",a?.streams?.textures instanceof Map?[...a.streams.textures.keys()]:"none","centers:",!!r),null}}class $n{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 Un{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]&Cn.deleted||("set"===this.op?this.selectionResult[n]?t[n]|=Cn.selected:t[n]&=~Cn.selected:"add"===this.op?this.selectionResult[n]&&(t[n]|=Cn.selected):"remove"===this.op&&this.selectionResult[n]&&(t[n]&=~Cn.selected));this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class On{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]&Cn.deleted||(t[n]|=Cn.selected);this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class Nn{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]&=~Cn.selected;this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class Wn{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]&Cn.deleted||(t[n]^=Cn.selected);this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class Hn{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]&Cn.selected&&(t[n]=(t[n]|Cn.hidden)&~Cn.selected);this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class Gn{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]&=~Cn.hidden;this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class jn{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]&Cn.selected&&(t[n]=(t[n]|Cn.deleted)&~Cn.selected);this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class Xn{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]&=~Cn.deleted;this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class qn{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 Yn{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]&Cn.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]&Cn.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(){}}const Zn=4294967295;class Kn{constructor(t){this._picker=null,this._app=t}pickRect(t,e,n,o,i){const s=t.camera;if(!s)return new Set;const a=this._getOrCreatePicker(o,i);a.resize(o,i),a.prepare(s,this._app.scene);const r=this._readPickPixels(a,0,0,o,i);return r?this._decodePixels(r):new Set}pickPoint(t,e,n){const o=t.camera;if(!o)return null;const i=this._app.graphicsDevice.canvas,s=i.width,a=i.height,r=this._getOrCreatePicker(s,a);r.resize(s,a),r.prepare(o,this._app.scene);const l=this._readPickPixels(r,e,n,1,1);if(!l||l.length<4)return null;const c=l[0],d=l[1],p=l[2],h=(l[3]<<24|c<<16|d<<8|p)>>>0;return h!==Zn?h:null}pickByMask(t,e){const n=t.camera;if(!n)return new Set;const o=this._app.graphicsDevice.canvas,i=o.width,s=o.height,a=this._getOrCreatePicker(i,s);a.resize(i,s),a.prepare(n,this._app.scene);const r=this._readPickPixels(a,0,0,i,s);if(!r)return new Set;const l=e.width,c=e.height,d=e.lock(),p=new Uint8Array(d);e.unlock();const h=new Set;for(let t=0;t<s;t++)for(let e=0;e<i;e++){const n=4*(t*i+e),o=r[n],a=r[n+1],d=r[n+2],u=(r[n+3]<<24|o<<16|a<<8|d)>>>0;if(u===Zn)continue;const m=Math.floor(e/i*l);p[4*(Math.floor(t/s*c)*l+m)+3]>25&&h.add(u)}return h}indicesToMask(t,e){const n=new Uint8Array(e);for(const o of t)o<e&&(n[o]=1);return n}destroy(){this._picker?.destroy(),this._picker=null}_getOrCreatePicker(e,n){return this._picker||(this._picker=new t.Picker(this._app,e,n)),this._picker}_readPickPixels(t,e,n,o,i){const s=this._app.graphicsDevice,a=t.renderTarget;if(!a)return null;const r=a.height-(n+i),l=Math.max(0,Math.floor(e)),c=Math.max(0,Math.floor(r)),d=Math.min(Math.floor(o),a.width-l),p=Math.min(Math.floor(i),a.height-c);if(d<=0||p<=0)return null;const h=new Uint8Array(4*d*p),u=s;return u.setRenderTarget(a),u.updateBegin(),u.readPixels(l,c,d,p,h),u.updateEnd(),h}_decodePixels(t){const e=new Set,n=t.length;for(let o=0;o<n;o+=4){const n=t[o],i=t[o+1],s=t[o+2],a=(t[o+3]<<24|n<<16|i<<8|s)>>>0;a!==Zn&&e.add(a)}return e}}class Qn{constructor(t,e){this._attachment=null,this._active=!1,this._previewBaseState=null,this._app=t,this._camera=e,this._dataProcessor=new Vn(t.graphicsDevice),this._picker=new Kn(t),this._history=new $n,this._history.onChange=()=>{this.onUndoRedoChange?.(this._history.canUndo,this._history.canRedo),this._notifyStats()}}activate(t){this._active&&this.deactivate(),this._attachment=new zn(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 Un(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 Un(this._attachment,t,o))}selectSplatAtPoint(t,e,n){if(!this._attachment)return null;const o=this._picker.pickPoint(this._camera,e,n);if(null===o)return"set"===t&&this._history.execute(new Nn(this._attachment)),null;const i=new Uint8Array(this._attachment.numSplats);return i[o]=1,this._history.execute(new Un(this._attachment,t,i)),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 Un(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 Un(this._attachment,t,o))}selectAll(){this._attachment&&this._history.execute(new On(this._attachment))}selectNone(){this._attachment&&this._history.execute(new Nn(this._attachment))}selectInvert(){this._attachment&&this._history.execute(new Wn(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 qn(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 Hn(this._attachment))}unhideAll(){this._attachment&&this._history.execute(new Gn(this._attachment))}deleteSelection(){this._attachment&&this._history.execute(new jn(this._attachment))}resetDeleted(){this._attachment&&this._history.execute(new Xn(this._attachment))}transformSelection(t){this._attachment&&this._history.execute(new Yn(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 Rn.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(),this._picker.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]&Cn.deleted||("set"===t?e[i]?n[i]|=Cn.selected:n[i]&=~Cn.selected:"add"===t?e[i]&&(n[i]|=Cn.selected):"remove"===t&&e[i]&&(n[i]&=~Cn.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 Jn="Apr 27, 22:53:11";export{Ve as ARPlacement,Jn as BUILD_VERSION,tt as CameraControls,oe as CustomScriptSystem,b as DEFAULT_BUTTON_LABELS,_n as EditorCameraController,ue as EntityAnimationSystem,se as FrameSequencePlayer,wn as GizmoManager,vt as GsplatRelighting,Tt as REVEAL_PRESETS,yn as SceneApiError,fn as SceneNotFoundError,Sn as SelectionManager,zn as SplatEditAttachment,Qn as SplatEditSystem,Rn as SplatSerializer,Cn as SplatState,kn as TransformPalette,it as VoxelCollision,Ue as addVec3,$e as cloneVec3,hn as createViewer,bn as createViewerFromSceneId,un as createViewerFromUrl,v as exportPropsToViewerConfig,xn as fetchSceneMeta,g as generateHTML,f as generateHTMLFromUrl,w as generateViewerStyles,Qe as generateWaypointPathSamples,yt as getGsplatRelightingClass,He as getPathSegmentCount,Lt as getRevealPreset,qe as getSegmentHandles,Ge as getSegmentIndices,je as getVirtualBezierHandles,Xe as isBezierSegment,We as isLoopingMode,Be as isNativeWebXRAvailable,Fe as isXR8Available,Ie as loadXR8,Ye as sampleCatmullRom,Ze as sampleCubicBezier,Ke as sampleWaypointPathSegment,Ne as scaleVec3,re as setupAnalyticsTracking,ie as setupCustomScript,pe as setupGoogleAnalytics,Oe as subVec3,y as transformSceneToExportProps};
1
+ import*as t from"playcanvas";import{Script as e,Color as n,Vec2 as o,Vec3 as i,PIXELFORMAT_RGBA8 as s,SEMANTIC_POSITION as a,ADDRESS_CLAMP_TO_EDGE as r,BlendState as l,drawQuadWithShader as c}from"playcanvas";const d="2.9.50";function p(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}function h(t){return t.replace(/<\/script/gi,"<\\/script")}function u(t){const e=JSON.stringify(t);return void 0===e?"undefined":h(e).replace(/</g,"\\u003c").replace(/\u2028/g,"\\u2028").replace(/\u2029/g,"\\u2029")}const m=/^G-[A-Z0-9]{6,20}$/i;function g(t,e={}){const{cdnUrl:n=`https://unpkg.com/storysplat-viewer@${d}/dist/storysplat-viewer.umd.js`,title:o=t.name||"StorySplat Scene",description:i=`Interactive 3D scene: ${t.name||"StorySplat"}`,faviconUrl:s,customCSS:a="",lazyLoad:r,lazyLoadButtonText:l,analytics:c,disableAnalytics:g=!1,googleTagId:f}=e,y=r??t.uiOptions?.lazyLoad??!1,v=l??t.uiOptions?.lazyLoadButtonText,b=c&&!g?c:void 0,x=g?void 0:function(t){if("string"!=typeof t)return;const e=t.trim();return m.test(e)?e.toUpperCase():void 0}(f),w=s?`<link rel="icon" href="${p(s)}" />`:"",S=a?`<style>${a}</style>`:"",_=h(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="${p(i)}">\n <title>${p(o)}</title>\n ${w}\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 touch-action: none;\n -webkit-touch-callout: none;\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 ${S}\n</head>\n<body>\n <div id="app"></div>\n\n <script src="https://cdn.jsdelivr.net/npm/playcanvas@2.18.0/build/playcanvas.min.js"><\/script>\n <script src="${p(n)}"><\/script>\n <script>\n (function() {\n 'use strict';\n\n var sceneData = ${_};\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: ${y},\n lazyLoadButtonText: ${v?u(v):"undefined"}${b?`,\n analytics: ${u(b)}`:""}${g?",\n disableAnalytics: true":""}${x?`,\n googleTagId: ${u(x)}`:""}\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 f(t,e={}){const n=await fetch(t);if(!n.ok)throw new Error(`Failed to fetch scene: ${n.statusText}`);return g(await n.json(),e)}function y(t){const e=t.loadedModelUrl||t.splatUrl||"",n=t.sogModelUrl||t.sogUrl,o=t.compressedPlyUrl,i=t.lodMetaUrl,s=t.mobileLoadedModelUrl||t.mobileSplatUrl,a=t.mobileSogModelUrl||t.mobileSogUrl,r=t.mobileCompressedPlyUrl,l=t.mobileLodMetaUrl,c="string"==typeof t.activeSkyboxUrl&&t.activeSkyboxUrl?t.activeSkyboxUrl:"string"==typeof t.skyboxUrl&&t.skyboxUrl?t.skyboxUrl:void 0,d=t.skybox||(c?{url:c,rotation:t.skyboxRotation}:void 0),p=e.split("?")[0].split(".").pop()?.toLowerCase(),h="ply"===p||e.includes(".compressed.ply");let u=e;n?u=n:o?u=o:h||console.warn("[StorySplat Viewer] Original file format may not be compatible with PlayCanvas:",p);const m=(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,curveOut:t.curveOut}}),g=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:u,sogUrl:n,lodMetaUrl:i,mobileSplatUrl:s,mobileSogUrl:a,mobileLodMetaUrl:l,mobileCompressedPlyUrl:r,fallbackUrls:[...t.fallbackUrls||[],n!==u?n:null,o!==u?o:null,e!==u?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:m,hotspots:t.hotspots||[],portals:t.portals||[],skybox:d,skyboxUrl:d?.url,skyboxRotation:d?.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:g,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,watermarkImageUrl:t.uiOptions?.watermarkImageUrl,watermarkImageHeight:t.uiOptions?.watermarkImageHeight,buttonPosition:t.uiOptions?.buttonPosition||"inline",buttonLabels:t.uiOptions?.buttonLabels,customPreloaderLogoUrl:t.uiOptions?.customPreloaderLogoUrl,preloaderLogoSize:t.uiOptions?.preloaderLogoSize,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,sceneMenuLinks:t.uiOptions?.sceneMenuLinks,measurementsEnabled:t.uiOptions?.measurementsEnabled,sceneScale:t.uiOptions?.sceneScale,sceneScaleUnit:t.uiOptions?.sceneScaleUnit,measurementColor:t.uiOptions?.measurementColor,includeDefaultMeasurementsInViewer:t.uiOptions?.includeDefaultMeasurementsInViewer,defaultMeasurementsVisibleOnLoad:t.uiOptions?.defaultMeasurementsVisibleOnLoad,alwaysShowDefaultMeasurements:t.uiOptions?.alwaysShowDefaultMeasurements,hideShareButton:t.uiOptions?.hideShareButton,showFisheyeButton:t.uiOptions?.showFisheyeButton},measurements:t.measurements,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,eighthWallBaseUrl:t.eighthWallBaseUrl,customScript:t.customScript,templateType:t.uiOptions?.uiType||"minimal",additionalSplats:t.additionalSplats||[],keepMeshesInMemory:t.keepMeshesInMemory??!1,enabledExploreModes:t.enabledExploreModes,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":"medium"),revealStyle:t.revealStyle,swapTransitionType:t.swapTransitionType,lodSettings:t.lodSettings,orbitCameraSettings:t.orbitCameraSettings,walkCameraSettings:t.walkCameraSettings,walkSpawnOverride:t.walkSpawnOverride,refocusTapMode:t.refocusTapMode,enableSwapTransition:t.enableSwapTransition,splatSwapMode:t.splatSwapMode,compareSettings:t.compareSettings,entityAnimations:t.entityAnimations||[],postProcessing:t.postProcessing,mirrorPlanes:t.mirrorPlanes||[],weather:t.weather,segmentDataUrl:t.segmentDataUrl,segmentMetaUrl:t.segmentMetaUrl,enableSegmentHover:t.enableSegmentHover,narrationTrack:t.narrationTrack,skins:t.skins}}function v(t){const e=t.fov||t.waypoints?.[0]?.fov||60;return{splatUrl:t.splatUrl,sogUrl:t.sogUrl,lodMetaUrl:t.lodMetaUrl,mobileSplatUrl:t.mobileSplatUrl,mobileSogUrl:t.mobileSogUrl,mobileLodMetaUrl:t.mobileLodMetaUrl,mobileCompressedPlyUrl:t.mobileCompressedPlyUrl,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,enabledExploreModes:t.enabledExploreModes,initialSplatExploreMode:t.initialSplatExploreMode,includeXR:t.includeXR,xrMode:t.xrMode,eighthWallBaseUrl:t.eighthWallBaseUrl,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,orbitCameraSettings:t.orbitCameraSettings,walkCameraSettings:t.walkCameraSettings,walkSpawnOverride:t.walkSpawnOverride,refocusTapMode:t.refocusTapMode,enableSwapTransition:t.enableSwapTransition,splatSwapMode:t.splatSwapMode,compareSettings:t.compareSettings,entityAnimations:t.entityAnimations||[],postProcessing:t.postProcessing,mirrorPlanes:t.mirrorPlanes||[],weather:t.weather,segmentDataUrl:t.segmentDataUrl,segmentMetaUrl:t.segmentMetaUrl,enableSegmentHover:t.enableSegmentHover,narrationTrack:t.narrationTrack,skins:t.skins,measurements:t.measurements}}const b={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",share:"Share",capturingPhoto:"Capturing photo...",photoReady:"Photo ready",downloadPhoto:"Download Photo",photoDownloaded:"Photo downloaded",copyLink:"Copy Experience Link",linkCopied:"Link copied!",shareFailed:"Unable to share photo",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",exitPanorama:"Exit 360°",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 x(t,e){return t?.[e]||b[e]}function w(t="#CC5833",e="minimal",n){const o=n||{},i=o.globalTextColor||"white",s=t=>{if(!("string"!=typeof t||0===t.length||t.length>200||/[{};<>]|\/\*|\*\/|@import|expression\(|url\(|\\/i.test(t)||/[\r\n]/.test(t)))return t},a=s(o.popupTitleFontFamily),r=s(o.popupTitleLetterSpacing),l=s(o.popupContentFontFamily),c=s(o.popupContentLetterSpacing),d=s(o.infoBannerTitleFontFamily),p=s(o.infoBannerTitleLetterSpacing),h=s(o.infoBannerContentFontFamily),u=s(o.infoBannerContentLetterSpacing),m=s(o.shareModalFontFamily);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 font-weight: ${o.progressFontWeight||"normal"};\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 font-weight: ${o.buttonFontWeight||"normal"};\n border-radius: ${o.buttonBorderRadius||"4px"};\n cursor: pointer;\n transition: background 0.2s;\n text-align: center;\n backdrop-filter: blur(${o.dropdownBlur||"10px"});\n -webkit-backdrop-filter: blur(${o.dropdownBlur||"10px"});\n }\n\n .storysplat-btn:hover {\n background: ${o.buttonHoverBg||"rgba(0, 0, 0, 0.5)"};\n }\n\n .storysplat-btn-play {\n background: ${o.buttonBg||"rgba(0, 0, 0, 0.3)"};\n border: none;\n color: ${i};\n padding: 4px 8px;\n border-radius: 4px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n backdrop-filter: blur(${o.dropdownBlur||"10px"});\n -webkit-backdrop-filter: blur(${o.dropdownBlur||"10px"});\n }\n\n .storysplat-btn-play:hover {\n background: ${o.buttonHoverBg||"rgba(0, 0, 0, 0.5)"};\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: 4px;\n cursor: pointer;\n transition: background 0.2s;\n text-align: center;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\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 font-weight: ${o.modeBtnFontWeight||"normal"};\n border-radius: 4px;\n cursor: pointer;\n transition: all 0.2s ease;\n text-align: center;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\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 /* Share Button */\n .storysplat-share-btn {\n position: absolute;\n top: 10px;\n right: 10px;\n background: ${o.shareButtonBg||o.buttonBg||"rgba(0, 0, 0, 0.3)"};\n border: none;\n padding: 8px 10px;\n border-radius: ${o.shareButtonBorderRadius||o.buttonBorderRadius||"8px"};\n cursor: pointer;\n z-index: 1000;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background-color 0.3s ease;\n box-sizing: border-box;\n }\n .storysplat-share-btn svg,\n .storysplat-share-btn svg path {\n fill: ${o.shareButtonIconColor||o.buttonTextColor||i} !important;\n }\n .storysplat-share-btn:hover {\n background: ${o.shareButtonHoverBg||o.buttonHoverBg||"rgba(0, 0, 0, 0.5)"};\n }\n .storysplat-share-btn:disabled {\n cursor: wait;\n opacity: 0.65;\n }\n /* When in top-right-bar, override absolute positioning and match sibling height */\n .storysplat-top-right-bar .storysplat-share-btn {\n position: relative;\n top: auto;\n right: auto;\n align-self: stretch;\n }\n\n .storysplat-photo-share-backdrop {\n position: absolute;\n inset: 0;\n z-index: 100004;\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100%;\n padding: 10px;\n padding: max(10px, env(safe-area-inset-top)) max(10px, env(safe-area-inset-right)) max(10px, env(safe-area-inset-bottom)) max(10px, env(safe-area-inset-left));\n background: ${o.shareModalBackdropBg||"rgba(0, 0, 0, 0.45)"};\n backdrop-filter: blur(${o.shareModalBackdropBlur||"6px"});\n -webkit-backdrop-filter: blur(${o.shareModalBackdropBlur||"6px"});\n box-sizing: border-box;\n }\n\n .storysplat-photo-share-panel {\n width: min(460px, 100%);\n max-height: min(680px, calc(100% - 20px), calc(100vh - 20px));\n max-height: min(680px, calc(100% - 20px), calc(100dvh - 20px));\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n overscroll-behavior: contain;\n background: ${o.shareModalBg||"rgba(16, 16, 16, 0.94)"};\n border: 1px solid ${o.shareModalBorderColor||"rgba(255, 255, 255, 0.18)"};\n border-radius: ${o.shareModalBorderRadius||"8px"};\n color: ${o.shareModalTextColor||i};\n font-family: ${m||o.fontFamily||"system-ui, -apple-system, sans-serif"};\n box-shadow: 0 18px 60px rgba(0, 0, 0, 0.35);\n box-sizing: border-box;\n }\n\n .storysplat-photo-share-header {\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 16px 16px 12px;\n }\n\n .storysplat-photo-share-title {\n display: block;\n color: ${o.shareModalTitleColor||o.shareModalTextColor||i};\n font-size: ${o.shareModalTitleFontSize||"18px"};\n font-weight: ${o.shareModalTitleFontWeight||"750"};\n line-height: 1.15;\n white-space: nowrap;\n }\n\n .storysplat-photo-share-close {\n border: none;\n background: transparent;\n color: ${o.shareModalCloseColor||o.shareModalTextColor||i};\n cursor: pointer;\n font-size: 22px;\n line-height: 1;\n padding: 2px 4px;\n }\n\n .storysplat-photo-share-preview {\n flex: 0 1 auto;\n display: block;\n width: calc(100% - 32px);\n height: min(340px, 38vh);\n height: min(340px, 38dvh);\n min-height: 120px;\n margin: 0 16px 14px;\n object-fit: contain;\n border-radius: ${o.shareModalPreviewBorderRadius||"6px"};\n background: ${o.shareModalPreviewBg||"#111"};\n }\n\n .storysplat-photo-share-socials {\n flex: 0 0 auto;\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 8px;\n padding: 0 16px 12px;\n }\n\n .storysplat-photo-share-social {\n min-height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0 10px;\n border: 1px solid ${o.shareModalBorderColor||"rgba(255, 255, 255, 0.18)"};\n border-radius: ${o.shareModalButtonBorderRadius||"6px"};\n background: ${o.shareModalSocialButtonBg||"rgba(255, 255, 255, 0.06)"};\n color: ${o.shareModalSocialButtonTextColor||o.shareModalTextColor||i};\n cursor: pointer;\n font: inherit;\n font-size: ${o.shareModalSocialFontSize||"13px"};\n font-weight: ${o.shareModalSocialFontWeight||"650"};\n line-height: 1;\n text-align: center;\n text-decoration: none;\n white-space: nowrap;\n }\n .storysplat-photo-share-social:hover {\n background: ${o.shareModalSocialButtonHoverBg||o.shareModalActionButtonHoverBg||o.buttonHoverBg||"rgba(255, 255, 255, 0.12)"};\n }\n\n .storysplat-photo-share-actions {\n flex: 0 0 auto;\n display: grid;\n grid-template-columns: 1fr;\n gap: 8px;\n padding: 0 16px 16px;\n }\n\n .storysplat-photo-share-action {\n min-height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0 12px;\n border: 1px solid ${o.shareModalBorderColor||"rgba(255, 255, 255, 0.22)"};\n border-radius: ${o.shareModalButtonBorderRadius||"6px"};\n background: ${o.shareModalActionButtonBg||"rgba(255, 255, 255, 0.08)"};\n color: ${o.shareModalActionButtonTextColor||o.shareModalTextColor||i};\n cursor: pointer;\n font: inherit;\n font-size: ${o.shareModalActionFontSize||"14px"};\n font-weight: ${o.shareModalActionFontWeight||"650"};\n line-height: 1;\n text-align: center;\n white-space: nowrap;\n }\n .storysplat-photo-share-action:hover:not(:disabled) {\n background: ${o.shareModalActionButtonHoverBg||o.buttonHoverBg||"rgba(255, 255, 255, 0.14)"};\n }\n .storysplat-photo-share-action:disabled {\n cursor: wait;\n opacity: 0.65;\n }\n\n .storysplat-photo-share-action.primary {\n background: ${o.shareModalPrimaryButtonBg||t};\n border-color: ${o.shareModalPrimaryButtonBg||t};\n color: ${o.shareModalPrimaryButtonTextColor||o.shareModalActionButtonTextColor||o.shareModalTextColor||i};\n }\n\n .storysplat-photo-share-action.primary:hover:not(:disabled) {\n background: ${o.shareModalPrimaryButtonHoverBg||o.shareModalPrimaryButtonBg||t};\n border-color: ${o.shareModalPrimaryButtonHoverBg||o.shareModalPrimaryButtonBg||t};\n }\n\n @media (max-width: 520px), (max-height: 720px) {\n .storysplat-photo-share-backdrop {\n padding: 8px;\n padding: max(8px, env(safe-area-inset-top)) max(8px, env(safe-area-inset-right)) max(8px, env(safe-area-inset-bottom)) max(8px, env(safe-area-inset-left));\n }\n\n .storysplat-photo-share-panel {\n width: 100%;\n max-height: min(100%, calc(100vh - 16px));\n max-height: min(100%, calc(100dvh - 16px));\n }\n\n .storysplat-photo-share-header {\n padding: 12px 12px 8px;\n }\n\n .storysplat-photo-share-title {\n max-width: calc(100% - 34px);\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .storysplat-photo-share-preview {\n width: calc(100% - 24px);\n height: min(260px, 30vh);\n height: min(260px, 30dvh);\n min-height: 96px;\n margin: 0 12px 10px;\n }\n\n .storysplat-photo-share-socials {\n gap: 6px;\n padding: 0 12px 10px;\n }\n\n .storysplat-photo-share-actions {\n gap: 6px;\n padding: 0 12px 12px;\n }\n\n .storysplat-photo-share-social {\n min-height: 34px;\n padding: 0 8px;\n }\n\n .storysplat-photo-share-action {\n min-height: 34px;\n padding: 0 10px;\n }\n }\n\n @media (max-height: 600px) {\n .storysplat-photo-share-preview {\n height: min(180px, 24vh);\n height: min(180px, 24dvh);\n min-height: 72px;\n }\n }\n\n /* Mute Button - Minimal (inside toolbar) */\n .storysplat-mute-btn {\n position: relative;\n top: auto;\n left: auto;\n background: transparent;\n border: none;\n padding: 4px;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.2s ease;\n }\n\n .storysplat-mute-btn:hover {\n background: ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.08)"};\n }\n\n .storysplat-mute-btn svg {\n width: 20px;\n height: 20px;\n fill: ${i};\n }\n\n /* Relighting Toggle Button (inside toolbar) */\n .storysplat-relight-btn {\n position: relative;\n top: auto;\n left: auto;\n background: transparent;\n border: none;\n padding: 4px;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.2s ease, opacity 0.3s ease;\n }\n\n .storysplat-relight-btn:hover {\n background: ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.08)"};\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 /* Fisheye / Tiny Planet Toggle Button (inside toolbar) */\n .storysplat-fisheye-btn {\n position: relative;\n top: auto;\n left: auto;\n background: transparent;\n border: none;\n padding: 4px;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.2s ease, opacity 0.3s ease;\n color: ${i};\n }\n .storysplat-fisheye-btn:hover {\n background: ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.08)"};\n }\n .storysplat-fisheye-btn svg {\n width: 20px;\n height: 20px;\n transition: color 0.3s ease;\n }\n .storysplat-fisheye-btn.active {\n color: #4FC3F7;\n }\n\n /* Measurement Tape Measure Button (inside toolbar) */\n .storysplat-measure-btn {\n position: relative;\n top: auto;\n left: auto;\n background: transparent;\n border: none;\n padding: 4px;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.2s ease, opacity 0.3s ease;\n }\n\n .storysplat-measure-btn:hover {\n background: ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.08)"};\n }\n\n .storysplat-measure-btn.active {\n opacity: 1;\n }\n\n .storysplat-measure-btn.active svg {\n fill: #FF9800;\n }\n\n .storysplat-measure-btn svg {\n width: 20px;\n height: 20px;\n fill: ${i};\n }\n\n /* Measurement distance label overlay */\n .storysplat-measure-label {\n position: absolute;\n pointer-events: none;\n background: rgba(0, 0, 0, 0.75);\n color: white;\n font-size: 12px;\n padding: 3px 8px;\n border-radius: 4px;\n white-space: nowrap;\n transform: translate(-50%, -100%) translateY(-8px);\n z-index: 1002;\n font-family: ${o.fontFamily||'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'};\n }\n\n /* Measurement point marker */\n .storysplat-measure-point {\n position: absolute;\n width: 10px;\n height: 10px;\n background: var(--storysplat-measure-color, #FF9800);\n border: 2px solid white;\n border-radius: 50%;\n pointer-events: none;\n transform: translate(-50%, -50%);\n z-index: 1001;\n }\n\n /* Measurement line canvas overlay */\n .storysplat-measure-canvas {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n z-index: 1001;\n }\n\n /* Measurement list dropdown */\n .storysplat-measure-list {\n position: absolute;\n top: 52px;\n left: 10px;\n background: rgba(0, 0, 0, 0.8);\n backdrop-filter: blur(8px);\n border-radius: 6px;\n padding: 8px 0;\n min-width: 160px;\n max-height: 200px;\n overflow-y: auto;\n z-index: 1004;\n display: none;\n font-family: ${o.fontFamily||'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'};\n }\n .storysplat-measure-list-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 4px 10px 6px;\n border-bottom: 1px solid rgba(255,255,255,0.15);\n margin-bottom: 4px;\n }\n .storysplat-measure-list-title {\n font-size: 10px;\n font-weight: 600;\n color: rgba(255,255,255,0.6);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n .storysplat-measure-list-clear {\n background: none;\n border: none;\n color: rgba(255,255,255,0.5);\n font-size: 10px;\n cursor: pointer;\n padding: 2px 4px;\n }\n .storysplat-measure-list-clear:hover {\n color: #ff6b6b;\n }\n .storysplat-measure-list-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 5px 10px;\n color: white;\n font-size: 12px;\n }\n .storysplat-measure-list-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--storysplat-measure-color, #FF9800);\n flex-shrink: 0;\n }\n .storysplat-measure-list-empty {\n padding: 8px 10px;\n color: rgba(255,255,255,0.4);\n font-size: 11px;\n font-style: italic;\n }\n\n /* AI Re-skin dropdown (palette icon, inside top-left toolbar) */\n .storysplat-skins-btn {\n position: relative;\n top: auto;\n left: auto;\n background: transparent;\n border: none;\n padding: 4px;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.2s ease, opacity 0.3s ease;\n }\n .storysplat-skins-btn:hover {\n background: ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.08)"};\n }\n .storysplat-skins-btn.active svg {\n fill: ${t};\n }\n .storysplat-skins-btn svg {\n width: 20px;\n height: 20px;\n fill: ${i};\n }\n .storysplat-skins-dropdown {\n position: absolute;\n top: 52px;\n left: 10px;\n background: rgba(0, 0, 0, 0.85);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n border-radius: 8px;\n padding: 8px;\n width: 240px;\n max-height: 360px;\n overflow-y: auto;\n z-index: 1004;\n display: none;\n font-family: ${o.fontFamily||'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'};\n border: 1px solid rgba(255,255,255,0.1);\n }\n .storysplat-skins-dropdown.open { display: block; }\n .storysplat-skins-dropdown-header {\n font-size: 10px;\n font-weight: 600;\n color: rgba(255,255,255,0.6);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n padding: 2px 4px 8px;\n border-bottom: 1px solid rgba(255,255,255,0.12);\n margin-bottom: 8px;\n }\n .storysplat-skin-item {\n display: flex;\n gap: 8px;\n align-items: center;\n padding: 6px;\n border-radius: 6px;\n cursor: pointer;\n transition: background 0.15s ease;\n position: relative;\n overflow: hidden;\n }\n .storysplat-skin-item:hover { background: rgba(255,255,255,0.08); }\n .storysplat-skin-item.active { background: ${t}; }\n @keyframes storysplat-skin-shimmer {\n 0% { transform: translateX(-100%); }\n 100% { transform: translateX(100%); }\n }\n .storysplat-skin-item.loading::after {\n content: '';\n position: absolute;\n inset: 0;\n background: linear-gradient(\n 90deg,\n transparent 0%,\n rgba(255, 255, 255, 0.18) 50%,\n transparent 100%\n );\n animation: storysplat-skin-shimmer 1.2s ease-in-out infinite;\n pointer-events: none;\n }\n .storysplat-skin-item-thumb {\n width: 56px;\n height: 40px;\n border-radius: 4px;\n object-fit: cover;\n flex-shrink: 0;\n background: rgba(255,255,255,0.05);\n }\n .storysplat-skin-item-prompt {\n font-size: 11px;\n line-height: 1.3;\n color: rgba(255,255,255,0.9);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n }\n .storysplat-skins-empty {\n padding: 12px 6px;\n color: rgba(255,255,255,0.4);\n font-size: 11px;\n text-align: center;\n }\n .storysplat-skin-opacity-row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 4px 4px;\n margin-top: 8px;\n border-top: 1px solid rgba(255,255,255,0.12);\n }\n .storysplat-skin-opacity-row label {\n font-size: 10px;\n color: rgba(255,255,255,0.6);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n .storysplat-skin-opacity-row input[type=range] {\n flex: 1;\n accent-color: ${t};\n }\n .storysplat-skin-exit-btn {\n width: 100%;\n background: rgba(255,255,255,0.08);\n color: white;\n border: none;\n padding: 6px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 11px;\n margin-top: 6px;\n }\n .storysplat-skin-exit-btn:hover { background: rgba(255,255,255,0.15); }\n\n /* Skin overlay (above canvas, below toolbar/dropdowns).\n The 3D camera keeps its vertical FOV constant across viewer sizes, so\n we always match height: 100% and derive width from the captured\n canvas aspect ratio (set inline per activation). The wrapper owns\n the fade-in/out transition so dragging the opacity slider (which\n updates inline opacity on the inner img) stays instant. */\n .storysplat-skin-overlay-wrapper {\n position: absolute;\n top: 0;\n left: 50%;\n height: 100%;\n width: auto;\n transform: translateX(-50%);\n pointer-events: none;\n z-index: 999;\n opacity: 0;\n visibility: hidden;\n transition: opacity 350ms ease-out, visibility 0s linear 350ms;\n }\n .storysplat-skin-overlay-wrapper.active {\n opacity: 1;\n visibility: visible;\n transition: opacity 350ms ease-out, visibility 0s linear 0s;\n }\n .storysplat-skin-overlay {\n display: block;\n height: 100%;\n width: 100%;\n object-fit: fill;\n user-select: none;\n }\n\n .storysplat-ar-mode .storysplat-share-btn,\n .storysplat-ar-mode .storysplat-photo-share-backdrop,\n .storysplat-ar-mode .storysplat-skins-btn,\n .storysplat-ar-mode .storysplat-skins-dropdown,\n .storysplat-ar-mode .storysplat-skin-overlay-wrapper {\n display: none !important;\n }\n\n /* Top-left toolbar container (frosted glass) */\n .storysplat-toolbar {\n position: absolute;\n top: 10px;\n left: 10px;\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 4px;\n background: ${o.buttonBg||"rgba(0, 0, 0, 0.3)"};\n border-radius: 8px;\n border: 1px solid ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.08)"};\n backdrop-filter: blur(${o.dropdownBlur||"10px"});\n -webkit-backdrop-filter: blur(${o.dropdownBlur||"10px"});\n z-index: 1002;\n }\n\n /* Help Button - Minimal */\n .storysplat-help-btn {\n position: relative;\n top: auto;\n left: auto;\n width: 28px;\n height: 28px;\n border-radius: 6px;\n background: transparent;\n color: ${i};\n border: none;\n cursor: pointer;\n font-size: 15px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s ease;\n }\n\n .storysplat-help-btn:hover {\n background: ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.08)"};\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: 52px;\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-vr-btn {\n left: 10px;\n }\n\n .storysplat-ar-btn {\n left: 10px;\n }\n\n /* When both XR buttons are visible, sit AR to the right of VR */\n .storysplat-vr-btn.available ~ .storysplat-ar-btn.available {\n left: 70px;\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: ${o.infoBannerTitleFontWeight||"bold"};\n ${d?`font-family: ${d};`:""}\n ${p?`letter-spacing: ${p};`:""}\n ${o.infoBannerTitleColor?`color: ${o.infoBannerTitleColor};`:""}\n }\n\n .storysplat-waypoint-info p {\n margin: 0;\n font-size: ${o.infoBannerContentFontSize||"14px"};\n font-weight: ${o.infoBannerContentFontWeight||"normal"};\n line-height: ${o.infoBannerContentLineHeight||"1.5"};\n opacity: ${o.infoBannerContentOpacity??"0.9"};\n ${h?`font-family: ${h};`:""}\n ${u?`letter-spacing: ${u};`:""}\n ${o.infoBannerContentColor?`color: ${o.infoBannerContentColor};`:""}\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: ${o.popupTitleFontWeight||"600"};\n ${a?`font-family: ${a};`:""}\n ${r?`letter-spacing: ${r};`:""}\n padding-right: 30px;\n }\n\n .storysplat-hotspot-popup p {\n margin: 0 0 10px 0;\n line-height: ${o.popupContentLineHeight||"1.5"};\n font-size: ${o.popupContentFontSize||"14px"};\n font-weight: ${o.popupContentFontWeight||"normal"};\n ${l?`font-family: ${l};`:""}\n ${c?`letter-spacing: ${c};`:""}\n ${void 0!==o.popupContentOpacity?`opacity: ${o.popupContentOpacity};`:""}\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: ${o.portalPopupTitleFontSize||"16px"};\n font-weight: ${o.portalPopupTitleFontWeight||"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: ${o.portalPopupBtnFontSize||"13px"};\n font-weight: ${o.portalPopupBtnFontWeight||"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 /* Panorama portal loading spinner */\n .storysplat-panorama-loading {\n position: absolute;\n inset: 0;\n z-index: 999;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n opacity: 0;\n animation: storysplat-pano-loading-fadein 180ms ease-out 120ms forwards;\n transition: opacity 200ms ease-out;\n pointer-events: auto;\n }\n .storysplat-panorama-loading--hiding {\n opacity: 0;\n animation: none;\n }\n .storysplat-panorama-loading-spinner {\n width: 40px;\n height: 40px;\n border: 3px solid rgba(255, 255, 255, 0.2);\n border-top-color: white;\n border-radius: 50%;\n animation: storysplat-pano-spin 0.8s linear infinite;\n }\n @keyframes storysplat-pano-spin {\n to { transform: rotate(360deg); }\n }\n @keyframes storysplat-pano-loading-fadein {\n to { opacity: 1; }\n }\n\n /* Panorama portal exit button */\n .storysplat-panorama-exit-btn {\n position: absolute;\n top: 16px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 1000;\n padding: 10px 24px;\n background: rgba(0, 0, 0, 0.65);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n border: 1px solid rgba(255, 255, 255, 0.2);\n border-radius: 24px;\n color: white;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.2s ease, transform 0.2s ease;\n animation: storysplat-panorama-exit-fadein 0.5s ease 0.8s both;\n }\n .storysplat-panorama-exit-btn:hover {\n background: rgba(0, 0, 0, 0.85);\n transform: translateX(-50%) scale(1.05);\n }\n @keyframes storysplat-panorama-exit-fadein {\n from { opacity: 0; transform: translateX(-50%) translateY(-10px); }\n to { opacity: 1; transform: translateX(-50%) translateY(0); }\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 /* Tap for audio overlay */\n .storysplat-tap-for-audio {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: 50;\n color: white;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n font-size: 14px;\n font-weight: 500;\n letter-spacing: 0.3px;\n text-shadow: 0 1px 4px rgba(0, 0, 0, 0.6), 0 0 12px rgba(0, 0, 0, 0.3);\n pointer-events: none;\n opacity: 0;\n transition: opacity 0.4s ease;\n user-select: none;\n }\n .storysplat-tap-for-audio.visible {\n opacity: 1;\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: ${o.waypointListFontSize||"14px"};\n font-weight: ${o.waypointListFontWeight||"normal"};\n line-height: 1;\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: ${o.waypointListFontSize||"14px"};\n font-weight: ${o.waypointListFontWeight||"normal"};\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: ${o.watermarkBg||"rgba(0, 0, 0, 0.3)"};\n color: ${i};\n padding: 5px 10px;\n border-radius: 8px;\n border: 1px solid ${"white"===i?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.08)"};\n font-size: ${o.watermarkFontSize||"12px"};\n font-weight: ${o.watermarkFontWeight||"normal"};\n z-index: 1000;\n pointer-events: auto;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n cursor: default;\n backdrop-filter: blur(${o.dropdownBlur||"10px"});\n -webkit-backdrop-filter: blur(${o.dropdownBlur||"10px"});\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-watermark.storysplat-watermark-image {\n background: transparent;\n border: none;\n padding: 0;\n backdrop-filter: none;\n -webkit-backdrop-filter: none;\n }\n\n .storysplat-watermark-logo {\n display: block;\n max-height: var(--storysplat-watermark-img-size, 100px);\n max-width: var(--storysplat-watermark-img-size, 100px);\n object-fit: contain;\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.sceneMenuFontSize||"13px"};\n font-weight: ${o.sceneMenuFontWeight||"normal"};\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 /* Folder headers stay small + bold regardless of sceneMenuFontSize/Weight —\n their job is to visually separate sections, not match item text. */\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.sceneMenuFontSize||"13px"};\n font-weight: ${o.sceneMenuFontWeight||"normal"};\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 .storysplat-scene-menu-link {\n text-decoration: none;\n color: ${o.sceneMenuLinkColor||"white"};\n }\n\n .storysplat-scene-menu-link-icon {\n width: 16px;\n height: 16px;\n fill: currentColor;\n flex-shrink: 0;\n opacity: 0.7;\n }\n\n .storysplat-scene-menu-link-ext {\n width: 12px;\n height: 12px;\n fill: currentColor;\n flex-shrink: 0;\n opacity: 0.4;\n margin-left: auto;\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\n\n /* ===== 8th Wall AR Mode Overrides ===== */\n /* Applied when container has .storysplat-ar-mode class (camera feed visible behind scene) */\n .storysplat-ar-mode .storysplat-waypoint-info {\n background: rgba(0, 0, 0, 0.75);\n }\n\n .storysplat-ar-mode .storysplat-hotspot-popup {\n background: rgba(0, 0, 0, 0.85) !important;\n }\n\n .storysplat-ar-mode .storysplat-custom-menu-toggle,\n .storysplat-ar-mode .storysplat-scene-menu-toggle {\n background: rgba(0, 0, 0, 0.7);\n }\n\n .storysplat-ar-mode .storysplat-toolbar .storysplat-btn {\n display: none;\n }\n\n .storysplat-ar-mode .storysplat-toolbar .storysplat-xr-btn {\n display: block;\n }\n\n .storysplat-ar-mode .storysplat-help-btn {\n display: none !important;\n }\n\n .storysplat-ar-mode .storysplat-measure-canvas {\n display: none !important;\n }\n\n /* Waypoint navigation (prev/next/play/progress) and explore-mode toggles\n don't apply when the scene is anchored to the real world. */\n .storysplat-ar-mode .storysplat-scroll-controls,\n .storysplat-ar-mode .storysplat-explore-controls,\n .storysplat-ar-mode .storysplat-waypoint-list-container,\n .storysplat-ar-mode .storysplat-waypoint-info,\n .storysplat-ar-mode .storysplat-scene-menu-container,\n .storysplat-ar-mode .storysplat-top-right-bar,\n .storysplat-ar-mode .storysplat-fullscreen-btn,\n .storysplat-ar-mode .storysplat-mute-btn,\n .storysplat-ar-mode .storysplat-mode-container {\n display: none !important;\n }\n\n /* Keep the AR button itself visible so the user can exit AR. */\n .storysplat-ar-mode .storysplat-ar-btn.available {\n display: block !important;\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=S[n];if(!t)continue;const i=C(o,n);e+=` ${t} { ${_.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 S={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"},_=new Set(["progressText","modeToggle","exploreControls","prevButton","playButton","nextButton"]);function C(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 M(t="#CC5833",e="minimal",n,o){const i=o?`storysplat-viewer-styles-${o}`:`storysplat-viewer-styles-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,s=document.getElementById(i);s&&s.remove();const a=document.createElement("style");return a.id=i,a.textContent=w(t,e,n),document.head.appendChild(a),a}function E(t,e,n,o){const i=document.createElement("div");i.className="storysplat-preloader";const s=!!e,a=document.createElement("div");a.className="storysplat-preloader-content";const r=document.createElement("div");if(r.className="storysplat-preloader-media",s){const t="video"===j(e)?document.createElement("video"):document.createElement("img");t.className="storysplat-preloader-image",t.src=e,t instanceof HTMLVideoElement?(t.autoplay=!0,t.loop=!0,t.muted=!0,t.playsInline=!0):t.alt="Custom Logo","number"==typeof o&&o>0&&(t.style.height=`${o}px`),r.appendChild(t)}else{const t=document.createElement("span");t.className="storysplat-preloader-logo-text",t.textContent="StorySplat",r.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),r.appendChild(e)}a.appendChild(r);const l=document.createElement("div");l.className="storysplat-preloader-progress";const c=document.createElement("div");c.className="storysplat-preloader-text",c.textContent=`${x(n,"loading")} 0%`;const d=document.createElement("div");return d.className="storysplat-preloader-bar",l.appendChild(c),l.appendChild(d),a.appendChild(l),i.appendChild(a),t.appendChild(i),s||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)}(),i}function P(t){t.classList.add("hidden"),setTimeout(()=>t.remove(),500)}function k(t,e,n=2e3){const o=t.querySelector(".storysplat-share-toast");o&&o.remove();const i=document.createElement("div");i.className="storysplat-share-toast",i.textContent=e,i.style.cssText="position:absolute;top:50px;right:10px;background:rgba(0,0,0,0.82);color:white;padding:8px 16px;border-radius:8px;font-size:14px;z-index:100005;pointer-events:none;font-family:system-ui,-apple-system,sans-serif;",t.appendChild(i),setTimeout(()=>i.remove(),n)}function T(t,e,n={}){const{uiColor:o="#4CAF50",showScrollControls:i=!0,showModeToggle:s=!0,showFullscreenButton:a=!0,showHelpButton:r=!1,showMuteButton:l=!1,showPreloader:c=!0,allowedCameraModes:d=["tour","explore"],enabledExploreModes:p=["orbit","fly"],defaultCameraMode:h="tour",customPreloaderLogoUrl:u,preloaderLogoSize:m,buttonLabels:g,hideWatermark:f=!1,watermarkText:y,watermarkLink:v,watermarkImageUrl:b,watermarkImageHeight:w,sceneId:S,showWaypointList:_=!0,template:C="minimal",hideProgressText:P=!0,viewerTheme:k,showRelightingToggle:T=!1,showFisheyeButton:A=!1,showSceneMenu:L,sceneMenuLinks:R,measurementsEnabled:z=!1,measurementColor:F,showShareButton:B=!0}=n,V={tour:x(g,"tour"),explore:x(g,"explore"),walk:x(g,"walk"),orbit:x(g,"orbit"),fly:x(g,"fly"),previous:x(g,"previous"),next:x(g,"next"),fullscreen:x(g,"fullscreen"),waypoints:x(g,"waypoints"),close:x(g,"close"),yes:x(g,"yes"),cancel:x(g,"cancel"),vr:x(g,"vr"),ar:x(g,"ar"),loading:x(g,"loading"),helpTitle:x(g,"helpTitle"),helpCameraModes:x(g,"helpCameraModes"),helpTourDesc:x(g,"helpTourDesc"),helpExploreDesc:x(g,"helpExploreDesc"),helpWalkDesc:x(g,"helpWalkDesc"),helpTourControls:x(g,"helpTourControls"),helpTourScroll:x(g,"helpTourScroll"),helpTourDrag:x(g,"helpTourDrag"),helpExploreControls:x(g,"helpExploreControls"),helpExploreLMB:x(g,"helpExploreLMB"),helpExploreRMB:x(g,"helpExploreRMB"),helpExploreWASD:x(g,"helpExploreWASD"),helpExploreShift:x(g,"helpExploreShift"),helpExploreScroll:x(g,"helpExploreScroll"),helpExploreDblClick:x(g,"helpExploreDblClick"),helpWalkControls:x(g,"helpWalkControls"),helpWalkClick:x(g,"helpWalkClick"),helpWalkWASD:x(g,"helpWalkWASD"),helpWalkMouse:x(g,"helpWalkMouse"),helpWalkShift:x(g,"helpWalkShift"),helpWalkSpace:x(g,"helpWalkSpace"),hotspotDefaultTitle:x(g,"hotspotDefaultTitle"),openExternalLink:x(g,"openExternalLink"),mute:x(g,"mute"),unmute:x(g,"unmute"),share:x(g,"share"),scenes:x(g,"scenes")},$={};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, .storysplat-measure-btn, .storysplat-measure-canvas, .storysplat-measure-label, .storysplat-measure-point, .storysplat-toolbar, .storysplat-share-btn, .storysplat-photo-share-backdrop, .storysplat-share-toast").forEach(t=>t.remove()),M(o,C,k,t.id),t.classList.add("storysplat-viewer-container"),c&&($.preloader=E(t,u,g,m));const O=document.createElement("div");O.className="storysplat-waypoint-info",O.innerHTML='\n <h2 class="storysplat-waypoint-title"></h2>\n <p class="storysplat-waypoint-description"></p>\n ',t.appendChild(O),$.waypointInfo=O;const N=document.createElement("div");if(N.className="storysplat-tap-for-audio",N.textContent="Tap for audio",N.style.display="none",t.appendChild(N),$.tapForAudio=N,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">${V.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">${V.next}</button>\n </div>\n <div class="storysplat-explore-controls">\n ${d.includes("explore")&&p.includes("orbit")?`<button class="storysplat-explore-btn" data-explore-mode="orbit">${V.orbit}</button>`:""}\n ${d.includes("explore")&&p.includes("fly")?`<button class="storysplat-explore-btn" data-explore-mode="fly">${V.fly}</button>`:""}\n ${d.includes("walk")?`<button class="storysplat-explore-btn" data-explore-mode="walk">${V.walk}</button>`:""}\n </div>\n ${s&&d.includes("tour")&&(d.includes("explore")||d.includes("walk"))?`\n <div class="storysplat-mode-container">\n <div class="storysplat-mode-toggle">\n ${d.includes("tour")?`<button class="storysplat-mode-btn ${"tour"===h?"selected":""}" data-mode="tour">${V.tour}</button>`:""}\n ${d.includes("explore")&&d.includes("walk")?`<button class="storysplat-mode-btn ${"explore"===h||"walk"===h?"selected":""}" data-mode="explore">${V.explore}</button>`:""}\n ${d.includes("explore")&&!d.includes("walk")?`<button class="storysplat-mode-btn ${"explore"===h?"selected":""}" data-mode="explore">${V.explore}</button>`:""}\n ${!d.includes("explore")&&d.includes("walk")?`<button class="storysplat-mode-btn ${"walk"===h?"selected":""}" data-mode="walk">${V.walk}</button>`:""}\n </div>\n </div>\n `:""}\n </div>\n `,t.appendChild(e),$.scrollControls=e,$.progressBar=e.querySelector(".storysplat-progress-bar"),$.progressText=e.querySelector(".storysplat-progress-text"),$.exploreControls=e.querySelector(".storysplat-explore-controls"),$.modeContainer=e.querySelector(".storysplat-mode-container"),$.prevButton=e.querySelector(".storysplat-btn-prev"),$.playButton=e.querySelector(".storysplat-btn-play"),$.nextButton=e.querySelector(".storysplat-btn-next"),"pro"===C){const e=document.createElement("div");e.className="storysplat-top-right-bar "+(a?"with-fullscreen":""),t.appendChild(e),$.modeContainer&&e.appendChild($.modeContainer)}const n=k?.uiLayout,o=t=>n?.elements?.[t]||n?.mobile?.[t];o("progressText")&&$.progressText&&t.appendChild($.progressText),o("exploreControls")&&$.exploreControls&&t.appendChild($.exploreControls),o("prevButton")&&$.prevButton&&t.appendChild($.prevButton),o("playButton")&&$.playButton&&t.appendChild($.playButton),o("nextButton")&&$.nextButton&&t.appendChild($.nextButton),"pro"!==C&&o("modeToggle")&&$.modeContainer&&t.appendChild($.modeContainer),P&&$.progressText&&($.progressText.style.display="none")}if(a){const e=document.createElement("button");e.className="storysplat-fullscreen-btn",e.setAttribute("aria-label",V.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),$.fullscreenButton=e}const W=document.createElement("div");if(W.className="storysplat-toolbar",t.appendChild(W),$.toolbar=W,l){const t=document.createElement("button");t.className="storysplat-mute-btn",t.setAttribute("aria-label",V.mute);const e=document.createElementNS("http://www.w3.org/2000/svg","svg");e.classList.add("storysplat-unmuted-icon"),e.setAttribute("viewBox","0 0 24 24");const n=document.createElementNS("http://www.w3.org/2000/svg","path");n.setAttribute("d","M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"),e.appendChild(n),t.appendChild(e);const o=document.createElementNS("http://www.w3.org/2000/svg","svg");o.classList.add("storysplat-muted-icon"),o.setAttribute("viewBox","0 0 24 24"),o.style.display="none";const i=document.createElementNS("http://www.w3.org/2000/svg","path");i.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"),o.appendChild(i),t.appendChild(o),W.appendChild(t),$.muteButton=t}if(T){const t=document.createElement("button");t.className="storysplat-relight-btn active",t.setAttribute("aria-label","Toggle Relighting");const e=document.createElementNS("http://www.w3.org/2000/svg","svg");e.setAttribute("viewBox","0 0 24 24");const n=document.createElementNS("http://www.w3.org/2000/svg","path");n.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"),e.appendChild(n),t.appendChild(e),W.appendChild(t),$.relightingButton=t}if(A){const t=document.createElement("button");t.className="storysplat-fisheye-btn",t.setAttribute("aria-label","Toggle Tiny Planet"),t.setAttribute("title","Tiny Planet");const e=document.createElementNS("http://www.w3.org/2000/svg","svg");e.setAttribute("viewBox","0 0 24 24");const n=document.createElementNS("http://www.w3.org/2000/svg","circle");n.setAttribute("cx","12"),n.setAttribute("cy","12"),n.setAttribute("r","9"),n.setAttribute("fill","none"),n.setAttribute("stroke","currentColor"),n.setAttribute("stroke-width","2");const o=document.createElementNS("http://www.w3.org/2000/svg","circle");o.setAttribute("cx","12"),o.setAttribute("cy","12"),o.setAttribute("r","3.5"),o.setAttribute("fill","currentColor"),e.appendChild(n),e.appendChild(o),t.appendChild(e),W.appendChild(t),$.fisheyeButton=t}if(_&&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="${V.waypoints}">\n ${V.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),$.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(B){const e=document.createElement("button");e.className="storysplat-share-btn",e.setAttribute("aria-label",V.share),e.setAttribute("title",V.share);const n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("viewBox","0 0 24 24"),n.style.width="20px",n.style.height="20px",n.style.fill="white";const o=document.createElementNS("http://www.w3.org/2000/svg","path");o.setAttribute("d","M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z"),n.appendChild(o),e.appendChild(n);const i=t.querySelector(".storysplat-top-right-bar");if(i)i.appendChild(e);else{if($.waypointListContainer){const n=a?45:10;requestAnimationFrame(()=>{const o=$.waypointListContainer?.querySelector(".storysplat-waypoint-list-toggle"),i=o?o.offsetWidth:120;e.style.right=`${n+i+8}px`,o&&(e.style.top=o.getBoundingClientRect().top-t.getBoundingClientRect().top+"px",e.style.height=o.offsetHeight+"px")})}else a&&(e.style.right="45px");t.appendChild(e)}$.shareButton=e}const H=e.portals&&e.portals.length>0,G=R&&R.length>0;if((H||G)&&!1!==L){const n=document.createElement("div");n.className="storysplat-scene-menu-container";const o=V.scenes,i=document.createElement("button");i.className="storysplat-scene-menu-toggle",i.setAttribute("aria-label",o);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","M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"),s.appendChild(a),i.appendChild(s);const r=document.createElement("span");r.className="storysplat-scene-menu-label",r.textContent=o,i.appendChild(r);const l=document.createElement("div");l.className="storysplat-scene-menu-dropdown",function(t,e,n=[]){const o={children:new Map,items:[]};function i(t,e){const n=e?.trim();if(!n)return void o.items.push(t);const i=n.split("/").map(t=>t.trim()).filter(Boolean);let s=o;for(const t of i)s.children.has(t)||s.children.set(t,{name:t,children:new Map,items:[]}),s=s.children.get(t);s.items.push(t)}const s=new Set;for(const t of e)t.menuOnly&&t.targetSceneId&&s.add(t.targetSceneId);for(const t of e)!t.menuOnly&&t.targetSceneId&&s.has(t.targetSceneId)||i({type:"portal",data:t},t.menuPath);for(const t of n)i({type:"link",data:t},t.menuPath);function a(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 r(e){const n=e.contentType&&"link"!==e.contentType,o=e.mediaUrl||e.url,i=n?document.createElement("div"):document.createElement("a");i.className="storysplat-scene-menu-item storysplat-scene-menu-link",n||(i.setAttribute("href",e.url),i.setAttribute("target",!1===e.openInNewTab?"_self":"_blank"),i.setAttribute("rel","noopener noreferrer")),i.setAttribute("data-link-id",e.id),n&&(i.setAttribute("data-content-type",e.contentType),i.setAttribute("data-media-url",o),i.style.cursor="pointer");const s=document.createElementNS("http://www.w3.org/2000/svg","svg");s.setAttribute("viewBox","0 0 24 24"),s.classList.add("storysplat-scene-menu-link-icon");const a=document.createElementNS("http://www.w3.org/2000/svg","path");a.setAttribute("d",U[e.icon||"link"]),s.appendChild(a),i.appendChild(s);const r=document.createElement("span");if(r.className="storysplat-scene-menu-item-label",r.textContent=e.label,i.appendChild(r),!n){const t=document.createElementNS("http://www.w3.org/2000/svg","svg");t.setAttribute("viewBox","0 0 24 24"),t.classList.add("storysplat-scene-menu-link-ext");const e=document.createElementNS("http://www.w3.org/2000/svg","path");e.setAttribute("d","M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"),t.appendChild(e),i.appendChild(t)}return i.addEventListener("click",i=>{i.stopPropagation(),n&&(i.preventDefault(),l(e.label,e.contentType,o,t.closest(".storysplat-viewer-container")||document.body))}),i}function l(t,e,n,o){const i=o.querySelector(".storysplat-menu-media-popup");i&&i.remove();const s=document.createElement("div");s.className="storysplat-menu-media-popup storysplat-hotspot-popup fullscreen",s.style.cssText="position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 100001; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,0.85);";let a=null;const r=()=>{a&&a();const t=s.querySelector("video");t&&(t.pause(),t.src=""),s.remove(),document.removeEventListener("keydown",l)},l=t=>{"Escape"===t.key&&r()};document.addEventListener("keydown",l);const c=document.createElement("button");c.className="storysplat-hotspot-popup-close",c.textContent="×",c.addEventListener("click",r),s.appendChild(c);const d=document.createElement("div");if(d.className="storysplat-hotspot-popup-content",d.style.cssText="width: 90%; max-width: 900px; max-height: 85vh; display: flex; flex-direction: column; align-items: center;",t){const e=document.createElement("h3");e.className="storysplat-hotspot-popup-title",e.textContent=t,e.style.cssText="color: white; margin: 0 0 12px 0; font-size: 18px;",d.appendChild(e)}if("pdf"===e)if(D())a=I(n,d);else{const e=document.createElement("iframe");e.src=n,e.title=t||"PDF Document",e.style.cssText="width: 100%; height: 70vh; max-height: 600px; border: none; border-radius: 8px;",d.appendChild(e)}else if("iframe"===e){const e=document.createElement("iframe");e.src=n,e.title=t||"Content",e.style.cssText="width: 100%; height: 70vh; max-height: 600px; border: none; border-radius: 8px;",d.appendChild(e)}else if("image"===e){const e=document.createElement("img");e.src=n,e.alt=t||"Image",e.style.cssText="max-width: 100%; max-height: 70vh; object-fit: contain; border-radius: 8px;",d.appendChild(e)}else if("video"===e){const t=document.createElement("video");t.src=n,t.controls=!0,t.setAttribute("playsinline",""),t.setAttribute("preload","metadata"),t.style.cssText="max-width: 100%; max-height: 70vh; border-radius: 8px;",d.appendChild(t)}s.appendChild(d),s.addEventListener("click",t=>{t.target===s&&r()}),o.appendChild(s)}function c(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 i=document.createElementNS("http://www.w3.org/2000/svg","svg");i.setAttribute("viewBox","0 0 24 24");const s=document.createElementNS("http://www.w3.org/2000/svg","path");s.setAttribute("d","M7 10l5 5 5-5z"),i.appendChild(s),o.appendChild(i),o.appendChild(document.createTextNode(n.name)),e.appendChild(o);const a=document.createElement("div");a.className="storysplat-scene-menu-folder-children",c(a,n),e.appendChild(a),t.appendChild(e)}for(const n of e.items)"portal"===n.type?t.appendChild(a(n.data)):t.appendChild(r(n.data))}c(t,o)}(l,e.portals||[],R||[]),n.appendChild(i),n.appendChild(l),t.appendChild(n),$.sceneMenuContainer=n,i.addEventListener("click",t=>{t.stopPropagation(),i.classList.toggle("open"),l.classList.toggle("open")}),l.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)||(i.classList.remove("open"),l.classList.remove("open"))})}if(z){F&&t.style.setProperty("--storysplat-measure-color",F);const e=document.createElement("button");e.className="storysplat-measure-btn",e.setAttribute("aria-label","Measure");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","M21 6H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 10H3V8h2v4h2V8h2v4h2V8h2v4h2V8h2v4h2V8h2v8z"),n.appendChild(o),e.appendChild(n);const i=document.createElement("canvas");i.className="storysplat-measure-canvas";const s=document.createElement("div");s.className="storysplat-measure-list",W.appendChild(e),t.appendChild(i),t.appendChild(s),$.measureButton=e,$.measureCanvas=i,$.measureList=s}if(e.skins&&Array.isArray(e.skins)&&e.skins.length>0){const n=document.createElement("button");n.className="storysplat-skins-btn",n.setAttribute("aria-label","Virtual Staging");const o=document.createElementNS("http://www.w3.org/2000/svg","svg");o.setAttribute("viewBox","0 0 24 24");const i=document.createElementNS("http://www.w3.org/2000/svg","path");i.setAttribute("d","M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"),o.appendChild(i),n.appendChild(o),W.appendChild(n);const s=document.createElement("div");s.className="storysplat-skins-dropdown";const a=document.createElement("div");a.className="storysplat-skins-dropdown-header",a.textContent="Virtual Staging",s.appendChild(a);const r=document.createElement("div");r.className="storysplat-skins-list",s.appendChild(r),e.skins.forEach((t,e)=>{const n=document.createElement("div");n.className="storysplat-skin-item",n.setAttribute("data-skin-id",t.id),n.setAttribute("data-skin-index",String(e));const o=document.createElement("img");o.className="storysplat-skin-item-thumb",o.src=t.thumbnailUrl||t.imageUrl,o.alt="",o.draggable=!1;const i=document.createElement("div");i.className="storysplat-skin-item-prompt",i.textContent=t.name||t.prompt||`Staging ${e+1}`,n.appendChild(o),n.appendChild(i),r.appendChild(n)});const l=document.createElement("div");l.className="storysplat-skin-opacity-row";const c=document.createElement("label");c.textContent="Blend";const d=document.createElement("input");d.type="range",d.min="0",d.max="100",d.value="100",l.appendChild(c),l.appendChild(d),s.appendChild(l);const p=document.createElement("button");p.className="storysplat-skin-exit-btn",p.textContent="Exit overlay",p.style.display="none",s.appendChild(p),t.appendChild(s);const h=document.createElement("div");h.className="storysplat-skin-overlay-wrapper";const u=document.createElement("img");u.className="storysplat-skin-overlay",u.alt="",u.draggable=!1,u.addEventListener("error",()=>{console.error("[StorySplat Viewer] Skin overlay image failed to load:",u.src)}),h.appendChild(u),t.appendChild(h),$.skinsButton=n,$.skinsDropdown=s,$.skinOverlayWrapper=h,$.skinOverlay=u,$.skinOpacitySlider=d,$.skinExitButton=p}const j=document.createElement("button");j.className="storysplat-xr-btn storysplat-vr-btn",j.setAttribute("aria-label",V.vr),j.textContent=V.vr,t.appendChild(j),$.vrButton=j;const X=document.createElement("button");if(X.className="storysplat-xr-btn storysplat-ar-btn",X.setAttribute("aria-label",V.ar),X.textContent=V.ar,t.appendChild(X),$.arButton=X,r){const e=document.createElement("button");e.className="storysplat-help-btn",e.setAttribute("title",V.helpTitle),e.textContent="?",W.insertBefore(e,W.firstChild),$.helpButton=e;const n=document.createElement("div");n.className="storysplat-help-panel";const o=document.createElement("h3");o.textContent=V.helpTitle,n.appendChild(o);const i=document.createElement("div");i.className="storysplat-help-tabs";[{key:"tour",label:V.tour},{key:"explore",label:V.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",V.helpTourDesc,[["Scroll",V.helpTourScroll],["Drag",V.helpTourDrag]],!0));const r=[["LMB",V.helpExploreLMB],["RMB",V.helpExploreRMB],["WASD",V.helpExploreWASD],["Shift",V.helpExploreShift],["Scroll",V.helpExploreScroll],["Dbl-click",V.helpExploreDblClick]];d.includes("walk")&&r.push(["",`— ${V.walk} —`],["Click",V.helpWalkClick],["WASD",V.helpWalkWASD],["Mouse",V.helpWalkMouse],["Shift",V.helpWalkShift],["Space",V.helpWalkSpace]),n.appendChild(a("explore",V.helpExploreDesc,r,!1)),t.appendChild(n),$.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 q=document.createElement("div");q.className="storysplat-hotspot-popup",q.id="hotspotContent",q.innerHTML=`\n <h2 class="storysplat-hotspot-popup-title"></h2>\n <div class="storysplat-hotspot-popup-content"></div>\n <button class="storysplat-hotspot-popup-close">${V.close}</button>\n `,t.appendChild(q),$.hotspotPopup=q;const Y=q.querySelector(".storysplat-hotspot-popup-close");Y?.addEventListener("click",()=>{q.classList.remove("visible","fullscreen")});const Z=document.createElement("div");Z.className="storysplat-portal-popup",Z.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">${V.yes}</button>\n <button class="storysplat-portal-popup-btn storysplat-portal-popup-cancel">${V.cancel}</button>\n </div>\n `,t.appendChild(Z),$.portalPopup=Z;const K=document.createElement("div");K.className="storysplat-joystick-container",K.innerHTML='\n <div class="storysplat-joystick-base"></div>\n <div class="storysplat-joystick-thumb"></div>\n ',t.appendChild(K),$.joystick=K,$.joystickThumb=K.querySelector(".storysplat-joystick-thumb");const Q=document.createElement("div");Q.className="storysplat-look-zone",Q.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(Q),$.lookZone=Q;const J=document.createElement("div");J.className="storysplat-wasd-hint";const tt=document.createElement("div");tt.className="storysplat-orbit-hint-row",tt.style.marginBottom="6px";const et=document.createElement("div");et.className="storysplat-orbit-hint-icon";const nt=document.createElementNS("http://www.w3.org/2000/svg","svg");nt.setAttribute("viewBox","0 0 24 24");const ot=document.createElementNS("http://www.w3.org/2000/svg","path");ot.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"),nt.appendChild(ot),et.appendChild(nt),tt.appendChild(et);const it=document.createElement("div");it.className="storysplat-orbit-hint-label";const st=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),at=e.refocusTapMode||"single";it.textContent=st?"single"===at?"Tap to refocus":"Double-tap to refocus":"single"===at?"Click to refocus":"Double-click to refocus",tt.appendChild(it),J.appendChild(tt);const rt=document.createElement("div");rt.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}`),rt.appendChild(e)}),J.appendChild(rt);const lt=document.createElement("div");lt.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}`),lt.appendChild(e)}),J.appendChild(lt);const ct=document.createElement("div");ct.className="storysplat-wasd-hint-label",ct.textContent="Move",J.appendChild(ct),t.appendChild(J),$.wasdHint=J;const dt=document.createElement("div");dt.className="storysplat-orbit-hint";const pt=document.createElement("div");pt.className="storysplat-orbit-hint-row";const ht=document.createElement("div");ht.className="storysplat-orbit-hint-icon";const ut=document.createElementNS("http://www.w3.org/2000/svg","svg");ut.setAttribute("viewBox","0 0 24 24");const mt=document.createElementNS("http://www.w3.org/2000/svg","path");mt.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"),ut.appendChild(mt),ht.appendChild(ut),pt.appendChild(ht);const gt=document.createElement("div");gt.className="storysplat-orbit-hint-label",gt.textContent="Drag to rotate",pt.appendChild(gt),dt.appendChild(pt);const ft=document.createElement("div");ft.className="storysplat-orbit-hint-row";const yt=document.createElement("div");yt.className="storysplat-orbit-hint-icon";const vt=document.createElementNS("http://www.w3.org/2000/svg","svg");vt.setAttribute("viewBox","0 0 24 24");const bt=document.createElementNS("http://www.w3.org/2000/svg","path");bt.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"),vt.appendChild(bt),yt.appendChild(vt),ft.appendChild(yt);const xt=document.createElement("div");xt.className="storysplat-orbit-hint-label";const wt=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),St=e.refocusTapMode||"single";xt.textContent=wt?"single"===St?"Tap to refocus":"Double-tap to refocus":"single"===St?"Click to refocus":"Double-click to refocus",ft.appendChild(xt),dt.appendChild(ft),t.appendChild(dt),$.orbitHint=dt;const _t=document.createElement("div");_t.className="storysplat-doubletap-hint";const Ct=document.createElement("div");Ct.className="storysplat-doubletap-hint-icon";const Mt=document.createElementNS("http://www.w3.org/2000/svg","svg");Mt.setAttribute("viewBox","0 0 24 24");const Et=document.createElementNS("http://www.w3.org/2000/svg","path");Et.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"),Mt.appendChild(Et),Ct.appendChild(Mt),_t.appendChild(Ct);const Pt=document.createElement("div");Pt.className="storysplat-doubletap-hint-text";const kt=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),Tt=e.refocusTapMode||"single";if(Pt.textContent=kt?"single"===Tt?"Tap to focus on a point":"Double-tap to focus on a point":"single"===Tt?"Click to focus on a point":"Double-click to focus on a point",_t.appendChild(Pt),t.appendChild(_t),$.doubleTapHint=_t,!f){const e=document.createElement("div");e.className="storysplat-watermark";const n=v||(S?`https://storysplat.com?ref=${S}`:"https://storysplat.com"),o=t=>{const e=document.createElement("a");return e.href=t,e.target="_blank",e};if(b){e.classList.add("storysplat-watermark-image"),w&&e.style.setProperty("--storysplat-watermark-img-size",`${w}px`);const t=o(n),i=document.createElement("img");i.src=b,i.alt="Logo",i.className="storysplat-watermark-logo",t.appendChild(i),e.appendChild(t)}else if(y){const t=o(n);t.textContent=y,e.appendChild(t)}else{e.appendChild(document.createTextNode("Created with "));const t=o(n);t.textContent="StorySplat",e.appendChild(t)}t.appendChild(e),$.watermark=e}if(n.debugMode){const e=document.createElement("div");e.className="storysplat-fps-counter",e.textContent="-- FPS",t.appendChild(e),$.fpsCounter=e}{const t=$.sceneMenuContainer,e=10;if(t){t.style.left="10px",t.style.top=`${e}px`;const n=t.querySelector(".storysplat-scene-menu-toggle");if(n){const t=()=>{const t=10+n.offsetWidth+8;W.style.left=`${t}px`;const o=n.offsetHeight||36,i=W.offsetHeight||36;W.style.top=`${e+(o-i)/2}px`};t();let o=null;"undefined"!=typeof ResizeObserver&&(o=new ResizeObserver(t),o.observe(n),o.observe(W)),window.addEventListener("resize",t);const i=$._cleanup;$._cleanup=()=>{i&&i(),o&&o.disconnect(),window.removeEventListener("resize",t)}}}0===W.children.length&&(W.style.display="none")}return $}function A(t,e,n="tour",o,i){const s=i?.container,a=i?.hideProgressText,r=t.prevButton||t.scrollControls?.querySelector(".storysplat-btn-prev"),l=t.nextButton||t.scrollControls?.querySelector(".storysplat-btn-next"),c=t.playButton||t.scrollControls?.querySelector(".storysplat-btn-play");if(r&&r.addEventListener("click",()=>e.prevWaypoint()),l&&l.addEventListener("click",()=>e.nextWaypoint()),c){const t=t=>{c.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>'};c.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.shareButton){const n=t.shareButton,i=async()=>{const t=window.location.href;if(navigator.share)try{return void await navigator.share({url:t})}catch{}try{await navigator.clipboard.writeText(t),k(s||n.parentElement||document.body,x(o,"linkCopied"))}catch{window.prompt("Copy this link:",t)}};n.addEventListener("click",async()=>{if(n.disabled)return;n.disabled=!0;const t=n.getAttribute("aria-label")||x(o,"share");n.setAttribute("aria-label",x(o,"capturingPhoto")),n.setAttribute("title",x(o,"capturingPhoto"));try{e.shareCurrentView?await e.shareCurrentView():await i()}catch(t){console.warn("[StorySplat Viewer] Share failed:",t),k(s||n.parentElement||document.body,x(o,"shareFailed"))}finally{n.disabled=!1,n.setAttribute("aria-label",t),n.setAttribute("title",t)}})}if(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 d=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)},p=e=>{t.exploreControls&&(e?t.exploreControls.classList.add("visible"):t.exploreControls.classList.remove("visible"))},h=s||t.scrollControls?.parentElement;if(d("tour"===n),p("explore"===n||"walk"===n),e.setCameraMode){const t=h?.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"),d("tour"===o),p("explore"===o||"walk"===o))})});const n=!!h?.querySelector('.storysplat-mode-btn[data-mode="walk"]');e.on("modeChange",({mode:e})=>{d("tour"===e),p("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 u=0,m=-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,r;s-u<33||(u=s,t.progressBar&&(t.progressBar.style.width=`${n}%`),t.progressText&&i!==m&&(m=i,t.progressText.innerHTML="",t.progressText.textContent=(a=o,r=i,(a?.percentageFormat||b.percentageFormat).replace("{n}",String(r)))))});let g=-1;e.on("waypointChange",({index:n,waypoint:o})=>{if(n===g)return;if(g=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 L(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}const R="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.8.69";let z=null;function D(){return/iPad|iPhone|iPod/.test(navigator.userAgent)||"MacIntel"===navigator.platform&&navigator.maxTouchPoints>1}function I(t,e){let n=!1;const o=document.createElement("div");o.style.cssText="width: 100%; height: 70vh; max-height: 600px; overflow-y: auto; border-radius: 8px; background: #525659; display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 8px 0;";const i=document.createElement("div");return i.textContent="Loading PDF...",i.style.cssText="color: white; padding: 20px; font-size: 14px;",o.appendChild(i),e.appendChild(o),(z||(z=import(`${R}/pdf.min.mjs`).then(t=>(t.GlobalWorkerOptions.workerSrc=`${R}/pdf.worker.min.mjs`,t)).catch(t=>(z=null,Promise.reject(t)))),z).then(async e=>{try{const s=await e.getDocument(t).promise;if(n)return;i.remove();for(let t=1;t<=s.numPages;t++){if(n)return;const e=await s.getPage(t);if(n)return;const i=Math.min(o.clientWidth-16,850),a=i/e.getViewport({scale:1}).width,r=e.getViewport({scale:a}),l=document.createElement("canvas");l.width=r.width,l.height=r.height,l.style.cssText=`width: ${r.width}px; max-width: 100%; height: auto; display: block;`,o.appendChild(l);const c=l.getContext("2d");c&&await e.render({canvasContext:c,viewport:r}).promise}}catch(e){if(n)return;i.textContent="Failed to load PDF.",console.error("[StorySplat] PDF.js render error:",e);const s=document.createElement("a");s.href=t,s.target="_blank",s.rel="noopener noreferrer",s.textContent="Open PDF in new tab",s.style.cssText="color: #4CAF50; margin-top: 8px; font-size: 14px;",o.appendChild(s)}}).catch(()=>{if(n)return;i.textContent="PDF viewer unavailable.";const e=document.createElement("a");e.href=t,e.target="_blank",e.rel="noopener noreferrer",e.textContent="Open PDF in new tab",e.style.cssText="color: #4CAF50; margin-top: 8px; font-size: 14px;",o.appendChild(e)}),()=>{n=!0}}function F(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 l=n.activationMode||"click",c=n.photoUrl||n.popupVideoUrl||"iframe"===n.contentType&&n.iframeUrl||"model"===n.contentType&&n.modelUrl||"pdf"===n.contentType&&n.pdfUrl;"click"===l&&c&&i.classList.add("fullscreen");const d=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}, ${d})`}else i.style.backgroundColor=n.backgroundColor}else i.style.backgroundColor=`rgba(0, 0, 0, ${d})`;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||x(o,"hotspotDefaultTitle"));let p="";if("iframe"===n.contentType&&n.iframeUrl&&(p+=`<iframe src="${n.iframeUrl}" title="${n.title||"Embedded content"}"></iframe>`),!n.popupVideoUrl||n.contentType&&"video"!==n.contentType||(p+=`<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||(p+=`<img src="${n.photoUrl}" alt="${n.title||"Hotspot image"}" />`),"model"===n.contentType&&n.modelUrl&&(p+='<div class="model-container"><div class="model-loading">Loading 3D model...</div><canvas class="model-canvas"></canvas></div>'),"pdf"===n.contentType&&n.pdfUrl&&(D()?p+='<div class="pdf-pdfjs-container"></div>':p+=`<iframe src="${L(n.pdfUrl)}" style="width: 100%; height: 70vh; max-height: 600px; border: none; border-radius: 8px;" title="${L(n.title||"PDF Document")}"></iframe>`),n.information&&(p+=`<p>${L(n.information)}</p>`),n.externalLinkUrl){const t=n.externalLinkButtonColor||"#007bff";p+=`\n <div onclick="window.open('${L(n.externalLinkUrl)}', '_blank', 'noopener,noreferrer')"\n class="storysplat-hotspot-popup-link" style="background-color: ${t}">\n ${L(n.externalLinkText||x(o,"openExternalLink"))}\n </div>\n `}if(a){if(a.innerHTML=p,"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,b=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),b=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,b*(v/o)),y())}t.preventDefault()},{passive:!1}),e.addEventListener("touchend",()=>{m=!1,v=0});const x=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))}});x.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=()=>{x.disconnect(),a.destroy()}}(e,n.modelUrl,i)}const e=a.querySelector(".pdf-pdfjs-container");if(e&&n.pdfUrl){const t=I(n.pdfUrl,e);i.__pdfCleanup=t}}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 B(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 V(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 $(t,e){t.lookZone&&(e?t.lookZone.classList.add("active"):t.lookZone.classList.remove("active"))}const U={link:"M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z",map:"M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z",calendar:"M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM9 10H7v2h2v-2zm4 0h-2v2h2v-2zm4 0h-2v2h2v-2z",phone:"M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z",cart:"M7 18c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2zM1 2v2h2l3.6 7.59-1.35 2.45c-.16.28-.25.61-.25.96 0 1.1.9 2 2 2h12v-2H7.42c-.14 0-.25-.11-.25-.25l.03-.12.9-1.63h7.45c.75 0 1.41-.41 1.75-1.03l3.58-6.49c.08-.14.12-.31.12-.48 0-.55-.45-1-1-1H5.21l-.94-2H1zm16 16c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2z",globe:"M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.91 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56-1.84-.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95c-.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z",download:"M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"};function O(t,e){t.wasdHint&&(e?t.wasdHint.classList.add("visible"):t.wasdHint.classList.remove("visible"))}function N(t,e){t.orbitHint&&(e?t.orbitHint.classList.add("visible"):t.orbitHint.classList.remove("visible"))}function W(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 H(t,e){t.doubleTapHint&&(t.doubleTapHint.classList.add("fading"),setTimeout(()=>{t.doubleTapHint?.classList.remove("visible","fading")},400))}function G(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")})}function j(t){const e=t.toLowerCase();return e.includes(".mp4")||e.includes(".webm")||e.includes(".mov")||e.includes(".ogg")?"video":e.includes(".gif")?"gif":"image"}const X=new t.Vec3,q=new t.Vec3,Y=new t.Quat,Z=new t.Pose,K=new t.InputFrame({move:[0,0,0],rotate:[0,0,0]}),Q=(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},J=(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=q.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 tt{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._orbitRotationLimits=null,this._orbitRotationLimitAnchor=null,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._prevPose=new t.Pose,this._prevPoseValid=!1,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),o.orbitRotationLimits&&this.setOrbitRotationLimits(o.orbitRotationLimits,!0)}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(X)}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,e={}){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 n=this._mode,o="orbit"===t&&!!e.orbitFocusPoint;if(n===t&&!o)return;switch(this._mode=t,this._flyToActive=!1,this._controller&&this._controller.detach(),this._mode){case"orbit":if(this._controller=this._orbitController,e.orbitFocusPoint){const t=this.camera.getPosition(),n="number"==typeof e.orbitDistance&&Number.isFinite(e.orbitDistance)&&e.orbitDistance>0?e.orbitDistance:t.distance(e.orbitFocusPoint);this._lastFocusPoint.copy(e.orbitFocusPoint),this._pose.look(t,e.orbitFocusPoint),this._startZoomDist=n,this._pose.distance=n}else if("fly"===n)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":this._controller=this._flyController,this.syncFromCamera(void 0,!0);break;case"focus":this._controller=this._focusController}const i=!0===e.snap;if(this._controller.attach(this._pose,i),"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)}!i&&"orbit"===this._mode&&this._applyOrbitRotationLimits()&&(this._controller.attach(this._pose,!1),this._applyPoseToCamera()),this.app.fire("cameracontrols:modechange",this._mode)}setMode(t){this._setMode(t)}setOrbitModeFromFocus(t,e,n=!1){this._setMode("orbit",{orbitFocusPoint:t,orbitDistance:e,snap:n})}getOrbitState(){return{focus:this._pose.getFocus(new t.Vec3),distance:this._pose.distance}}setOrbitRotationLimits(t,e=!1){const n=this._normalizeOrbitRotationLimits(t);this._orbitRotationLimits=n,n?(!e&&this._orbitRotationLimitAnchor||this._captureOrbitRotationLimitAnchor(),"orbit"===this._mode&&this._applyOrbitRotationLimits()&&(this._controller.attach(this._pose,!1),this._applyPoseToCamera())):this._orbitRotationLimitAnchor=null}setOrbitRotationLimitAnchor(e,n){const o=X.sub2(n,e);if(o.lengthSq()<1e-8)return;o.normalize();const i=Math.atan2(-o.y,Math.sqrt(o.x*o.x+o.z*o.z))*t.math.RAD_TO_DEG;let s=Math.atan2(-o.x,-o.z)*t.math.RAD_TO_DEG;for(;s>180;)s-=360;for(;s<-180;)s+=360;this._orbitRotationLimitAnchor={pitch:Math.max(-89,Math.min(89,-i)),yaw:s,focus:n.clone()}}_normalizeOrbitRotationLimits(e){if(!e?.enabled)return null;const n=(e,n)=>{const o="number"==typeof e&&Number.isFinite(e)?e:45;return t.math.clamp(o,0,n)};return{enabled:!0,up:n(e.up,180),down:n(e.down,180),left:n(e.left,180),right:n(e.right,180)}}_captureOrbitRotationLimitAnchor(){const t=this._pose.getFocus(X);this._lastFocusPoint.copy(t),this._orbitRotationLimitAnchor={pitch:this._pose.angles.x,yaw:this._pose.angles.y,focus:t.clone()}}_rebuildOrbitPoseFromFocus(t){const e=q.set(0,0,-1);Y.setFromEulerAngles(this._pose.angles).transformVector(e,e),this._pose.position.copy(t).sub(e.mulScalar(this._pose.distance))}_applyPoseToCamera(){this.camera.setPosition(this._pose.position),this.camera.setEulerAngles(this._pose.angles)}_shortestAngleDeltaDegrees(t,e){let n=t-e;for(;n>180;)n-=360;for(;n<-180;)n+=360;return n}_applyOrbitRotationLimits(){const e=this._orbitRotationLimits;if("orbit"!==this._mode||!e)return!1;this._orbitRotationLimitAnchor||this._captureOrbitRotationLimitAnchor();const n=this._orbitRotationLimitAnchor;if(!n)return!1;let o=!1;const i=n.pitch-e.down,s=n.pitch+e.up,a=t.math.clamp(this._pose.angles.x,i,s);Math.abs(a-this._pose.angles.x)>1e-4&&(this._pose.angles.x=a,o=!0);const r=this._shortestAngleDeltaDegrees(this._pose.angles.y,n.yaw),l=t.math.clamp(r,-e.left,e.right);return Math.abs(l-r)>1e-4&&(this._pose.angles.y=n.yaw+l,this._lastYaw=this._pose.angles.y,o=!0),o&&(this._rebuildOrbitPoseFromFocus(n.focus),this._lastFocusPoint.copy(n.focus)),o}_isOrbitPivotLocked(){return"orbit"===this._mode&&null!==this._orbitRotationLimits}_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){if(this._isOrbitPivotLocked())return;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);if((this._collisionEntities.length>0||null!==this._voxelCollision)&&this.checkCollision(this._flyToEndPos)){const e=X;let o=0,s=i;for(let i=0;i<8;i++){const i=.5*(o+s);e.lerp(n,t,i),this.checkCollision(e)?s=i:o=i}this._flyToEndPos.lerp(n,t,o)}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?X.copy(this.camera.getPosition()).sub(t).normalize().mulScalar(this._startZoomDist).add(t):this.camera.getPosition();this._controller.attach(Z.look(n,t))}reset(t,e){"focus"!==this._mode&&(this._preFocusMode=this._mode),this._setMode("focus"),this._controller.attach(Z.look(e,t))}syncFromCamera(t,e=!1,n=!1){if(this._isOrbitPivotLocked()&&!e)return;const o=this.camera.getPosition().clone();if(this._prevPositionValid=!1,t){this._lastFocusPoint.copy(t);const e=o.distance(t);this._startZoomDist=e,this._pose.distance=e,this._pose.look(o,t);let n=this._pose.angles.y;for(;n>180;)n-=360;for(;n<-180;)n+=360;this._pose.angles.y=n,this._lastYaw=n,this._pose.angles.x=Math.max(-89,Math.min(89,this._pose.angles.x)),this._pose.angles.z=0}else{const t=this.camera.getEulerAngles();this._pose.position.copy(o),this._pose.angles.x=t.x,this._pose.angles.y=t.y,this._pose.angles.z=0;let e=this._pose.angles.y;for(;e>180;)e-=360;for(;e<-180;)e+=360;this._pose.angles.y=e,this._lastYaw=e,this._pose.angles.x=Math.max(-89,Math.min(89,this._pose.angles.x));const n=this.camera.forward.clone();this._lastFocusPoint.copy(o).add(n.mulScalar(10));const i=10;this._startZoomDist=i,this._pose.distance=i}this._controller.attach(this._pose,n)}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,n=!1){this._isOrbitPivotLocked()&&!n||(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._orbitMobileInput.detach(),this._flyMobileInput.attach(t)):(this._flyMobileInput.detach(),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();Q(h,this.gamepadDeadZone.x,this.gamepadDeadZone.y),Q(u,this.gamepadDeadZone.x,this.gamepadDeadZone.y),this._state.axis.add(X.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=m*+(this.enablePan&&!this._isOrbitPivotLocked()),b=+this._flyMobileInput.layout.endsWith("joystick"),x=(this._state.shift?this.moveFastSpeed:this._state.ctrl?this.moveSlowSpeed:this.moveSpeed)*e,w=60*this.zoomSpeed*e,S=w*this.zoomPinchSens,_=60*this.rotateSpeed*e,C=_*this.rotateTouchSens,M=this.rotateSpeed*this.rotateJoystickSens*60*e,{deltas:E}=K,P=X.set(0,0,0),k=this._state.axis.clone().normalize();P.add(k.mulScalar(g*x*this.keyboardSpeedMultiplier));const T=J(this.cameraComponent,s[0],s[1],this._pose.distance);P.add(T.mulScalar(v*y));const A=q.set(0,0,a[0]);P.add(A.mulScalar(m*w)),E.move.append([P.x,P.y,P.z]),P.set(0,0,0);const L=q.set(s[0],s[1],0);P.add(L.mulScalar((1-m*y)*_)),this.invertRotation&&(P.y=-P.y),E.rotate.append([P.x,P.y,P.z]),P.set(0,0,0);const R=q.set(d[0],0,-d[1]);P.add(R.mulScalar(g*x));const z=J(this.cameraComponent,r[0],r[1],this._pose.distance);P.add(z.mulScalar(v*f));const D=q.set(0,0,l[0]);P.add(D.mulScalar(m*f*S)),E.move.append([P.x,P.y,P.z]),P.set(0,0,0);const I=q.set(r[0],r[1],0);P.add(I.mulScalar(m*(1-f)*C));const F=q.set(p[0],p[1],0);P.add(F.mulScalar(g*(b?M:C))),this.invertRotation&&(P.y=-P.y),E.rotate.append([P.x,P.y,P.z]),P.set(0,0,0);const B=q.set(h[0],0,-h[1]);if(P.add(B.mulScalar(g*x)),E.move.append([P.x,P.y,P.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])}P.set(0,0,0);const V=q.set(u[0],u[1],0);if(P.add(V.mulScalar(g*M)),this.invertRotation&&(P.y=-P.y),E.rotate.append([P.x,P.y,P.z]),this.app.xr?.active)return void K.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(K,e));const $=this._collisionEntities.length>0||null!==this._voxelCollision;if("fly"===this._mode&&$){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}else"orbit"===this._mode&&$?(this._prevPoseValid&&this.checkCollision(this._pose.position)?(this._pose.copy(this._prevPose),this._controller.attach(this._pose,!0)):(this._prevPose.copy(this._pose),this._prevPoseValid=!0),this._prevPositionValid=!1):(this._prevPositionValid=!1,this._prevPoseValid=!1);if("orbit"===this._mode){let t=this._pose.angles.y;for(;t>180;)t-=360;for(;t<-180;)t+=360;if(void 0!==this._lastYaw){const e=t-this._lastYaw;e>180?t-=360:e<-180&&(t+=360)}this._pose.angles.y=t,this._lastYaw=t}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._applyOrbitRotationLimits()&&this._controller.attach(this._pose,!0),this._applyPoseToCamera()}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 et(t,e,n){return t+4*e+16*n}function nt(t,e,n){const o=2*e;return n<32?1==(t[o]>>>n&1):1==(t[o+1]>>>n-32&1)}function ot(t,e){let n=t&(1<<e)-1,o=0;for(;n;)o++,n&=n-1;return o}class it{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 it(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 nt(this.leafData,h,et(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+ot(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(!nt(this.leafData,u,et(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),b=Math.max(h-n,0,n-f),x=Math.max(m-o,0,o-y);if(v*v+b*b+x*x<=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,b=1&t?l:m,x=2&t?c:g,w=4&t?d:f,S=Math.max(p-e,0,e-b),_=Math.max(y-n,0,n-x),C=Math.max(v-o,0,o-w);if(S*S+_*_+C*C<=i){const s=u+ot(h,t);if(this._descendSphere(s,e,n,o,i,p,y,v,b,x,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)}findGroundInvertedY(t,e,n){return t<this.gridMinX||t>=this.gridMaxX||e<this.gridMinZ||e>=this.gridMaxZ||0===this.nodes.length?null:this._findCeiling(0,t,e,n,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)&&nt(this.leafData,h,et(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,b=0|f|y;for(const t of[v,b]){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,b=2&t?l:m,x=4&t?c:g,w=h+ot(p,t),S=this._findGround(w,e,n,o,d,f,y,v,b,x);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.findGroundInvertedY(i,s,r);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)&&nt(this.leafData,h,et(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,b=2|f|y;for(const t of[v,b]){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,b=2&t?l:m,x=4&t?c:g,w=h+ot(p,t),S=this._findCeiling(w,e,n,o,d,f,y,v,b,x);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 nt(this.leafData,d,et(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+ot(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 b=-1/0,x=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),C=(u-d)/2+o-Math.abs(r-(d+u)/2);if(v<=0||_<=0||C<=0)continue;const M=o-Math.sqrt(y);M>b&&(b=M,v<=_&&v<=C?(x=Math.sign(s-(t+p)/2)*v,w=0,S=0):_<=C?(x=0,w=Math.sign(a-(c+h)/2)*_,S=0):(x=0,w=0,S=Math.sign(r-(d+u)/2)*C))}if(b<=0)break;if(g>0){const t=x*h+w*u+S*m;t<0&&(x-=t*h,w-=t*u,S-=t*m)}s+=x,a+=w,r+=S,l+=x,c+=w,d+=S,p=!0;const _=Math.sqrt(x*x+w*w+S*S);_>1e-6&&(h=x/_,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,b=4&t?a:u,x=(16777215&c)+ot(d,t);this._collectLeaves(x,m,g,f,y,v,b,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;nt(this.leafData,p,et(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,b=4&t?a:m,x=p+ot(d,t);this._collectSolid(x,c,g,f,y,v,b,r,l)}}}function st(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 at{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._walkTarget=null,this._walkTargetArrivalDist=.5,this.tmpVec=new t.Vec3,this.tmpVec2=new t.Vec3,this.tmpVec3=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)return;const i=o.split("?")[0].split(".").pop()?.toLowerCase()||"";if("glb"!==i&&"gltf"!==i)return void console.warn(`[CharacterController] Unsupported collision mesh format ".${i}" — only .glb and .gltf are supported. Skipping: ${o}. Convert the mesh to GLB, or use voxel collision / primitives instead.`);console.log("[CharacterController] Loading custom collision mesh:",o);const s=new t.Asset(`collision-custom-${n}`,"container",{url:o});try{await new Promise((i,a)=>{s.ready(()=>{try{const o=new t.Entity(`collision-custom-${n}`),a=s.resource;if(a&&a.instantiateRenderEntity){const t=a.instantiateRenderEntity();for(;t.children.length>0;)o.addChild(t.children[0]);t.destroy()}this.configureCollisionEntity(o,e),this.computeAndStoreBounds(o),i()}catch(t){console.error("[CharacterController] Error setting up custom mesh:",t),a(t)}}),s.on("error",t=>{console.error("[CharacterController] Error loading custom mesh:",o,t),a(t)}),this.app.assets.add(s),this.app.assets.load(s)})}catch(t){console.error("[CharacterController] Failed to load custom collision mesh:",o,t);try{this.app.assets.remove(s)}catch{}}}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=st(n.position,[0,0,0]);e.setPosition(o[0],o[1],-o[2]);const i=st(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,b=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(b,n))}else e.setRotation(b);const x=st(n.scaling,[1,1,1]),w=n.meshType;if("plane"===w){const t=.01;e.setLocalScale(3*x[0],x[2]*t,3*x[1])}else"cube"===w||"sphere"===w?e.setLocalScale(3*x[0],3*x[1],3*x[2]):"floor"===w?e.setLocalScale(100*x[0],1*x[1],100*x[2]):e.setLocalScale(x[0],x[1],x[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 e=new t.Vec3(0,0,-1);this.camera.getRotation().transformVector(e,e);const n=Math.atan2(-e.y,Math.sqrt(e.x*e.x+e.z*e.z))*(180/Math.PI),o=Math.atan2(-e.x,-e.z)*(180/Math.PI);this.pitch=-n,this.yaw=o,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.updateSplatTransform(),this.snapToGroundIfClose(),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=>{const e=document.activeElement?.tagName;"INPUT"===e||"TEXTAREA"===e||document.activeElement?.isContentEditable||(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,Math.abs(e)+Math.abs(n)>2&&(this.autoMoveForward&&(this.autoMoveForward=!1),this._walkTarget&&(this._walkTarget=null)),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,n=!0){if(!this.voxelCollision)return!1;const o=this.tmpVec;this.worldToFile(t,o);const i=this.voxelCollision.querySphere(o.x,o.y,o.z,e);if(!i)return!1;const s=this.tmpVec2;s.set(i.x,i.y,i.z);const a=this.tmpVec;return this.fileVecToWorld(s,a),t.x+=a.x,t.z+=a.z,n&&(t.y+=a.y),n||Math.abs(a.x)>1e-6||Math.abs(a.z)>1e-6}checkGround(t){const e=t.y-this.playerHeight,n=e+this.stepHeight;let o=null;const i=t=>{t>n||(null===o||t>o)&&(o=t)};if(this.floorEntity){const e=this.floorEntity.getPosition(),n=this.floorEntity.getLocalScale(),o=n.x/2,s=n.z/2;Math.abs(t.x-e.x)<o&&Math.abs(t.z-e.z)<s&&i(e.y)}for(const e of this.collisionEntities){const n=e.getPosition(),o=e.getLocalScale(),s=e._collisionMeshType;if("floor"===s||"plane"===s||"sphere"===s)continue;let a,r,l,c,d,p;const h=e._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=n.x,d=n.y,p=n.z,a=o.x/2,r=o.y/2,l=o.z/2);const u=d+r;Math.abs(t.x-c)<a+this.collisionRadius&&Math.abs(t.z-p)<l+this.collisionRadius&&i(u)}if(this.voxelCollision){const n=this.tmpVec,o=this.tmpVec2;n.set(t.x,e,t.z),this.worldToFile(n,o);const s=this.worldYPerFileY(),a=this.fileStepHeight(),r=s<0?this.voxelCollision.findGroundInvertedY(o.x,o.z,o.y-a):this.voxelCollision.findGround(o.x,o.z,o.y+a);if(null!==r){const t=this.tmpVec3;t.set(o.x,r,o.z);const e=this._splatEntity?this._splatMatrix.data:null;let n;n=e?e[1]*t.x+e[5]*t.y+e[9]*t.z+e[13]:-r,i(n)}}return o}snapToGroundIfClose(){const t=this.camera.getPosition().clone(),e=this.checkGround(t);if(null===e)return void(this.isGrounded=!1);t.y-this.playerHeight<=e+this.groundCheckDistance&&(t.y=e+this.playerHeight,this.camera.setPosition(t),this.velocity.y=0,this.isGrounded=!0,this.wasGroundedLastFrame=!0,this.lastGroundedTime=this.currentTime)}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);if(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),!this._walkTarget||0===n&&0===o||(this._walkTarget=null),this._walkTarget){const t=this.camera.getPosition(),i=this._walkTarget.x-t.x,s=this._walkTarget.z-t.z;if(Math.sqrt(i*i+s*s)<this._walkTargetArrivalDist)this._walkTarget=null;else{let t=Math.atan2(-i,-s)*(180/Math.PI)-this.yaw;t>180&&(t-=360),t<-180&&(t+=360);const a=Math.abs(t),r=(120+80*Math.min(a/90,1))*e;this.yaw+=Math.max(-r,Math.min(r,t)),o=1,n=0}}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,!1);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),at.MAX_LANDING_DIP)),this.landingDipAmount*=1-Math.min(1,e*at.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?at.SPRINT_BOB_FREQ:at.WALK_BOB_FREQ,n=i?at.SPRINT_BOB_AMP_Y:at.WALK_BOB_AMP_Y,o=i?at.SPRINT_BOB_AMP_X:at.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,b=c.z+this.bobOffsetX*this.right.z;this.camera.setPosition(y,v,b),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 it.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}worldYPerFileY(){return this._splatEntity&&this._splatMatrix.data[5]||-1}fileStepHeight(){const t=Math.abs(this.worldYPerFileY());return t>1e-6?this.stepHeight/t:this.stepHeight}get voxelCollisionInstance(){return this.voxelCollision}get collisionMeshEntities(){return this.collisionEntities}addCollisionEntity(t){this.collisionEntities.includes(t)||(this.collisionEntities.push(t),t._collisionMeshType||(t._collisionMeshType="cube"),"floor"===t._collisionMeshType&&(this.floorEntity=t))}removeCollisionEntity(t){const e=this.collisionEntities.indexOf(t);-1!==e&&this.collisionEntities.splice(e,1),this.floorEntity===t&&(this.floorEntity=null)}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}walkTo(t,e=.5){this._walkTarget=t.clone(),this._walkTargetArrivalDist=e}cancelWalkTo(){this._walkTarget=null}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"),d=this.app.scene.layers.getLayerByName("VoxelDebug");c.addComponent("render",{type:"asset",meshInstances:[l],castShadows:!1,receiveShadows:!1,...d?{layers:[d.id]}:{}}),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}findGroundAt(t){return this.checkGround(t)}}at.WALK_BOB_FREQ=10,at.WALK_BOB_AMP_Y=.025,at.WALK_BOB_AMP_X=.012,at.SPRINT_BOB_FREQ=14,at.SPRINT_BOB_AMP_Y=.04,at.SPRINT_BOB_AMP_X=.02,at.LANDING_DIP_DECAY=8,at.MAX_LANDING_DIP=.15;const rt="\nuniform vec4 uMirrorClipPlane;\n\nvoid mirrorClipSplat(vec3 center, inout vec3 scale) {\n vec3 n = uMirrorClipPlane.xyz;\n if (dot(n, n) > 0.25) {\n float dist = dot(center, n) - uMirrorClipPlane.w;\n if (dist < 0.0) {\n scale = vec3(0.0);\n }\n }\n}\n",lt="\nuniform uMirrorClipPlane: vec4f;\n\nfn mirrorClipSplat(center: vec3f, scale: ptr<function, vec3f>) {\n let n = uniform.uMirrorClipPlane.xyz;\n if (dot(n, n) > 0.25) {\n let dist = dot(center, n) - uniform.uMirrorClipPlane.w;\n if (dist < 0.0) {\n *scale = vec3f(0.0);\n }\n }\n}\n",ct=rt+"\nvoid modifySplatCenter(inout vec3 center) {}\n\nvoid modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) {\n mirrorClipSplat(modifiedCenter, scale);\n}\n\nvoid modifySplatColor(vec3 center, inout vec4 color) {}\n",dt=lt+"\nfn modifySplatCenter(center: ptr<function, vec3f>) {}\n\nfn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr<function, vec4f>, scale: ptr<function, vec3f>) {\n mirrorClipSplat(modifiedCenter, scale);\n}\n\nfn modifySplatColor(center: vec3f, color: ptr<function, vec4f>) {}\n",pt=rt+"\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];\nuniform mat4 uRelightModelMatrix;\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 // Transform center from model-space to world-space via the entity's world matrix.\n // Unified mode: identity (centers already world-space in work buffer).\n // Non-unified mode: entity world transform (needed when swap splats have overrides).\n vec3 worldPos = (uRelightModelMatrix * vec4(center, 1.0)).xyz;\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",ht=lt+"\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>;\nuniform uRelightModelMatrix: mat4x4f;\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 // Transform center from model-space to world-space via the entity's world matrix.\n // Unified mode: identity (centers already world-space in work buffer).\n // Non-unified mode: entity world transform (needed when swap splats have overrides).\n let worldPos = (uniform.uRelightModelMatrix * vec4f(center, 1.0)).xyz;\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",ut=pt+"\nvoid modifySplatCenter(inout vec3 center) {}\n\nvoid modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) {\n mirrorClipSplat(modifiedCenter, scale);\n}\n\nvoid modifySplatColor(vec3 center, inout vec4 color) {\n applyRelighting(center, color);\n}\n",mt=ht+"\nfn modifySplatCenter(center: ptr<function, vec3f>) {}\n\nfn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr<function, vec4f>, scale: ptr<function, vec3f>) {\n mirrorClipSplat(modifiedCenter, scale);\n}\n\nfn modifySplatColor(center: vec3f, color: ptr<function, vec4f>) {\n applyRelighting(center, color);\n}\n";let gt=null;const ft=16;function yt(){if(gt)return gt;const e=t.createScript("gsplatRelighting");return Object.assign(e.prototype,{material:null,_effectInitialized:!1,_shadersNeedApplication:!1,_shaderManagedExternally:!1,_isUnified:!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._isUnified=!!this.entity.gsplat?.unified,this._ambientArray=[1,1,1],this._posArray=new Float32Array(48),this._colorArray=new Float32Array(48),this._rangeArray=new Float32Array(ft),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(e){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._isUnified?this._setUniform("uRelightModelMatrix",t.Mat4.IDENTITY.data):this._setUniform("uRelightModelMatrix",this.entity.getWorldTransform().data),this.material.update())):this.entity.gsplat&&(this._effectInitialized=!0,this._isUnified=!!this.entity.gsplat?.unified,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,ft);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<ft;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:()=>ut,getShaderWGSL:()=>mt,_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()}}),gt=e,e}const vt={get class(){return yt()}};let bt=null;function xt(){if(bt)return bt;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:20,acceleration:0,delay:.5,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);this._setUniform("uWarpedRadius",t/Math.sqrt(1+t/5))},_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=t/Math.sqrt(1+t/5),n=this.delay;if(0===this.acceleration)return n+e/this.speed;const o=this.speed*this.speed+2*this.acceleration*e;if(o<0)return 1/0;return n+(-this.speed+Math.sqrt(o))/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;\nuniform float uWarpedRadius; // warped max scene radius for reverse-wave\n\n// Shared globals (initialized once per vertex)\nfloat g_dist;\nfloat g_dotWavePos;\nfloat g_liftTime;\nfloat g_liftWavePos;\nfloat g_reverseReveal; // 0 = not reverse-revealed, 1 = fully revealed by reverse wave\n\nvoid initShared(vec3 center) {\n float rawDist = length(center - uCenter);\n // Distance warp: compress far distances so distant splats reveal sooner.\n // See gsplat-reveal-bloom.ts for the derivation. K=5 matches bloom.\n const float K = 5.0;\n g_dist = rawDist / sqrt(1.0 + rawDist / K);\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 // Reverse wave: outer 10% of splats reveal from outside-in simultaneously.\n // distFromEdge is small for edge splats (reveal first), grows inward.\n // revWavePos grows over time → reveals progressively inward.\n g_reverseReveal = 0.0;\n float outerThreshold = uWarpedRadius * 0.9;\n if (g_dist > outerThreshold && uWarpedRadius > 0.0) {\n float distFromEdge = uWarpedRadius - g_dist;\n float revWavePos = uSpeed * uTime + 0.5 * uAcceleration * uTime * uTime;\n g_reverseReveal = 1.0 - smoothstep(revWavePos - uBandWidth, revWavePos + uBandWidth, distFromEdge);\n }\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 mirrorClipSplat(modifiedCenter, scale);\n\n // Reverse wave: if fully revealed from outside-in, show at full size\n if (g_reverseReveal >= 1.0) return;\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: use reverse reveal if active, otherwise invisible\n if (g_reverseReveal > 0.0) {\n scale *= g_reverseReveal;\n } else {\n scale = vec3(0.0);\n }\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;\nuniform uWarpedRadius: 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;\nvar<private> g_reverseReveal: f32;\n\nfn initShared(center: vec3f) {\n let rawDist = length(center - uniform.uCenter);\n // Distance warp: compress far distances so distant splats reveal sooner.\n // See gsplat-reveal-bloom.ts for the derivation. K=5 matches bloom.\n let K: f32 = 5.0;\n g_dist = rawDist / sqrt(1.0 + rawDist / K);\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 // Reverse wave: outer 10% of splats reveal from outside-in simultaneously.\n // distFromEdge is small for edge splats (reveal first), grows inward.\n // revWavePos grows over time → reveals progressively inward.\n g_reverseReveal = 0.0;\n let outerThreshold = uniform.uWarpedRadius * 0.9;\n if (g_dist > outerThreshold && uniform.uWarpedRadius > 0.0) {\n let distFromEdge = uniform.uWarpedRadius - g_dist;\n let revWavePos = uniform.uSpeed * uniform.uTime + 0.5 * uniform.uAcceleration * uniform.uTime * uniform.uTime;\n g_reverseReveal = 1.0 - smoothstep(revWavePos - uniform.uBandWidth, revWavePos + uniform.uBandWidth, distFromEdge);\n }\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 mirrorClipSplat(modifiedCenter, scale);\n\n // Reverse wave: if fully revealed from outside-in, show at full size\n if (g_reverseReveal >= 1.0) {\n return;\n }\n\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: use reverse reveal if active, otherwise invisible\n if (g_reverseReveal > 0.0) {\n *scale *= g_reverseReveal;\n } else {\n *scale = vec3f(0.0);\n }\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?ht:pt)+"\n"+o.replace("// EFFECT_HOOK","applyRelighting(center, color);")}else{o=("wgsl"===n?lt:rt)+o}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.entity.script&&this.entity.script.gsplatCompareClip;if(e&&e.enabled)return e.material=null,e._shadersNeedApplication=!0,void(this.material=null);const n=this.app.graphicsDevice,o=n?.isWebGPU?"wgsl":"glsl";this.material.getShaderChunks(o).delete("gsplatModifyVS"),this.material.update(),this.material=null},_setUniform(t,e){this.material?.setParameter(t,e)},destroy(){this._removeShaders()}}),bt=e,e}let wt=null;let St=null;let _t=null;function Ct(){if(_t)return _t;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:100,acceleration:32,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,e=t/Math.sqrt(1+t/5);if(0===this.acceleration)return e/this.speed+.5;const n=this.speed*this.speed+2*this.acceleration*e;if(n<0)return 1/0;return(-this.speed+Math.sqrt(n))/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));const t=this._autoEndRadius>0?this._autoEndRadius:this.endRadius;this._setUniform("uWarpedRadius",t/Math.sqrt(1+t/5)),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\nuniform float uWarpedRadius; // warped max scene radius for reverse-wave\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 float rawDist = length(center - uCenter);\n\n // Distance warp: compress far distances so distant splats reveal sooner,\n // while preserving near-field pacing. Formula saturates smoothly as rawDist grows:\n // warped = raw / sqrt(1 + raw/K)\n // With K=5: near rawDist ~10 → ~4.5, rawDist ~500 → ~49 (~10x compression).\n // This makes the reveal wave reach distant horizon splats ~10x sooner.\n const float K = 5.0;\n g_dist = rawDist / sqrt(1.0 + rawDist / K);\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 // --- Forward wave (inside-out) ---\n float forwardBloom;\n if (effectiveDist > wavePos + uBandWidth * 0.5) {\n forwardBloom = 0.0;\n } else if (effectiveDist > wavePos - uBandWidth * 0.5) {\n forwardBloom = smoothstep(wavePos + uBandWidth * 0.5, wavePos - uBandWidth * 0.5, effectiveDist);\n } else {\n float distBehind = (wavePos - uBandWidth * 0.5) - effectiveDist;\n float settleTime = distBehind / max(uWaveSpeed + uAcceleration * uTime, 1.0);\n float overshootDecay = exp(-settleTime * 6.0);\n forwardBloom = 1.0 + (uOvershoot - 1.0) * overshootDecay;\n }\n\n // --- Reverse wave (outside-in) for outer 10% of splats ---\n // Starts at time 0 from the scene edge, sweeping inward simultaneously.\n // Only affects splats in the outer 10% of warped distance.\n float reverseBloom = 0.0;\n float outerThreshold = uWarpedRadius * 0.9;\n if (g_dist > outerThreshold && uWarpedRadius > 0.0) {\n float distFromEdge = uWarpedRadius - effectiveDist;\n // Reverse wave also uses the same speed/accel\n float revWavePos = uWaveSpeed * uTime + 0.5 * uAcceleration * uTime * uTime;\n if (distFromEdge < revWavePos + uBandWidth * 0.5) {\n if (distFromEdge > revWavePos - uBandWidth * 0.5) {\n reverseBloom = smoothstep(revWavePos + uBandWidth * 0.5, revWavePos - uBandWidth * 0.5, distFromEdge);\n } else {\n float distBehind = (revWavePos - uBandWidth * 0.5) - distFromEdge;\n float settleTime = distBehind / max(uWaveSpeed + uAcceleration * uTime, 1.0);\n float overshootDecay = exp(-settleTime * 6.0);\n reverseBloom = 1.0 + (uOvershoot - 1.0) * overshootDecay;\n }\n }\n }\n\n // Take whichever wave reveals first\n g_bloom = max(forwardBloom, reverseBloom);\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 mirrorClipSplat(modifiedCenter, 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;\nuniform uWarpedRadius: f32;\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 let rawDist = length(center - uniform.uCenter);\n\n // Distance warp: compress far distances so distant splats reveal sooner,\n // while preserving near-field pacing. See GLSL version for the derivation.\n let K: f32 = 5.0;\n g_dist = rawDist / sqrt(1.0 + rawDist / K);\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 // --- Forward wave (inside-out) ---\n var forwardBloom: f32;\n if (effectiveDist > wavePos + uniform.uBandWidth * 0.5) {\n forwardBloom = 0.0;\n } else if (effectiveDist > wavePos - uniform.uBandWidth * 0.5) {\n forwardBloom = 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 forwardBloom = 1.0 + (uniform.uOvershoot - 1.0) * overshootDecay;\n }\n\n // --- Reverse wave (outside-in) for outer 10% of splats ---\n var reverseBloom: f32 = 0.0;\n let outerThreshold = uniform.uWarpedRadius * 0.9;\n if (g_dist > outerThreshold && uniform.uWarpedRadius > 0.0) {\n let distFromEdge = uniform.uWarpedRadius - effectiveDist;\n let revWavePos = uniform.uWaveSpeed * uniform.uTime + 0.5 * uniform.uAcceleration * uniform.uTime * uniform.uTime;\n if (distFromEdge < revWavePos + uniform.uBandWidth * 0.5) {\n if (distFromEdge > revWavePos - uniform.uBandWidth * 0.5) {\n reverseBloom = smoothstep(revWavePos + uniform.uBandWidth * 0.5, revWavePos - uniform.uBandWidth * 0.5, distFromEdge);\n } else {\n let distBehind = (revWavePos - uniform.uBandWidth * 0.5) - distFromEdge;\n let settleTime = distBehind / max(uniform.uWaveSpeed + uniform.uAcceleration * uniform.uTime, 1.0);\n let overshootDecay = exp(-settleTime * 6.0);\n reverseBloom = 1.0 + (uniform.uOvershoot - 1.0) * overshootDecay;\n }\n }\n }\n\n g_bloom = max(forwardBloom, reverseBloom);\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 mirrorClipSplat(modifiedCenter, scale);\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?ht:pt)+"\n"+o.replace("// EFFECT_HOOK","applyRelighting(center, color);")}else{o=("wgsl"===n?lt:rt)+o}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.entity.script&&this.entity.script.gsplatCompareClip;if(e&&e.enabled)return e.material=null,e._shadersNeedApplication=!0,void(this.material=null);const n=this.app.graphicsDevice,o=n?.isWebGPU?"wgsl":"glsl";this.material.getShaderChunks(o).delete("gsplatModifyVS"),this.material.update(),this.material=null},_setUniform(t,e){this.material?.setParameter(t,e)},destroy(){this._removeShaders()}}),_t=e,e}let Mt=null;const Et=1e6;class Pt{constructor(e,n={}){this.extents=new t.Vec3(12,12,12),this.density=2,this.speed=1,this.drift=.15,this.opacity=.8,this.color=[1,1,1],this.particleMinSize=.006,this.particleMaxSize=.012,this.elongate=1,this._container=null,this._format=null,this._time=0,this._baseCellArray=[0,0,0],this._camPosArray=[0,0,0],this._gridHalfArray=[0,0,0],this._particleSizeArray=[0,0],this._effectiveHalfCells=[0,0,0],this._scalarsDirty=!0,this.app=e,this.entity=new t.Entity("GsplatWeather"),this.followEntity=n.followEntity??null,this.applyOptions(n),this._buildFormat(),this._buildContainer()}applyOptions(t){t.extents&&this.extents.set(t.extents.x,t.extents.y,t.extents.z),void 0!==t.density&&(this.density=t.density),void 0!==t.speed&&(this.speed=t.speed),void 0!==t.drift&&(this.drift=t.drift),void 0!==t.opacity&&(this.opacity=t.opacity),t.color&&(this.color=[t.color[0],t.color[1],t.color[2]]),void 0!==t.particleMinSize&&(this.particleMinSize=t.particleMinSize),void 0!==t.particleMaxSize&&(this.particleMaxSize=t.particleMaxSize),void 0!==t.elongate&&(this.elongate=t.elongate),void 0!==t.followEntity&&(this.followEntity=t.followEntity),this._scalarsDirty=!0}markScalarsDirty(){this._scalarsDirty=!0}get cellSize(){return 1/Math.max(this.density,.1)}_halfCells(t){return Math.min(128,Math.max(1,Math.floor(t*Math.max(this.density,.1))))}get numParticles(){return 2*this._halfCells(this.extents.x)*this._halfCells(this.extents.y)*2*this._halfCells(this.extents.z)*2}_buildFormat(){const e=t;this._format=new e.GSplatFormat(this.app.graphicsDevice,[{name:"data",format:e.PIXELFORMAT_RGBA8}],{readGLSL:"\n uniform float uTime;\n uniform float uCellSize;\n uniform vec3 uGridHalf;\n uniform vec3 uBaseCell;\n uniform vec3 uCameraPos;\n uniform float uSpeed;\n uniform float uDrift;\n uniform float uOpacity;\n uniform vec3 uColor;\n uniform vec2 uParticleSize;\n uniform float uElongate;\n\n vec3 weatherLocalPos;\n vec3 weatherWC;\n vec4 sd;\n\n float weatherHash(vec3 p) {\n return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453);\n }\n\n vec3 getCenter() {\n sd = loadData();\n\n float dx = floor(sd.r * 255.0 + 0.5) - uGridHalf.x;\n float dz = floor(sd.b * 255.0 + 0.5) - uGridHalf.z;\n float dy = floor(sd.g * 255.0 + 0.5);\n\n vec3 worldCell = uBaseCell + vec3(dx, dy - uGridHalf.y, dz);\n weatherWC = worldCell;\n\n vec3 col = vec3(worldCell.x, 0.0, worldCell.z);\n\n float fx = weatherHash(col + vec3(1.0, 0.0, 0.0));\n float fz = weatherHash(col + vec3(0.0, 0.0, 3.0));\n\n fx += (weatherHash(worldCell + vec3(4.0, 0.0, 0.0)) - 0.5) * 0.4;\n fz += (weatherHash(worldCell + vec3(0.0, 0.0, 5.0)) - 0.5) * 0.4;\n\n float spd = mix(0.3, 0.8, weatherHash(col)) * uSpeed;\n\n float gridYf = uGridHalf.y * 2.0;\n float colY = (dy + weatherHash(worldCell + vec3(0.0, 2.0, 0.0))) / gridYf;\n colY = fract(colY - uTime * spd / gridYf);\n\n float phase = weatherHash(col + vec3(7.0, 0.0, 0.0)) * 6.28318;\n fx += sin(uTime * 0.8 + phase) * uDrift;\n fz += cos(uTime * 0.6 + phase) * uDrift;\n\n weatherLocalPos = vec3(\n (dx + fx) * uCellSize,\n (-uGridHalf.y + colY * gridYf) * uCellSize,\n (dz + fz) * uCellSize\n );\n return weatherLocalPos;\n }\n\n vec4 getColor() {\n vec3 camOffset = uCameraPos - uBaseCell * uCellSize;\n float dist = length(weatherLocalPos - camOffset);\n float maxDist = min(uGridHalf.x, uGridHalf.z) * uCellSize * 0.9;\n float fade = 1.0 - smoothstep(maxDist * 0.6, maxDist, dist);\n float alpha = mix(0.5, 0.9, weatherHash(weatherWC + 20.0)) * fade * uOpacity;\n return vec4(uColor, alpha);\n }\n\n vec3 getScale() {\n float size = mix(uParticleSize.x, uParticleSize.y, weatherHash(weatherWC + 10.0));\n return vec3(size, size * uElongate, size);\n }\n\n vec4 getRotation() { return vec4(0.0, 0.0, 0.0, 1.0); }\n",readWGSL:"\n uniform uTime: f32;\n uniform uCellSize: f32;\n uniform uGridHalf: vec3f;\n uniform uBaseCell: vec3f;\n uniform uCameraPos: vec3f;\n uniform uSpeed: f32;\n uniform uDrift: f32;\n uniform uOpacity: f32;\n uniform uColor: vec3f;\n uniform uParticleSize: vec2f;\n uniform uElongate: f32;\n\n var<private> weatherLocalPos: vec3f;\n var<private> weatherWC: vec3f;\n var<private> sd: vec4f;\n\n fn weatherHash(p: vec3f) -> f32 {\n return fract(sin(dot(p, vec3f(127.1, 311.7, 74.7))) * 43758.5453);\n }\n\n fn getCenter() -> vec3f {\n sd = loadData();\n\n let dx = floor(sd.r * 255.0 + 0.5) - uniform.uGridHalf.x;\n let dz = floor(sd.b * 255.0 + 0.5) - uniform.uGridHalf.z;\n let dy = floor(sd.g * 255.0 + 0.5);\n\n let worldCell = uniform.uBaseCell + vec3f(dx, dy - uniform.uGridHalf.y, dz);\n weatherWC = worldCell;\n\n let col = vec3f(worldCell.x, 0.0, worldCell.z);\n\n var fx = weatherHash(col + vec3f(1.0, 0.0, 0.0));\n var fz = weatherHash(col + vec3f(0.0, 0.0, 3.0));\n\n fx = fx + (weatherHash(worldCell + vec3f(4.0, 0.0, 0.0)) - 0.5) * 0.4;\n fz = fz + (weatherHash(worldCell + vec3f(0.0, 0.0, 5.0)) - 0.5) * 0.4;\n\n let spd = mix(0.3, 0.8, weatherHash(col)) * uniform.uSpeed;\n\n let gridYf = uniform.uGridHalf.y * 2.0;\n var colY = (dy + weatherHash(worldCell + vec3f(0.0, 2.0, 0.0))) / gridYf;\n colY = fract(colY - uniform.uTime * spd / gridYf);\n\n let phase = weatherHash(col + vec3f(7.0, 0.0, 0.0)) * 6.28318;\n fx = fx + sin(uniform.uTime * 0.8 + phase) * uniform.uDrift;\n fz = fz + cos(uniform.uTime * 0.6 + phase) * uniform.uDrift;\n\n weatherLocalPos = vec3f(\n (dx + fx) * uniform.uCellSize,\n (-uniform.uGridHalf.y + colY * gridYf) * uniform.uCellSize,\n (dz + fz) * uniform.uCellSize\n );\n return weatherLocalPos;\n }\n\n fn getColor() -> vec4f {\n let camOffset = uniform.uCameraPos - uniform.uBaseCell * uniform.uCellSize;\n let dist = length(weatherLocalPos - camOffset);\n let maxDist = min(uniform.uGridHalf.x, uniform.uGridHalf.z) * uniform.uCellSize * 0.9;\n let fade = 1.0 - smoothstep(maxDist * 0.6, maxDist, dist);\n let alpha = mix(0.5, 0.9, weatherHash(weatherWC + 20.0)) * fade * uniform.uOpacity;\n return vec4f(uniform.uColor, alpha);\n }\n\n fn getScale() -> vec3f {\n let size = mix(uniform.uParticleSize.x, uniform.uParticleSize.y, weatherHash(weatherWC + 10.0));\n return vec3f(size, size * uniform.uElongate, size);\n }\n\n fn getRotation() -> vec4f { return vec4f(0.0, 0.0, 0.0, 1.0); }\n"})}rebuild(){this._buildContainer()}_buildContainer(){const e=t;this._container&&(this._container.destroy(),this._container=null);const n=this.app.graphicsDevice;let o=this._halfCells(this.extents.x),i=this._halfCells(this.extents.y),s=this._halfCells(this.extents.z),a=2*o*(2*i)*(2*s);if(a>Et){const t=Math.cbrt(Et/a);o=Math.max(1,Math.floor(o*t)),i=Math.max(1,Math.floor(i*t)),s=Math.max(1,Math.floor(s*t));const e=2*o*(2*i)*(2*s);console.warn(`[GsplatWeather] Clamped particle count from ${a} to ${e} (cap 1000000)`),a=e}const r=2*o,l=2*i,c=2*s,d=this.cellSize;this._effectiveHalfCells[0]=o,this._effectiveHalfCells[1]=i,this._effectiveHalfCells[2]=s,this._container=new e.GSplatContainer(n,a,this._format);const p=this._container.getTexture("data").lock(),h=this._container.centers;let u=0;for(let t=0;t<r;t++)for(let e=0;e<l;e++)for(let n=0;n<c;n++)p[4*u+0]=t,p[4*u+1]=e,p[4*u+2]=n,p[4*u+3]=256*Math.random()|0,h[3*u+0]=(t-o+.5)*d,h[3*u+1]=(e-i+.5)*d,h[3*u+2]=(n-s+.5)*d,u++;this._container.getTexture("data").unlock();const m=o*d,g=i*d,f=s*d;this._container.aabb=new t.BoundingBox(t.Vec3.ZERO,new t.Vec3(m,g,f)),this._container.update(a,!0),this.entity.gsplat?this.entity.gsplat.resource=this._container:this.entity.addComponent("gsplat",{resource:this._container,unified:!0})}update(t){if(!this._container||!this.entity.gsplat)return;if(!this.entity.enabled)return;if(this._time+=t,this.opacity<=0)return;const e=this.cellSize;let n=0,o=0,i=0;if(this.followEntity){const t=this.followEntity.getPosition();n=t.x,o=t.y,i=t.z}else{const t=this.entity.getPosition();n=t.x,o=t.y,i=t.z}const s=Math.floor(n/e),a=Math.floor(o/e),r=Math.floor(i/e);this.entity.setPosition(s*e,a*e,r*e),this._baseCellArray[0]=s,this._baseCellArray[1]=a,this._baseCellArray[2]=r,this._camPosArray[0]=n,this._camPosArray[1]=o,this._camPosArray[2]=i,this._gridHalfArray[0]=this._effectiveHalfCells[0],this._gridHalfArray[1]=this._effectiveHalfCells[1],this._gridHalfArray[2]=this._effectiveHalfCells[2],this._scalarsDirty&&(this._particleSizeArray[0]=this.particleMinSize,this._particleSizeArray[1]=this.particleMaxSize);const l=this.entity.gsplat;l.setParameter("uTime",this._time),l.setParameter("uCellSize",e),l.setParameter("uGridHalf",this._gridHalfArray),l.setParameter("uBaseCell",this._baseCellArray),l.setParameter("uCameraPos",this._camPosArray),this._scalarsDirty&&(l.setParameter("uSpeed",this.speed),l.setParameter("uDrift",this.drift),l.setParameter("uOpacity",this.opacity),l.setParameter("uColor",this.color),l.setParameter("uParticleSize",this._particleSizeArray),l.setParameter("uElongate",this.elongate),this._scalarsDirty=!1);const c=l.material;c&&"function"==typeof c.update&&c.update()}destroy(){this._container&&(this._container.destroy(),this._container=null),this.entity&&this.entity.destroy()}}const kt={snow:{density:2,speed:1,drift:.15,opacity:.85,color:[1,1,1],particleMinSize:.006,particleMaxSize:.014,elongate:1},rain:{density:3,speed:18,drift:.02,opacity:.55,color:[.8,.85,.95],particleMinSize:.004,particleMaxSize:.008,elongate:12}},Tt={fast:{speed:40,acceleration:8,delay:.1,oscillationIntensity:.1,dotTint:{r:0,g:1,b:1},waveTint:{r:1,g:.5,b:0},endRadius:500},medium:{speed:20,acceleration:4,delay:.5,oscillationIntensity:.2,dotTint:{r:0,g:1,b:1},waveTint:{r:1,g:.5,b:0},endRadius:500},slow:{speed:12,acceleration:0,delay:.75,oscillationIntensity:.25,dotTint:{r:0,g:1,b:1},waveTint:{r:1,g:.5,b:0},endRadius:500}},At={fast:{speed:32,acceleration:100,delay:0,oscillationIntensity:.15,dotTint:{r:1,g:.6,b:.15},waveTint:{r:.2,g:.5,b:1},endRadius:500},medium:{speed:20,acceleration:72,delay:0,oscillationIntensity:.2,dotTint:{r:1,g:.6,b:.15},waveTint:{r:.2,g:.5,b:1},endRadius:500},slow:{speed:12,acceleration:40,delay:0,oscillationIntensity:.25,dotTint:{r:1,g:.6,b:.15},waveTint:{r:.2,g:.5,b:1},endRadius:500}};function Lt(t,e="bloom"){if("none"===t)return;return("bloom"===e?At:Tt)[t]}class Rt{constructor(t){this.isDragging=!1,this.labelFadeTimeout=null,this.snapPositions=[0,.5,1],this.lastSnapIndex=1,this.destroyed=!1,this._resizeObserver=null,this.container=t.container,this.config=t.config,this.onPositionChange=t.onPositionChange,this.orientation=t.config.orientation||"vertical",this.position=(t.config.initialPosition??50)/100;const e=t.theme||{};this.overlay=document.createElement("div"),this.overlay.className="ss-compare-overlay",Object.assign(this.overlay.style,{position:"absolute",inset:"0",pointerEvents:"none",zIndex:"10",overflow:"hidden"});const n=e.compareLineColor||t.config.lineColor||"#ffffff",o=t.config.lineOpacity??.6;t.config.handleColor;const i={position:"absolute",pointerEvents:"none",boxShadow:"0 0 4px rgba(0,0,0,0.4)"};this.lineA=document.createElement("div"),this.lineA.className="ss-compare-line",Object.assign(this.lineA.style,i),this.lineB=document.createElement("div"),this.lineB.className="ss-compare-line",Object.assign(this.lineB.style,i),this._applyLineColor(n,o),this.handle=document.createElement("div"),this.handle.className="ss-compare-handle",this.handle.tabIndex=0,this.handle.setAttribute("role","slider"),this.handle.setAttribute("aria-label","Compare slider"),this.handle.setAttribute("aria-valuemin","0"),this.handle.setAttribute("aria-valuemax","100"),Object.assign(this.handle.style,{position:"absolute",cursor:"vertical"===this.orientation?"ew-resize":"ns-resize",pointerEvents:"auto",display:"flex",alignItems:"center",justifyContent:"center",transform:"translate(-50%, -50%)",transition:"box-shadow 0.15s ease",outline:"none",touchAction:"none",boxSizing:"border-box"}),this.updateHandleStyle(t.config.handleStyle||"arrows");const s=e.compareLabelBg||"rgba(0,0,0,0.5)",a=e.compareLabelColor||"#ffffff",r=e.compareLabelFontSize||"13px";this.beforeLabel=this._createLabel(t.config.beforeLabel||"Before",s,a,r),this.afterLabel=this._createLabel(t.config.afterLabel||"After",s,a,r),this.overlay.appendChild(this.lineA),this.overlay.appendChild(this.lineB),this.overlay.appendChild(this.handle),this.overlay.appendChild(this.beforeLabel),this.overlay.appendChild(this.afterLabel),!1===t.config.showLabels&&(this.beforeLabel.style.display="none",this.afterLabel.style.display="none"),this.container.appendChild(this.overlay),this._updateLayout(),this._onPointerDown=this._handlePointerDown.bind(this),this._onPointerMove=this._handlePointerMove.bind(this),this._onPointerUp=this._handlePointerUp.bind(this),this._onKeyDown=this._handleKeyDown.bind(this),this._onDblClick=this._handleDblClick.bind(this),this.handle.addEventListener("pointerdown",this._onPointerDown),window.addEventListener("pointermove",this._onPointerMove),window.addEventListener("pointerup",this._onPointerUp),!1!==t.config.enableKeyboard&&this.handle.addEventListener("keydown",this._onKeyDown),!1!==t.config.enableSnap&&this.handle.addEventListener("dblclick",this._onDblClick),this.handle.addEventListener("focus",()=>{this.handle.style.boxShadow="0 0 0 3px rgba(59, 130, 246, 0.5)"}),this.handle.addEventListener("blur",()=>{this.handle.style.boxShadow=""}),this._resizeObserver=new ResizeObserver(()=>{this.destroyed||this._updateLayout()}),this._resizeObserver.observe(this.container),this._resetLabelFade()}_createLabel(t,e,n,o){const i=document.createElement("div");return i.className="ss-compare-label",i.textContent=t,Object.assign(i.style,{position:"absolute",top:"20px",pointerEvents:"none",background:e,color:n,fontSize:o,fontFamily:"system-ui, -apple-system, sans-serif",padding:"4px 12px",borderRadius:"12px",whiteSpace:"nowrap",transition:"opacity 0.3s ease",userSelect:"none"}),i}static _hexToRgba(t,e){const n=t.replace("#","");if(!/^[0-9a-fA-F]{6}$/.test(n))return`rgba(255,255,255,${e})`;return`rgba(${parseInt(n.substring(0,2),16)},${parseInt(n.substring(2,4),16)},${parseInt(n.substring(4,6),16)},${e})`}_applyLineColor(t,e){const n=Rt._hexToRgba(t,e);this.lineA.style.background=n,this.lineB.style.background=n}_updateLayout(){const t=100*this.position+"%",e=this.handle.offsetWidth||40,n=this.handle.offsetHeight||40,o=("vertical"===this.orientation?n:e)/2;if("vertical"===this.orientation){const e=this.container.clientHeight||600,n=this.container.clientWidth||800,i=e/2;if(Object.assign(this.lineA.style,{left:t,top:"0",width:"2px",height:`${Math.max(0,i-o)}px`}),Object.assign(this.lineB.style,{left:t,top:`${i+o}px`,width:"2px",height:`${Math.max(0,e-i-o)}px`}),this.handle.style.left=t,this.handle.style.top="50%",this.beforeLabel){const t=20,e=this.position*n;this.beforeLabel.style.left=`${Math.max(8,e-t-60)}px`,this.beforeLabel.style.right="",this.beforeLabel.style.transform="",this.afterLabel.style.left=`${Math.min(n-8,e+t)}px`,this.afterLabel.style.right="",this.afterLabel.style.transform=""}}else{const e=this.container.clientWidth||800,n=this.container.clientHeight||600,i=e/2;Object.assign(this.lineA.style,{top:t,left:"0",height:"2px",width:`${Math.max(0,i-o)}px`}),Object.assign(this.lineB.style,{top:t,left:`${i+o}px`,height:"2px",width:`${Math.max(0,e-i-o)}px`}),this.handle.style.top=t,this.handle.style.left="50%",this.beforeLabel&&(this.beforeLabel.style.left="50%",this.beforeLabel.style.transform="translateX(-50%)",this.beforeLabel.style.top=`${Math.max(8,this.position*n-40)}px`,this.afterLabel.style.left="50%",this.afterLabel.style.transform="translateX(-50%)",this.afterLabel.style.top=`${Math.min(n-30,this.position*n+20)}px`)}this.handle.setAttribute("aria-valuenow",String(Math.round(100*this.position)))}_handlePointerDown(t){t.preventDefault(),t.stopPropagation(),this.isDragging=!0,this.handle.setPointerCapture(t.pointerId),this._showLabels()}_handlePointerMove(t){if(!this.isDragging)return;t.preventDefault();const e=this.container.getBoundingClientRect();let n;n="vertical"===this.orientation?(t.clientX-e.left)/e.width:(t.clientY-e.top)/e.height,this._setPosition(Math.max(0,Math.min(1,n)))}_handlePointerUp(){this.isDragging&&(this.isDragging=!1,this._resetLabelFade())}_handleKeyDown(t){const e=.02;let n=this.position;if("vertical"===this.orientation)if("ArrowLeft"===t.key)n-=e;else{if("ArrowRight"!==t.key)return;n+=e}else if("ArrowUp"===t.key)n-=e;else{if("ArrowDown"!==t.key)return;n+=e}t.preventDefault(),this._setPosition(Math.max(0,Math.min(1,n))),this._showLabels(),this._resetLabelFade()}_handleDblClick(){this.lastSnapIndex=(this.lastSnapIndex+1)%this.snapPositions.length,this._setPosition(this.snapPositions[this.lastSnapIndex]),this._showLabels(),this._resetLabelFade()}_setPosition(t){this.position=t,this._updateLayout(),this.onPositionChange(t)}_showLabels(){this.beforeLabel.style.opacity="1",this.afterLabel.style.opacity="1"}_resetLabelFade(){this.labelFadeTimeout&&clearTimeout(this.labelFadeTimeout),this._showLabels(),this.labelFadeTimeout=setTimeout(()=>{this.destroyed||(this.beforeLabel.style.opacity="0",this.afterLabel.style.opacity="0")},3e3)}getPosition(){return this.position}setPosition(t){this._setPosition(Math.max(0,Math.min(1,t)))}setLabels(t,e){this.beforeLabel.textContent=t,this.afterLabel.textContent=e}setOrientation(t){this.orientation!==t&&(this.orientation=t,this.handle.style.cursor="vertical"===t?"ew-resize":"ns-resize",this.updateHandleStyle(this.config.handleStyle||"arrows"),this._updateLayout())}updateLineStyle(t,e){const n=t||this.config.lineColor||"#ffffff",o=e??this.config.lineOpacity??.6;void 0!==t&&(this.config.lineColor=t),void 0!==e&&(this.config.lineOpacity=e),this._applyLineColor(n,o)}updateHandleColor(t,e){this.config.handleColor=t,void 0!==e&&(this.config.handleOpacity=e),this.updateHandleStyle(this.config.handleStyle||"arrows")}_handleRgba(){const t=this.config.handleColor||this.config.lineColor||"#ffffff",e=this.config.handleOpacity??.6;return Rt._hexToRgba(t,e)}updateHandleStyle(t){const e=this._handleRgba();for(this.config.handleColor||this.config.lineColor;this.handle.firstChild;)this.handle.removeChild(this.handle.firstChild);const n="rgba(0,0,0,0.6)";if("arrows"===t)Object.assign(this.handle.style,{width:"40px",height:"40px",borderRadius:"50%",borderStyle:"solid",borderWidth:"2px",borderColor:e,background:n}),this.handle.appendChild(function(t,e="white"){const n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("width","20"),n.setAttribute("height","20"),n.setAttribute("viewBox","0 0 20 20"),n.setAttribute("fill","none");const o=document.createElementNS("http://www.w3.org/2000/svg","path"),i=document.createElementNS("http://www.w3.org/2000/svg","path"),s={stroke:e,"stroke-width":"1.5","stroke-linecap":"round","stroke-linejoin":"round"};for(const[t,e]of Object.entries(s))o.setAttribute(t,e),i.setAttribute(t,e);return"vertical"===t?(o.setAttribute("d","M6 10L2 10M2 10L4.5 7.5M2 10L4.5 12.5"),i.setAttribute("d","M14 10L18 10M18 10L15.5 7.5M18 10L15.5 12.5")):(o.setAttribute("d","M10 6L10 2M10 2L7.5 4.5M10 2L12.5 4.5"),i.setAttribute("d","M10 14L10 18M10 18L7.5 15.5M10 18L12.5 15.5")),n.appendChild(o),n.appendChild(i),n}(this.orientation,e));else if("circle"===t)Object.assign(this.handle.style,{width:"28px",height:"28px",borderRadius:"50%",borderStyle:"solid",borderWidth:"2px",borderColor:e,background:n});else{const t="vertical"===this.orientation?{width:"6px",height:"40px"}:{width:"40px",height:"6px"};Object.assign(this.handle.style,{...t,borderRadius:"3px",border:"none",background:e})}this.config.handleStyle=t,this._updateLayout()}setEnableKeyboard(t){this.handle.removeEventListener("keydown",this._onKeyDown),t&&this.handle.addEventListener("keydown",this._onKeyDown)}setEnableSnap(t){this.handle.removeEventListener("dblclick",this._onDblClick),t&&this.handle.addEventListener("dblclick",this._onDblClick)}show(){this.overlay.style.display=""}hide(){this.overlay.style.display="none"}destroy(){this.destroyed=!0,this.labelFadeTimeout&&clearTimeout(this.labelFadeTimeout),this._resizeObserver?.disconnect(),this._resizeObserver=null,this.handle.removeEventListener("pointerdown",this._onPointerDown),window.removeEventListener("pointermove",this._onPointerMove),window.removeEventListener("pointerup",this._onPointerUp),this.handle.removeEventListener("keydown",this._onKeyDown),this.handle.removeEventListener("dblclick",this._onDblClick),this.overlay.remove()}}var zt,Dt={},It={},Ft={};function Bt(){if(zt)return Ft;zt=1,Object.defineProperty(Ft,"__esModule",{value:!0}),Ft.loop=Ft.conditional=Ft.parse=void 0;Ft.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};Ft.conditional=function(t,e){return function(n,o,i,s){e(n,o,i)&&s(n,t,o,i)}};return Ft.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}},Ft}var Vt,$t,Ut={};function Ot(){if(Vt)return Ut;Vt=1,Object.defineProperty(Ut,"__esModule",{value:!0}),Ut.readBits=Ut.readArray=Ut.readUnsigned=Ut.readString=Ut.peekBytes=Ut.readBytes=Ut.peekByte=Ut.readByte=Ut.buildStream=void 0;Ut.buildStream=function(t){return{data:t,pos:0}};var t=function(){return function(t){return t.data[t.pos++]}};Ut.readByte=t;Ut.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)}};Ut.readBytes=e;Ut.peekBytes=function(t){return function(e){return e.data.subarray(e.pos,e.pos+t)}};Ut.readString=function(t){return function(n){return Array.from(e(t)(n)).map(function(t){return String.fromCharCode(t)}).join("")}};Ut.readUnsigned=function(t){return function(n){var o=e(2)(n);return t?(o[1]<<8)+o[0]:(o[0]<<8)+o[1]}};Ut.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 Ut.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},{})}},Ut}var Nt,Wt={};var Ht,Gt,jt={};var Xt=function(){if(Gt)return Dt;Gt=1,Object.defineProperty(Dt,"__esModule",{value:!0}),Dt.decompressFrames=Dt.decompressFrame=Dt.parseGIF=void 0;var t,e=($t||($t=1,function(t){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var e=Bt(),n=Ot(),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}(It)),(t=It)&&t.__esModule?t:{default:t}),n=Bt(),o=Ot(),i=(Nt||(Nt=1,Object.defineProperty(Wt,"__esModule",{value:!0}),Wt.deinterlace=void 0,Wt.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}),Wt),s=(Ht||(Ht=1,Object.defineProperty(jt,"__esModule",{value:!0}),jt.lzw=void 0,jt.lzw=function(t,e,n){var o,i,s,a,r,l,c,d,p,h,u,m,g,f,y,v,b=4096,x=n,w=new Array(n),S=new Array(b),_=new Array(b),C=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<x;){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){C[f++]=_[d],c=d,g=d;continue}for(l=d,d==o&&(C[f++]=g,d=c);d>i;)C[f++]=_[d],d=S[d];g=255&_[d],C[f++]=g,o<b&&(S[o]=c,_[o]=g,0===(++o&s)&&o<b&&(a++,s+=o)),c=l}f--,w[y++]=C[f],p++}for(p=y;p<x;p++)w[p]=0;return w}),jt);Dt.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 Dt.decompressFrame=a,Dt.decompressFrames=function(t,e){return t.frames.filter(function(t){return t.image}).map(function(n){return a(n,t.gct,e)})},Dt}();class qt{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=Xt.parseGIF(n);if(this.frames=Xt.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 Yt(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 Zt(t){return Math.abs(t)<1e-10?0:t}class Kt{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(${Zt(a[0])},${Zt(-a[1])},${Zt(a[2])},${Zt(a[3])},${Zt(a[4])},${Zt(-a[5])},${Zt(a[6])},${Zt(a[7])},${Zt(a[8])},${Zt(-a[9])},${Zt(a[10])},${Zt(a[11])},${Zt(a[12])},${Zt(-a[13])},${Zt(a[14])},${Zt(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(${Zt(t[0])},${Zt(t[1])},${Zt(t[2])},${Zt(t[3])},${Zt(-t[4])},${Zt(-t[5])},${Zt(-t[6])},${Zt(-t[7])},${Zt(t[8])},${Zt(t[9])},${Zt(t[10])},${Zt(t[11])},${Zt(t[12])},${Zt(t[13])},${Zt(t[14])},${Zt(t[15])})`}(n)} scale(${Zt(1/e.pixelWidth)}, ${Zt(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=Yt(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,n){const o=(t,e,o)=>e<=o?t>=e&&t<=o:!!n&&(t>=e||t<=o);this.meshes.forEach(n=>{const i=n.config;if(i.visibilityRange){const s=i.visibilityRange;let a=!0;"percentage"===s.type?a=o(t,s.start,s.end):"waypoint"===s.type&&(a=o(e,s.start,s.end)),n.entity.enabled=a}if(i.billboard&&i.billboardRange){const s=i.billboardRange;let a=!1;"percentage"===s.type?a=o(t,s.start,s.end):"waypoint"===s.type&&(a=o(e,s.start,s.end)),n.entity._billboardActive=a}else i.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=Yt(e.position,{x:0,y:0,z:0});i.setPosition(s.x,s.y,-s.z);const a=Yt(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,b=d*h*g-p*u*m;return i.setRotation(-y,-v,b,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 Qt(t,e){const n=new Kt(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 Jt=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"]),te=new Set(["width","height","clientWidth","clientHeight","getBoundingClientRect","addEventListener","removeEventListener"]),ee=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"]),ne=["window","document","self","globalThis","top","parent","frames","opener","exports","module","require","define","fetch","XMLHttpRequest","WebSocket","EventSource","localStorage","sessionStorage","indexedDB","caches","Worker","SharedWorker","ServiceWorker","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 oe{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&&Jt.has(e)){const n=t[e];return"function"==typeof n?n.bind(t):n}},set:()=>!1,has:(t,e)=>"string"==typeof e&&Jt.has(e),ownKeys:()=>[...Jt],getPrototypeOf:()=>null})),l=(e=this.api.canvas,new Proxy(e,{get(t,e){if(!ee.has(e)&&"string"==typeof e&&te.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",...ne],u=`'use strict';\n(function(eval) {\n${t}\n}).call(this, undefined);`,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,...ne.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 ie(t,e,n,o,i,s){if(!o||""===o.trim())return null;console.log("[Custom Script] Initializing with hardened sandbox...");const a=new oe(o);return a.initialize({viewer:t,canvas:n,getScrollPercentage:i,getCurrentWaypointIndex:s},e),a.execute(),a}class se{constructor(e,n,o={}){this.options=o,this.frameAssets=new Map,this.currentFrame=0,this.isPlaying=!1,this.lastFrameTime=0,this.loadingFrames=new Set,this.destroyed=!1,this.listeners=new Map,this.app=e,this.frameUrls=n.frameUrls,this.fps=n.fps||24,this.loop=!1!==n.loop,this.preloadCount=n.preloadCount||10,this.frameInterval=1e3/this.fps,this.entity=new t.Entity("frameSequenceSplat"),this.entity.addComponent("gsplat",{unified:!0}),n.rotation&&this.entity.setEulerAngles(n.rotation[0],n.rotation[1],n.rotation[2]),this.entity.enabled=!1,this.app.root.addChild(this.entity),this.preloadInitialFrames(),n.autoplay&&this.preloadFrame(0).then(()=>{this.destroyed||this.play()})}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},{reorder:!1});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)}}displayFrame(t){if(this.destroyed)return!1;const e=this.frameAssets.get(t);if(!e||!e.loaded)return!1;const n=this.entity.gsplat;return n&&(n.asset=e),this.entity.enabled=!0,this.currentFrame=t,this.emit("frameChange",t,this.frameUrls.length),this.options.onFrameChange?.(t,this.frameUrls.length),this.updatePreloadWindow(),!0}async displayFrameAsync(t){if(this.destroyed)return;if(this.displayFrame(t))return;await this.preloadFrame(t)&&!this.destroyed&&this.displayFrame(t)}update(t){if(!this.isPlaying||this.destroyed)return;const e=performance.now(),n=e-this.lastFrameTime;if(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)&&(this.lastFrameTime=e-n%this.frameInterval)}}play(){this.isPlaying||this.destroyed||(this.isPlaying=!0,this.lastFrameTime=performance.now(),this.entity.enabled||this.displayFrameAsync(this.currentFrame),this.emit("play"))}pause(){this.isPlaying=!1,this.emit("pause")}stop(){this.isPlaying=!1,this.currentFrame=0,this.displayFrameAsync(0),this.emit("stop")}setFrame(t){t<0&&(t=0),t>=this.frameUrls.length&&(t=this.frameUrls.length-1),this.displayFrameAsync(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.entity.setPosition(t,e,n)}setRotation(t,e,n){this.entity.setEulerAngles(t,e,n)}setScale(t,e,n){this.entity.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.entity.destroy(),this.listeners.clear()}}class ae{static isRetryableStatus(t){return 408===t||429===t||t>=500}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.beaconFlushed=!1,this.retryNotBeforeMs=0,this.handleCanvasClick=null,this.handleUiClick=null,this.viewerContainer=null,this.samplingPaused=!1,this.viewerEventBindings=[],this.sessionId=crypto.randomUUID();try{this.visitorId=localStorage.getItem("storysplat_visitor_id")||sessionStorage.getItem("storysplat_visitor_id")||"",this.visitorId||(this.visitorId=crypto.randomUUID());try{localStorage.setItem("storysplat_visitor_id",this.visitorId)}catch{}}catch{this.visitorId=crypto.randomUUID()}this.handleVisibility=()=>{"hidden"===document.visibilityState&&this.beaconFlush(!1)},this.handleUnload=()=>this.beaconFlush(!0),this.setupEventListeners(),this.sampleCamera(!0),this.startCameraSampling(),this.startFlushInterval(),document.addEventListener("visibilitychange",this.handleVisibility),window.addEventListener("beforeunload",this.handleUnload),this.observeViewerEvent("error",()=>this.destroy())}ts(){return Date.now()-this.sessionStart}quantize(t,e){return Math.round(t/e)*e}safeEventName(t){return String(t||"").replace(/[^a-zA-Z0-9 _-]+/g," ").replace(/\s+/g," ").trim().slice(0,64).trim()||"unknown"}isSafeTrackingId(t){return/^[a-zA-Z0-9_-]{1,128}$/.test(t)}safeHostname(t){const e=String(t||"").trim().toLowerCase().replace(/:\d+$/,"").slice(0,200);return e&&!e.includes("..")&&/^[a-z0-9.-]{1,200}$/.test(e)?e:""}safeUrlForAnalytics(t,e){const n=String(t||"").trim();if(!n)return"";try{const t=new URL(n);return/^https?:$/.test(t.protocol)?`${t.origin}${t.pathname}`.slice(0,e):""}catch{return n.replace(/[\u0000-\u001F<>]/g,"").slice(0,e)}}getEmbedSourceMeta(){try{return{hostname:this.safeHostname(window.location.hostname||""),referrer:this.safeUrlForAnalytics(document.referrer||"",500),embedUrl:this.safeUrlForAnalytics(window.location.href||"",500)}}catch{return{hostname:"",referrer:"",embedUrl:""}}}recordInteraction(t,e){const n=this.safeEventName(e);this.pendingEvents.push({type:t,name:n}),this.timeline.push({t:this.ts(),k:"u",c:t,n:n})}elementText(t){return(t.getAttribute("aria-label")||t.getAttribute("title")||t.textContent||t.dataset.linkId||t.dataset.portalId||t.getAttribute("href")||"").trim()}describeButton(t){const e=t.classList;if(e.contains("storysplat-btn-prev"))return"previous waypoint";if(e.contains("storysplat-btn-next"))return"next waypoint";if(e.contains("storysplat-btn-play"))return"play pause";if(e.contains("storysplat-mode-btn"))return`camera mode ${t.dataset.mode||this.elementText(t)}`;if(e.contains("storysplat-explore-btn"))return`explore mode ${t.dataset.exploreMode||this.elementText(t)}`;if(e.contains("storysplat-fullscreen-btn"))return"fullscreen";if(e.contains("storysplat-mute-btn"))return"mute toggle";if(e.contains("storysplat-help-btn"))return"help";if(e.contains("storysplat-help-tab"))return`help tab ${t.dataset.tab||this.elementText(t)}`;if(e.contains("storysplat-share-btn"))return"share";if(e.contains("storysplat-photo-share-close"))return"photo share close";if(e.contains("storysplat-photo-share-action"))return`photo share ${this.elementText(t)}`;if(e.contains("storysplat-relight-btn"))return"relighting toggle";if(e.contains("storysplat-fisheye-btn"))return"tiny planet toggle";if(e.contains("storysplat-waypoint-list-toggle"))return"waypoint menu toggle";if(e.contains("storysplat-scene-menu-toggle"))return"scene menu toggle";if(e.contains("storysplat-portal-popup-confirm"))return"portal confirm";if(e.contains("storysplat-portal-popup-cancel"))return"portal cancel";if(e.contains("storysplat-hotspot-popup-close"))return"hotspot popup close";if(e.contains("storysplat-measure-btn"))return"measure toggle";if(e.contains("storysplat-skins-btn"))return"virtual staging toggle";if(e.contains("storysplat-skin-exit-btn"))return"virtual staging exit";if(e.contains("storysplat-vr-btn"))return"vr";if(e.contains("storysplat-ar-btn"))return"ar";if(e.contains("storysplat-lazy-load-start-btn"))return"start experience";const n=this.elementText(t);if(n)return n;const o=Array.from(e).find(t=>t.startsWith("storysplat-"));return o?o.replace(/^storysplat-/,"").replace(/-/g," "):"button"}trackStagingView(t){if(!this.isSafeTrackingId(t))return;const e=`${this.baseUrl}/api/track-embed`,n=JSON.stringify({sceneId:this.sceneId,ownerId:this.ownerId,type:"staging-view",skinId:t,visitorId:this.visitorId,sessionId:this.sessionId});fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:n}).then(t=>{!t.ok&&ae.isRetryableStatus(t.status)&&(this.saveToRetryQueue(n),429===t.status&&this.noteRetryAfter(t))}).catch(()=>{this.saveToRetryQueue(n)})}trackUiClick(t){if(this.destroyed)return;const e=t.target;if(!(e instanceof Element))return;if(e===this.viewer.canvas||e.closest("canvas")===this.viewer.canvas)return;const n=e.closest('.storysplat-scene-menu-item, .storysplat-scene-menu-folder-header, .storysplat-waypoint-item, .storysplat-hotspot-popup-link, .storysplat-skin-item, button, a, [role="button"]');if(n&&this.viewerContainer?.contains(n)&&!(n instanceof HTMLButtonElement&&n.disabled)){if(n.classList.contains("storysplat-skin-item")){const t=n.dataset.skinId||n.getAttribute("data-skin-id")||"",e=Number(n.dataset.skinIndex),o=Number.isFinite(e)?`staging ${e+1}`:"virtual staging image",i=this.elementText(n)||o;return this.recordInteraction("ui",`virtual staging ${i}`),void(t&&this.trackStagingView(t))}if(n.classList.contains("storysplat-scene-menu-item")){const t=this.elementText(n);if(n.dataset.portalId)return void this.recordInteraction("menu",`portal ${t||n.dataset.portalId}`);if(n.dataset.linkId){const e=n.dataset.contentType||"link";return this.recordInteraction("menu",`${e} ${t||n.dataset.linkId}`),void("link"===e&&this.recordInteraction("link",`menu ${t||n.dataset.linkId}`))}return void this.recordInteraction("menu",t||"scene menu item")}if(n.classList.contains("storysplat-scene-menu-folder-header"))this.recordInteraction("menu",`folder ${this.elementText(n)}`);else{if(n.classList.contains("storysplat-waypoint-item")){const t=n.dataset.waypointIndex;return void this.recordInteraction("menu",`waypoint ${t?Number(t)+1:this.elementText(n)}`)}if(n.classList.contains("storysplat-hotspot-popup-link"))this.recordInteraction("link",`hotspot ${this.elementText(n)}`);else{if(n instanceof HTMLAnchorElement){const t=this.elementText(n);return void this.recordInteraction("link",t||n.href||"link")}this.recordInteraction("ui",this.describeButton(n))}}}}sampleCamera(t=!1){if(!(this.destroyed||this.samplingPaused||this.ts()>this.maxDurationMs))try{const e=this.viewer.getPosition(),n=this.viewer.getRotation(),o=Math.abs(e.x-this.lastPos.x)+Math.abs(e.y-this.lastPos.y)+Math.abs(e.z-this.lastPos.z),i=Math.abs(n.x-this.lastRot.x)+Math.abs(n.y-this.lastRot.y)+Math.abs(n.z-this.lastRot.z);if(!t&&o<.1&&i<2)return;this.lastPos={...e},this.lastRot={...n},this.timeline.push({t:this.ts(),k:"c",p:[this.quantize(e.x,.01),this.quantize(e.y,.01),this.quantize(e.z,.01)],r:[this.quantize(n.x,.1),this.quantize(n.y,.1),this.quantize(n.z,.1)]})}catch{}}startCameraSampling(){this.cameraIntervalId=setInterval(()=>this.sampleCamera(),1e3)}observeViewerEvent(t,e){(this.viewer.onInternal?this.viewer.onInternal.bind(this.viewer):this.viewer.on.bind(this.viewer))(t,e),this.viewerEventBindings.push({viewer:this.viewer,event:t,callback:e})}setupEventListeners(){this.observeViewerEvent("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})}),this.observeViewerEvent("modeChange",t=>{const e=t?.mode||"unknown";this.pendingEvents.push({type:"mode",name:e}),this.timeline.push({t:this.ts(),k:"m",m:e})}),this.observeViewerEvent("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]})}),this.observeViewerEvent("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||""})}),this.observeViewerEvent("progressUpdate",t=>{void 0!==t?.progress&&this.timeline.push({t:this.ts(),k:"s",v:Math.round(1e3*t.progress)/1e3})}),this.observeViewerEvent("playbackStart",()=>{this.timeline.push({t:this.ts(),k:"pb",a:"start"})}),this.observeViewerEvent("playbackStop",()=>{this.timeline.push({t:this.ts(),k:"pb",a:"stop"})}),this.observeViewerEvent("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{}try{this.viewerContainer=this.viewer.canvas.closest(".storysplat-viewer-container")||this.viewer.canvas.parentElement,this.viewerContainer&&(this.handleUiClick=t=>this.trackUiClick(t),this.viewerContainer.addEventListener("click",this.handleUiClick,!0))}catch{}}detachDomListeners(){if(this.handleCanvasClick){try{this.viewer.canvas.removeEventListener("click",this.handleCanvasClick)}catch{}this.handleCanvasClick=null}if(this.handleUiClick&&this.viewerContainer){try{this.viewerContainer.removeEventListener("click",this.handleUiClick,!0)}catch{}this.handleUiClick=null,this.viewerContainer=null}}detachViewerEventListeners(){for(const t of this.viewerEventBindings)try{t.viewer.off(t.event,t.callback)}catch{}this.viewerEventBindings=[]}beginPortalTransition(){this.destroyed||(this.samplingPaused=!0,this.detachDomListeners(),this.detachViewerEventListeners())}attachViewer(t){this.destroyed||(this.detachDomListeners(),this.detachViewerEventListeners(),this.viewer=t,this.samplingPaused=!1,this.setupEventListeners(),this.observeViewerEvent("error",()=>this.destroy()),this.sampleCamera(!0))}startFlushInterval(){this.flushIntervalId=setInterval(()=>this.flush(),3e4)}buildAnalyticsPayload(){const t=Math.round((Date.now()-this.sessionStart)/1e3),e=this.pendingEvents.splice(0,100),n=this.getEmbedSourceMeta();return{sceneId:this.sceneId,ownerId:this.ownerId,type:"analytics",sessionId:this.sessionId,visitorId:this.visitorId,sessionDuration:t,events:e,hostname:n.hostname,referrer:n.referrer}}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};if(0===this.chunkIndex){const t=this.getEmbedSourceMeta();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(),referrer:t.referrer,embedUrl:t.embedUrl,hostname:t.hostname}}if(t){const t=Date.now()-this.sessionStart;n.duration=Math.round(t/1e3),n.durationMs=t}return this.chunkIndex++,n}async drainRetryQueue(t,e){if(Date.now()<this.retryNotBeforeMs)return;let n;try{const t=localStorage.getItem(ae.QUEUE_KEY);if(!t)return;if(n=JSON.parse(t),!Array.isArray(n)||0===n.length)return}catch{try{localStorage.removeItem(ae.QUEUE_KEY)}catch{}return}const o=Date.now(),i=n.filter(t=>t&&"string"==typeof t.body&&"number"==typeof t.savedAt&&o-t.savedAt<ae.QUEUE_MAX_AGE_MS);if(0===i.length){try{localStorage.removeItem(ae.QUEUE_KEY)}catch{}return}const s=await Promise.allSettled(i.map(n=>{const o=new AbortController,i=setTimeout(()=>o.abort(),ae.RETRY_FETCH_TIMEOUT_MS);return fetch(t,{method:"POST",headers:e,body:n.body,signal:o.signal}).finally(()=>clearTimeout(i))})),a=[];s.forEach((t,e)=>{if("fulfilled"===t.status){const n=t.value;if(n.ok)return;ae.isRetryableStatus(n.status)&&(a.push(i[e]),429===n.status&&this.noteRetryAfter(n))}else a.push(i[e])});try{0===a.length?localStorage.removeItem(ae.QUEUE_KEY):localStorage.setItem(ae.QUEUE_KEY,JSON.stringify(a))}catch{}}noteRetryAfter(t){const e=t.headers.get("Retry-After");if(!e)return;let n=0;const o=Number(e);if(Number.isFinite(o)&&o>=0)n=1e3*o;else{const t=Date.parse(e);Number.isNaN(t)||(n=Math.max(0,t-Date.now()))}n=Math.min(Math.max(n,1e3),3e5),this.retryNotBeforeMs=Math.max(this.retryNotBeforeMs,Date.now()+n)}saveToRetryQueue(t){try{const e=localStorage.getItem(ae.QUEUE_KEY),n=e?JSON.parse(e):[],o=Array.isArray(n)?n:[];for(o.push({body:t,savedAt:Date.now()});o.length>ae.MAX_QUEUE_SIZE;)o.shift();localStorage.setItem(ae.QUEUE_KEY,JSON.stringify(o))}catch{}}async flush(){if(this.destroyed)return;const t=`${this.baseUrl}/api/track-embed`,e={"Content-Type":"application/json"};if(await this.drainRetryQueue(t,e),this.pendingEvents.length>0){const n=JSON.stringify(this.buildAnalyticsPayload());try{const o=await fetch(t,{method:"POST",headers:e,body:n});!o.ok&&ae.isRetryableStatus(o.status)&&(this.saveToRetryQueue(n),429===o.status&&this.noteRetryAfter(o))}catch{this.saveToRetryQueue(n)}}if(this.timeline.length>0){const n=JSON.stringify(this.buildSessionChunkPayload(!1));try{const o=await fetch(t,{method:"POST",headers:e,body:n});!o.ok&&ae.isRetryableStatus(o.status)&&(this.saveToRetryQueue(n),429===o.status&&this.noteRetryAfter(o))}catch{this.saveToRetryQueue(n)}}}beaconFlush(t){if(this.destroyed||this.beaconFlushed)return;t&&(this.beaconFlushed=!0);const e=`${this.baseUrl}/api/track-embed`;if(this.pendingEvents.length>0)try{const t=this.buildAnalyticsPayload();navigator.sendBeacon(e,new Blob([JSON.stringify(t)],{type:"text/plain"}))}catch{}if(this.timeline.length>0||t)try{const n=this.buildSessionChunkPayload(t);navigator.sendBeacon(e,new Blob([JSON.stringify(n)],{type:"text/plain"}))}catch{}}destroy(){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.detachDomListeners(),this.detachViewerEventListeners())}}function re(t,e,n,o){const i=new ae(t,e,n,o);return{destroy:()=>i.destroy(),beginPortalTransition:()=>i.beginPortalTransition(),attachViewer:t=>i.attachViewer(t)}}ae.QUEUE_KEY="storysplat_analytics_queue",ae.MAX_QUEUE_SIZE=10,ae.QUEUE_MAX_AGE_MS=36e5,ae.RETRY_FETCH_TIMEOUT_MS=5e3;const le=/^G-[A-Z0-9]{6,20}$/i;function ce(t){return function(t){return"string"==typeof t&&le.test(t.trim())}(t)?t.trim().toUpperCase():null}function de(t,e="unknown",n=100){return(String(t||e).trim()||e).slice(0,n)}function pe(t,e,n){const o=ce(e);if(!o||"undefined"==typeof window||"undefined"==typeof document)return{destroy:()=>{}};!function(t){const e=`storysplat-ga-${t.replace(/[^A-Z0-9_-]/gi,"-")}`;if(!document.getElementById(e)){const n=document.createElement("script");n.id=e,n.async=!0,n.src=`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(t)}`,document.head.appendChild(n)}window.dataLayer=window.dataLayer||[],"function"!=typeof window.gtag&&(window.gtag=function(...t){window.dataLayer.push(t)}),window.gtag("js",new Date),window.gtag("config",t,{send_page_view:!1})}(o);const i=window.gtag;if("function"!=typeof i)return{destroy:()=>{}};const s=Date.now(),a=de(n?.sceneId,"",128),r=de(n?.sceneName||document.title,"",120),l=[],c=(e,n)=>{(t.onInternal?t.onInternal.bind(t):t.on.bind(t))(e,n),l.push({event:e,callback:n})};i("event","page_view",{page_title:r||document.title,storysplat_scene_id:a,storysplat_scene_name:r}),c("waypointChange",t=>{i("event","storysplat_waypoint",{waypoint_name:de(t?.waypoint?.name||t?.name),waypoint_index:t?.index??0,storysplat_scene_id:a})}),c("hotspotClick",t=>{i("event","storysplat_hotspot",{hotspot_name:de(t?.hotspot?.title||t?.hotspot?.name),hotspot_id:de(t?.hotspot?.id,"",128),storysplat_scene_id:a})}),c("modeChange",t=>{i("event","storysplat_mode_change",{camera_mode:de(t?.mode),storysplat_scene_id:a})}),c("portalActivated",t=>{i("event","storysplat_portal",{portal_target:de(t?.targetSceneName),portal_target_id:de(t?.targetSceneId,"",128),storysplat_scene_id:a})}),c("playbackStart",()=>{i("event","storysplat_playback",{action:"start",storysplat_scene_id:a})}),c("playbackStop",()=>{i("event","storysplat_playback",{action:"stop",storysplat_scene_id:a})}),c("playbackComplete",()=>{i("event","storysplat_playback",{action:"complete",storysplat_scene_id:a})});const d=()=>{const t=Math.round((Date.now()-s)/1e3);i("event","storysplat_engagement",{session_duration:t,storysplat_scene_id:a})};return window.addEventListener("beforeunload",d),{destroy:()=>{window.removeEventListener("beforeunload",d);for(const e of l)try{t.off(e.event,e.callback)}catch{}l.length=0}}}function he(t,e){const n=t.keyframes;if(0===n.length)return null;if(1===n.length)return n[0].value;if(e<=n[0].time)return n[0].value;if(e>=n[n.length-1].time)return n[n.length-1].value;let o=0,i=n.length-1;for(;o<i-1;){const t=o+i>>1;n[t].time<=e?o=t:i=t}const s=n[o],a=n[i],r=a.time-s.time;if(r<=0)return s.value;const l=function(t,e){switch(e){case"linear":default:return t;case"ease-in":return t*t*t;case"ease-out":return 1-(1-t)*(1-t)*(1-t);case"ease-in-out":return t*t*(3-2*t)}}((e-s.time)/r,s.easing);return s.value+(a.value-s.value)*l}class ue{constructor(t,e,n={}){this.states=new Map,this.destroyed=!1,this.warnedEntities=new Set,this.curveCache=new Map,this.curveSetCache=new Map,this.entityLookup=t,this.sceneHandlers=n,this.animations=e;for(const t of e)"independent"===t.timelineMode&&this.states.set(t.id,{time:0,delayRemaining:t.delay||0,direction:1,playing:t.autoplay&&t.enabled})}update(t,e){if(!this.destroyed)for(const n of this.animations)if(n.enabled)if("scroll"===n.timelineMode){const t=100*e;this.evaluateAndApply(n,t)}else{const e=this.states.get(n.id);if(!e||!e.playing)continue;if(e.delayRemaining>0){e.delayRemaining-=t;continue}if(e.time+=t*e.direction,e.time>=n.duration)switch(n.playbackMode){case"loop":e.time=e.time%n.duration;break;case"pingpong":{const t=e.time-n.duration;e.time=Math.max(0,n.duration-t),e.direction=-1;break}default:e.time=n.duration,e.playing=!1}else e.time<=0&&-1===e.direction&&(e.time=Math.min(n.duration,Math.abs(e.time)),e.direction=1);this.evaluateAndApply(n,e.time)}}evaluateAndApply(t,e){const n=new Map;for(const o of t.tracks){const t=he(o,e);null!==t&&n.set(o.property,t)}if(0===n.size)return;if("fog"===t.entityType||"weather"===t.entityType){const e=this.sceneHandlers[t.entityType];return void(e&&e.apply(n))}const o=this.entityLookup(t.entityId,t.entityType);if(!o){const e=`${t.entityId}:${t.entityType}`;return void(this.warnedEntities.has(e)||(this.warnedEntities.add(e),console.warn(`[EntityAnim] Entity not found: id="${t.entityId}", type="${t.entityType}"`)))}this.applyValues(o,n,t.id)}getOrCreateCurve(e,n){let o=this.curveCache.get(e);return o?o.keys[0][1]=n:(o=new t.Curve([0,n]),this.curveCache.set(e,o)),o}getOrCreateCurveSet(e,n,o,i){let s=this.curveSetCache.get(e);return s?(s.curves[0].keys[0][1]=n,s.curves[1].keys[0][1]=o,s.curves[2].keys[0][1]=i):(s=new t.CurveSet([[0,n],[0,o],[0,i]]),this.curveSetCache.set(e,s)),s}applyValues(e,n,o){const i=e.getLocalPosition().clone(),s=e.getLocalEulerAngles().clone(),a=e.getLocalScale().clone();n.has("position.x")&&(i.x=n.get("position.x")),n.has("position.y")&&(i.y=n.get("position.y")),n.has("position.z")&&(i.z=-n.get("position.z"));const r=180/Math.PI;n.has("rotation.x")&&(s.x=n.get("rotation.x")*r),n.has("rotation.y")&&(s.y=n.get("rotation.y")*r),n.has("rotation.z")&&(s.z=-n.get("rotation.z")*r),n.has("scale.x")&&(a.x=n.get("scale.x")),n.has("scale.y")&&(a.y=n.get("scale.y")),n.has("scale.z")&&(a.z=n.get("scale.z")),e.setLocalPosition(i),e.setLocalEulerAngles(s),e.setLocalScale(a);const l=e.particlesystem;if(l){if(n.has("particle.rate")){const t=n.get("particle.rate");l.rate=t>0?1/t:0}if(n.has("particle.lifetime")&&(l.lifetime=n.get("particle.lifetime")),n.has("particle.speed")){const e=n.get("particle.speed");l.emitterShape===t.EMITTERSHAPE_SPHERE?l.radialSpeedGraph=this.getOrCreateCurve(`${o}-speed`,e):l.localVelocityGraph=this.getOrCreateCurveSet(`${o}-localVel`,e,e,e)}if(n.has("particle.speed2")){const e=n.get("particle.speed2");l.emitterShape===t.EMITTERSHAPE_SPHERE?l.radialSpeedGraph2=this.getOrCreateCurve(`${o}-speed2`,e):l.localVelocityGraph2=this.getOrCreateCurveSet(`${o}-localVel2`,e,e,e)}n.has("particle.radialSpeed")&&(l.radialSpeedGraph=this.getOrCreateCurve(`${o}-radialSpeed`,n.get("particle.radialSpeed"))),n.has("particle.radialSpeed2")&&(l.radialSpeedGraph2=this.getOrCreateCurve(`${o}-radialSpeed2`,n.get("particle.radialSpeed2"))),n.has("particle.scale")&&(l.scaleGraph=this.getOrCreateCurve(`${o}-scale`,n.get("particle.scale"))),n.has("particle.scale2")&&(l.scaleGraph2=this.getOrCreateCurve(`${o}-scale2`,n.get("particle.scale2"))),n.has("particle.rotationSpeedMin")&&(l.rotationSpeedGraph=this.getOrCreateCurve(`${o}-rotSpeedMin`,n.get("particle.rotationSpeedMin"))),n.has("particle.rotationSpeedMax")&&(l.rotationSpeedGraph2=this.getOrCreateCurve(`${o}-rotSpeedMax`,n.get("particle.rotationSpeedMax")));const e=n.get("particle.gravity.x"),i=n.get("particle.gravity.y"),s=n.get("particle.gravity.z");if(void 0!==e||void 0!==i||void 0!==s){const t=l.velocityGraph?.curves?.[0]?.keys?.[0]?.[1]??0,n=l.velocityGraph?.curves?.[1]?.keys?.[0]?.[1]??0,a=l.velocityGraph?.curves?.[2]?.keys?.[0]?.[1]??0;l.velocityGraph=this.getOrCreateCurveSet(`${o}-velocity`,e??t,i??n,s??a)}n.has("particle.opacity")&&(l.alphaGraph=this.getOrCreateCurve(`${o}-opacity`,n.get("particle.opacity"))),(n.has("particle.scale")||n.has("particle.scale2"))&&l.reset&&l.reset()}}play(){for(const t of this.animations){if("independent"!==t.timelineMode||!t.enabled)continue;const e=this.states.get(t.id);e&&(e.playing=!0)}}pause(){for(const t of this.states.values())t.playing=!1}reset(){for(const t of this.animations){if("independent"!==t.timelineMode)continue;const e=this.states.get(t.id);e&&(e.time=0,e.direction=1,e.delayRemaining=t.delay||0,e.playing=t.autoplay&&t.enabled)}}playAnimation(t){const e=this.states.get(t);e&&(e.playing=!0)}pauseAnimation(t){const e=this.states.get(t);e&&(e.playing=!1)}resetAnimation(t){const e=this.animations.find(e=>e.id===t),n=this.states.get(t);n&&e&&(n.time=0,n.direction=1,n.delayRemaining=e.delay||0)}evaluateAt(t,e){const n=this.animations.find(e=>e.id===t);n?this.evaluateAndApply(n,e):console.warn(`[EntityAnim] evaluateAt: animation "${t}" not found in ${this.animations.length} animations`)}evaluateAllAt(t,e){if(!this.destroyed)for(const n of this.animations)n.enabled&&("scroll"===n.timelineMode?void 0!==e&&this.evaluateAndApply(n,100*e):this.evaluateAndApply(n,t))}setAnimations(t){this.animations=t,this.warnedEntities.clear(),this.curveCache.clear(),this.curveSetCache.clear();const e=new Set(this.states.keys());for(const n of t)"independent"!==n.timelineMode||e.has(n.id)||this.states.set(n.id,{time:0,delayRemaining:n.delay||0,direction:1,playing:n.autoplay&&n.enabled});const n=new Set(t.map(t=>t.id));for(const t of this.states.keys())n.has(t)||this.states.delete(t)}getEntityTransform(e,n){if("fog"===n||"weather"===n){const t=this.sceneHandlers[n],e=t?.readValues();return e?{position:{x:0,y:0,z:0},rotation:{x:0,y:0,z:0},scale:{x:1,y:1,z:1},particleProps:e}:null}const o=this.entityLookup(e,n);if(!o)return null;const i=o.getLocalPosition(),s=o.getLocalEulerAngles(),a=o.getLocalScale(),r=Math.PI/180,l={position:{x:i.x,y:i.y,z:-i.z},rotation:{x:s.x*r,y:s.y*r,z:-s.z*r},scale:{x:a.x,y:a.y,z:a.z}},c=o.particlesystem;return c&&(l.particleProps={"particle.rate":c.rate>0?1/c.rate:0,"particle.lifetime":c.lifetime??5,"particle.speed":c.emitterShape===t.EMITTERSHAPE_SPHERE?c.radialSpeedGraph?.keys?.[0]?.[1]??1:c.localVelocityGraph?.curves?.[0]?.keys?.[0]?.[1]??1,"particle.speed2":c.emitterShape===t.EMITTERSHAPE_SPHERE?c.radialSpeedGraph2?.keys?.[0]?.[1]??1:c.localVelocityGraph2?.curves?.[0]?.keys?.[0]?.[1]??1,"particle.radialSpeed":c.radialSpeedGraph?.keys?.[0]?.[1]??0,"particle.radialSpeed2":c.radialSpeedGraph2?.keys?.[0]?.[1]??0,"particle.rotationSpeedMin":c.rotationSpeedGraph?.keys?.[0]?.[1]??0,"particle.rotationSpeedMax":c.rotationSpeedGraph2?.keys?.[0]?.[1]??0},c.velocityGraph&&(l.particleProps["particle.gravity.x"]=c.velocityGraph.curves?.[0]?.keys?.[0]?.[1]??0,l.particleProps["particle.gravity.y"]=c.velocityGraph.curves?.[1]?.keys?.[0]?.[1]??0,l.particleProps["particle.gravity.z"]=c.velocityGraph.curves?.[2]?.keys?.[0]?.[1]??0)),l}destroy(){this.destroyed=!0,this.states.clear(),this.animations=[],this.warnedEntities.clear(),this.curveCache.clear(),this.curveSetCache.clear()}}class me{constructor(e,n,o){this.metadata=null,this.highlightedSegment=0,this._material=null,this.entity=e;const i=e.root;if(this.device=i?.app?.graphicsDevice??t.app?.graphicsDevice,!this.device)throw new Error("SegmentAttachment: cannot find graphics device");this._numSplats=o;const s=e.gsplat;if(!s)throw new Error("SegmentAttachment: entity has no gsplat component");const a=s.instance,r=s._placement,l=s.asset?.resource??a?.resource??r?.resource,c=l?.textureDimensions??l?.streams?.textureDimensions;if(c)this._texWidth=c.x,this._texHeight=c.y;else{const t=Math.ceil(Math.sqrt(o));this._texWidth=t,this._texHeight=t}this.segmentIds=new Uint16Array(this._texWidth*this._texHeight);const d=Math.min(n.length,this.segmentIds.length);this.segmentIds.set(n.subarray(0,d)),this.segmentTexture=new t.Texture(this.device,{name:"segmentIds",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._uploadSegments()}updateMaterialParams(){if(!this._material){const t=this.entity.gsplat;if(!t)return;const e=t.instance??t._placement;if(!e)return;this._material=e.material??e.meshInstance?.material}this._material&&(this._material.setParameter("segmentTexture",this.segmentTexture),this._material.setParameter("highlightedSegment",this.highlightedSegment),this._material.update())}getSegmentAtSplat(t){return t<0||t>=this._numSplats?0:this.segmentIds[t]}setHighlight(t){this.highlightedSegment!==t&&(this.highlightedSegment=t,this.updateMaterialParams())}clearHighlight(){this.setHighlight(0)}destroy(){this.segmentTexture.destroy(),this._material=null}_uploadSegments(){const t=this.segmentTexture.lock(),e=t instanceof Uint16Array?t:new Uint16Array(t instanceof ArrayBuffer?t:t.buffer);e.set(this.segmentIds.subarray(0,e.length)),this.segmentTexture.unlock()}}async function ge(t,e,n){const o=await fetch(e);if(!o.ok)throw new Error(`Failed to fetch segment data: ${o.status}`);const i=await o.arrayBuffer(),s=new Uint16Array(i),a=s.length,r=new me(t,s,a);if(n)try{const t=await fetch(n);t.ok&&(r.metadata=await t.json())}catch(t){console.warn("[SegmentAttachment] Failed to load segment metadata:",t)}return r}const fe="x-storysplat-ar-log",ye="storysplatArDebug",ve=/(authorization|cookie|set[-_]?cookie|credential|password|secret|session(?:[-_]?id)?|signature|signed|token|jwt|csrf|xsrf|api[-_]?key|key)$/i,be=/(url|uri|href|baseUrl)$/i,xe=/\bhttps?:\/\/[^\s"'<>`]+/gi,we=/(^|[\s"'(])\/[^\s"'<>`)]*[?#][^\s"'<>`)]*/g,Se=/((?:authorization|cookie|set[-_]?cookie|credential|password|secret|session(?:[-_]?id)?|signature|signed|token|jwt|csrf|xsrf|api[-_]?key|key)=)[^&\s"'<>`]+/gi,_e=/Bearer\s+[A-Za-z0-9._~+/=-]+/gi,Ce=/\b(authorization|cookie|set-cookie|x-csrf-token|x-xsrf-token|csrf-token|xsrf-token|jwt|api[-_]?key)\s*:\s*[^;\r\n]+/gi;function Me(t){try{const e=new URL(t,"undefined"!=typeof window?window.location.origin:"http://localhost");return"http://localhost"===e.origin&&t.startsWith("/")?e.pathname:`${e.origin}${e.pathname}`}catch(t){return"[redacted-url]"}}function Ee(t){return t.replace(xe,t=>Me(t)).replace(we,(t,e)=>`${e}${Me(t.slice(e.length))}`).replace(Se,"$1[redacted]").replace(_e,"Bearer [redacted]").replace(Ce,"$1: [redacted]")}function Pe(){if("undefined"!=typeof window)try{const t=new URL(window.location.href);return`${t.origin}${t.pathname}`}catch(t){return}}function ke(){return"undefined"!=typeof window&&(("localhost"===(t=window.location.hostname)||"127.0.0.1"===t||"0.0.0.0"===t||t.endsWith(".local")||/^10\./.test(t)||/^192\.168\./.test(t)||/^172\.(1[6-9]|2\d|3[0-1])\./.test(t))&&function(){if("undefined"==typeof window)return!1;try{return"1"===new URLSearchParams(window.location.search).get(ye)||"true"===window.localStorage?.getItem(ye)}catch(t){return!1}}());var t}function Te(t,e,n){const o=function(t){if(void 0===t)return;const e=new WeakSet;try{return JSON.parse(JSON.stringify(t,(t,n)=>{const o=String(t);if(ve.test(o))return"[redacted]";if(be.test(o)&&"string"==typeof n)return Me(n);if("string"==typeof n)return Ee(n);if("function"==typeof n)return`[Function ${n.name||"anonymous"}]`;if("bigint"==typeof n)return n.toString();if(n instanceof Error)return{name:n.name,message:Ee(n.message)};if(n&&"object"==typeof n){if(e.has(n))return"[Circular]";e.add(n)}return n}))}catch(e){return Ee(String(t))}}(n),i=Ee(e),s="[StorySplat AR]",a=void 0===o?[s,i]:[s,i,o];"error"===t?console.error(...a):"warn"===t?console.warn(...a):console.log(...a),function(t){if(ke())try{const e=JSON.stringify(t);fetch("/__storysplat_ar_log",{method:"POST",headers:{"Content-Type":"application/json",[fe]:"1"},body:e,keepalive:e.length<6e4}).catch(()=>{})}catch(t){}}({source:"storysplat-ar",level:t,message:i,data:o,timestamp:(new Date).toISOString(),url:Pe()})}const Ae=(t,e)=>Te("debug",t,e),Le=(t,e)=>Te("info",t,e),Re=(t,e)=>Te("warn",t,e),ze=(t,e)=>Te("error",t,e);let De=null;function Ie(t,e){return De||(De=(async()=>{const n=function(t,e){if(e)return e.replace(/\/$/,"");const n=t?.getAttribute("data-8thwall-base");if(n)return n.replace(/\/$/,"");const o=document.documentElement.getAttribute("data-8thwall-base");return o?o.replace(/\/$/,""):"/cdn/8thwall"}(t,e);var o;Le("Loading 8th Wall engine",{baseUrl:n}),await(o=`${n}/xr.js`,new Promise((t,e)=>{if(document.querySelector(`script[src="${o}"]`))return void t();const n=document.createElement("script");n.src=o,n.async=!0,n.onload=()=>t(),n.onerror=()=>e(new Error(`[StorySplat] Failed to load 8th Wall script: ${o}`)),document.head.appendChild(n)}));const i=await function(t=15e3){return new Promise((e,n)=>{if(window.XR8)return void e(window.XR8);const o=setTimeout(()=>{window.removeEventListener("xrloaded",i),n(new Error(`[StorySplat] 8th Wall engine did not initialize within ${t}ms`))},t);function i(){clearTimeout(o),window.removeEventListener("xrloaded",i),window.XR8?e(window.XR8):n(new Error("[StorySplat] xrloaded event fired but XR8 global not found"))}window.addEventListener("xrloaded",i)})}();return Le("Loading 8th Wall SLAM module"),await i.loadChunk("slam"),i.XrController.configure({scale:"absolute"}),Le("8th Wall engine loaded",{version:i.version(),hasPlayCanvasWrapper:!!i.PlayCanvas?.run,hasXrController:!!i.XrController}),i})(),De.catch(t=>{ze("8th Wall engine load failed",t),De=null}),De)}function Fe(){return!!window.XR8}function Be(){return new Promise(t=>{navigator.xr?navigator.xr.isSessionSupported("immersive-ar").then(e=>t(e)).catch(()=>t(!1)):t(!1)})}class Ve{constructor(t){this.isPlaced=!1,this.reticle=null,this.reticleMat=null,this.reticleMesh=null,this.reticleOpacity=0,this.reticleVisibleUntil=0,this.currentScale=1,this.loggedFirstHit=!1,this.trackingStatus="",this.hitTestErrorCount=0,this.trackingGateWarned=!1,this.lastPinchDistance=0,this.lastRotationAngle=0,this.isTwoFingerGesture=!1,this.wasMultiTouch=!1,this.touchStartX=0,this.touchStartY=0,this.touchMoved=!1,this.lastTapTime=0,this.lastTouchHandledAt=0,this.doubleTapThresholdMs=300,this.tapMoveThresholdPx=12,this.reticleHoldMs=420,this.app=t.app,this.sceneRoot=t.sceneRoot,this.canvas=t.canvas,this.minScale=t.minScale??.1,this.maxScale=t.maxScale??5,this.tapMode=t.tapMode??"single",this.shouldIgnoreTap=t.shouldIgnoreTap,this.onPlaced=t.onPlaced,this.boundTouchStart=this.handleTouchStart.bind(this),this.boundTouchMove=this.handleTouchMove.bind(this),this.boundTouchEnd=this.handleTouchEnd.bind(this),this.boundClick=this.handleClick.bind(this),this.boundDblClick=this.handleDblClick.bind(this)}start(){this.isPlaced=!1,this.currentScale=1,this.loggedFirstHit=!1,this.trackingGateWarned=!1,this.hitTestErrorCount=0,this.reticleOpacity=0,this.reticleVisibleUntil=0,Le("AR placement controls started",{sceneRootEnabled:this.sceneRoot.enabled,sceneRootPosition:this.sceneRoot.getPosition(),tapMode:this.tapMode}),this.sceneRoot.enabled=!0,this.createReticle(),this.canvas.addEventListener("touchstart",this.boundTouchStart,{passive:!1}),this.canvas.addEventListener("touchmove",this.boundTouchMove,{passive:!1}),this.canvas.addEventListener("touchend",this.boundTouchEnd,{passive:!1}),this.canvas.addEventListener("click",this.boundClick),this.canvas.addEventListener("dblclick",this.boundDblClick),this.app.on("update",this.updateReticlePulse,this)}setTrackingStatus(t){this.trackingStatus=t}setTapMode(t){this.tapMode=t,this.lastTapTime=0}stop(){Le("AR placement controls stopped",{wasPlaced:this.isPlaced,sceneRootPosition:this.sceneRoot.getPosition(),sceneRootScale:this.sceneRoot.getLocalScale()}),this.canvas.removeEventListener("touchstart",this.boundTouchStart),this.canvas.removeEventListener("touchmove",this.boundTouchMove),this.canvas.removeEventListener("touchend",this.boundTouchEnd),this.canvas.removeEventListener("click",this.boundClick),this.canvas.removeEventListener("dblclick",this.boundDblClick),this.app.off("update",this.updateReticlePulse,this),this.reticle&&(this.reticle.destroy(),this.reticle=null),this.reticleMat?.destroy(),this.reticleMat=null,this.reticleMesh?.destroy(),this.reticleMesh=null}createReticle(){this.reticle=new t.Entity("ar-reticle");const e=new t.StandardMaterial;e.emissive=new t.Color(1,1,1),e.diffuse=new t.Color(0,0,0),e.useLighting=!1,e.opacity=1,e.blendType=t.BLEND_NORMAL,e.depthTest=!1,e.depthWrite=!1,e.cull=t.CULLFACE_NONE,e.update(),this.reticleMat=e;const n=new t.TorusGeometry({tubeRadius:.048,ringRadius:.193,segments:24,sides:10});this.reticleMesh=t.Mesh.fromGeometry(this.app.graphicsDevice,n);const o=new t.MeshInstance(this.reticleMesh,e);o.drawOrder=9999,this.reticle.addComponent("render",{meshInstances:[o],castShadows:!1}),this.reticle.setLocalScale(.72,.32,.72),this.reticle.enabled=!1,this.app.root.addChild(this.reticle)}updateReticlePulse(t){if(!this.reticle||!this.reticleMat)return;const e=performance.now()<this.reticleVisibleUntil?1:0,n=e>this.reticleOpacity?16:5;this.reticleOpacity+=(e-this.reticleOpacity)*Math.min(1,t*n),Math.abs(this.reticleOpacity-e)<.01&&(this.reticleOpacity=e),this.reticleOpacity>0?(this.reticleMat.opacity=.85*this.reticleOpacity,this.reticleMat.update(),this.reticle.enabled=!0):this.reticle.enabled=!1}getHitAtClientPoint(t,e){const n=window.XR8;if(!n)return null;if("NORMAL"!==this.trackingStatus)return this.trackingGateWarned||(this.trackingGateWarned=!0,Re("AR placement tap ignored until XR8 tracking is NORMAL",{status:this.trackingStatus})),null;const o=this.canvas.getBoundingClientRect();if(o.width<=0||o.height<=0)return null;const i=Math.max(0,Math.min(1,(t-o.left)/o.width)),s=Math.max(0,Math.min(1,(e-o.top)/o.height));try{const t=n.XrController.hitTest(i,s,["FEATURE_POINT","ESTIMATED_SURFACE"])[0]??null;return t&&!this.loggedFirstHit&&(this.loggedFirstHit=!0,Ae("AR placement hit test found first surface",{type:t.type,position:t.position})),t}catch(t){return this.hitTestErrorCount+=1,this.hitTestErrorCount<=3&&Re("XR8 hitTest threw; tap placement skipped",t),null}}shouldSkipTap(t,e){return"none"===this.tapMode||!!this.shouldIgnoreTap?.(t,e)}handleClick(t){"single"===this.tapMode&&(Date.now()-this.lastTouchHandledAt<500||this.placeAtClientPoint(t.clientX,t.clientY))}handleDblClick(t){"double"===this.tapMode&&(Date.now()-this.lastTouchHandledAt<500||this.placeAtClientPoint(t.clientX,t.clientY))}handleTouchStart(t){t.touches.length>1?(this.wasMultiTouch=!0,2===t.touches.length&&(this.isPlaced&&t.preventDefault(),this.isTwoFingerGesture=!0,this.lastPinchDistance=this.getTouchDistance(t.touches),this.lastRotationAngle=this.getTouchAngle(t.touches))):1===t.touches.length&&(this.touchStartX=t.touches[0].clientX,this.touchStartY=t.touches[0].clientY,this.touchMoved=!1)}handleTouchMove(t){if(1===t.touches.length&&!this.isTwoFingerGesture){const e=t.touches[0].clientX-this.touchStartX,n=t.touches[0].clientY-this.touchStartY;return void(e*e+n*n>this.tapMoveThresholdPx*this.tapMoveThresholdPx&&(this.touchMoved=!0))}if(!this.isPlaced||2!==t.touches.length||!this.isTwoFingerGesture)return;t.preventDefault();const e=this.getTouchDistance(t.touches);if(this.lastPinchDistance>0){const t=e/this.lastPinchDistance;this.currentScale=Math.max(this.minScale,Math.min(this.maxScale,this.currentScale*t)),this.sceneRoot.setLocalScale(this.currentScale,this.currentScale,this.currentScale)}this.lastPinchDistance=e;const n=this.getTouchAngle(t.touches),o=n-this.lastRotationAngle;Math.abs(o)<Math.PI&&this.sceneRoot.rotateLocal(0,-o*(180/Math.PI),0),this.lastRotationAngle=n}handleTouchEnd(t){if(t.touches.length<2&&(this.isTwoFingerGesture=!1,this.lastPinchDistance=0),this.wasMultiTouch)return 0===t.touches.length&&(this.wasMultiTouch=!1),void(this.lastTapTime=0);if(1!==t.changedTouches.length||this.touchMoved)return;const e=t.changedTouches[0];if(!this.shouldSkipTap(e.clientX,e.clientY)){if("single"===this.tapMode)return t.preventDefault(),this.lastTouchHandledAt=Date.now(),void this.placeAtClientPoint(e.clientX,e.clientY);if("double"===this.tapMode){const n=Date.now();n-this.lastTapTime<this.doubleTapThresholdMs?(t.preventDefault(),this.lastTouchHandledAt=n,this.lastTapTime=0,this.placeAtClientPoint(e.clientX,e.clientY)):this.lastTapTime=n}}}placeAtClientPoint(e,n){if(this.shouldSkipTap(e,n))return;const o=this.getHitAtClientPoint(e,n);if(!o)return;this.isPlaced=!0;const i=new t.Vec3(o.position.x,o.position.y,o.position.z);this.sceneRoot.setPosition(i.x,i.y,i.z),this.sceneRoot.setLocalScale(this.currentScale,this.currentScale,this.currentScale),this.sceneRoot.enabled=!0,this.showReticleAtHit(o),this.onPlaced?.(i.clone()),Le("AR scene placed/moved",{position:{x:i.x,y:i.y,z:i.z},scale:this.currentScale,tapMode:this.tapMode})}showReticleAtHit(t){this.reticle&&this.reticleMat&&(this.reticle.setPosition(t.position.x,t.position.y,t.position.z),t.rotation?this.reticle.setRotation(t.rotation.x,t.rotation.y,t.rotation.z,t.rotation.w):this.reticle.setEulerAngles(0,0,0),this.reticleOpacity=1,this.reticleVisibleUntil=performance.now()+this.reticleHoldMs,this.reticleMat.opacity=.85,this.reticleMat.update(),this.reticle.enabled=!0)}getTouchDistance(t){const e=t[0].clientX-t[1].clientX,n=t[0].clientY-t[1].clientY;return Math.sqrt(e*e+n*n)}getTouchAngle(t){return Math.atan2(t[1].clientY-t[0].clientY,t[1].clientX-t[0].clientX)}}const $e=t=>({x:t.x,y:t.y,z:t.z}),Ue=(t,e)=>({x:t.x+e.x,y:t.y+e.y,z:t.z+e.z}),Oe=(t,e)=>({x:t.x-e.x,y:t.y-e.y,z:t.z-e.z}),Ne=(t,e)=>({x:t.x*e,y:t.y*e,z:t.z*e});function We(t){return!0===t||"loop"===t}function He(t,e){return t.length<2?0:We(e)?t.length:t.length-1}function Ge(t,e,n){const o=t.length,i=He(t,n);if(o<2||e<0||e>=i)return null;const s=We(n),a=t=>(t%o+o)%o,r=s?a(e):e,l=s?a(e+1):e+1;return{prevIndex:s?a(e-1):Math.max(e-1,0),startIndex:r,endIndex:l,nextIndex:s?a(e+2):Math.min(e+2,o-1)}}function je(t,e,n){const o=Ge(t,e,n);if(!o)return null;const i=t[o.prevIndex].position,s=t[o.startIndex].position,a=t[o.endIndex].position,r=t[o.nextIndex].position;return{handleOut:Ne(Oe(a,i),1/6),handleInNext:Ne(Oe(s,r),1/6)}}function Xe(t,e,n){const o=Ge(t,e,n);return!!o&&"bezier"===t[o.startIndex].curveOut?.mode}function qe(t,e,n){const o=Ge(t,e,n);if(!o)return null;const i=je(t,e,n);if(!i)return null;const s=t[o.startIndex].curveOut;return"bezier"!==s?.mode?i:{handleOut:s.handleOut?$e(s.handleOut):i.handleOut,handleInNext:s.handleInNext?$e(s.handleInNext):i.handleInNext}}function Ye(t,e,n,o,i){const s=i*i,a=s*i;return{x:.5*(2*e.x+(-t.x+n.x)*i+(2*t.x-5*e.x+4*n.x-o.x)*s+(-t.x+3*e.x-3*n.x+o.x)*a),y:.5*(2*e.y+(-t.y+n.y)*i+(2*t.y-5*e.y+4*n.y-o.y)*s+(-t.y+3*e.y-3*n.y+o.y)*a),z:.5*(2*e.z+(-t.z+n.z)*i+(2*t.z-5*e.z+4*n.z-o.z)*s+(-t.z+3*e.z-3*n.z+o.z)*a)}}function Ze(t,e,n,o,i){const s=1-i,a=s*s,r=a*s,l=i*i,c=l*i;return{x:r*t.x+3*a*i*e.x+3*s*l*n.x+c*o.x,y:r*t.y+3*a*i*e.y+3*s*l*n.y+c*o.y,z:r*t.z+3*a*i*e.z+3*s*l*n.z+c*o.z}}function Ke(t,e,n,o){const i=Ge(t,e,o);if(!i)return null;const s=t[i.prevIndex].position,a=t[i.startIndex].position,r=t[i.endIndex].position,l=t[i.nextIndex].position;if(!Xe(t,e,o))return Ye(s,a,r,l,n);const c=qe(t,e,o);return c?Ze(a,Ue(a,c.handleOut),Ue(r,c.handleInNext),r,n):null}function Qe(t,e,n){if(t.length<2)return t.map((t,e)=>({...$e(t.position),segmentIndex:e,t:0}));const o=[],i=He(t,n),s=Math.max(1,e);for(let e=0;e<i;e++)for(let i=0;i<s;i++){const a=i/s,r=Ke(t,e,a,n);r&&o.push({...r,segmentIndex:e,t:a})}const a=We(n)?0:t.length-1;return o.push({...$e(t[a].position),segmentIndex:Math.max(0,i-1),t:1}),o}class Je extends e{static scriptName="xrControllers";basePath="https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets/dist/profiles";controllers=new Map;_pendingInputSources=new Set;_visible=!0;_handlers=null;initialize(){this.app.xr?(this._handlers={onAdd:this._onInputSourceAdd.bind(this),onRemove:this._onInputSourceRemove.bind(this),onXrEnd:this._onXrEnd.bind(this)},this.app.xr.input.on("add",this._handlers.onAdd),this.app.xr.input.on("remove",this._handlers.onRemove),this.app.xr.on("end",this._handlers.onXrEnd),this.once("destroy",()=>{this._onDestroy()})):console.error("XrControllers script requires XR to be enabled on the application")}_onDestroy(){this._handlers&&this.app.xr&&(this.app.xr.input.off("add",this._handlers.onAdd),this.app.xr.input.off("remove",this._handlers.onRemove),this.app.xr.off("end",this._handlers.onXrEnd)),this._destroyAllControllers(),this._handlers=null,this._pendingInputSources.clear()}_onXrEnd(){this._destroyAllControllers(),this._pendingInputSources.clear()}_destroyController(t){const e=this.controllers.get(t);e&&(e.entity.destroy(),e.asset&&(this.app.assets.remove(e.asset),e.asset.unload()),this.controllers.delete(t),this.app.fire("xr:controller:remove",t))}_destroyAllControllers(){for(const t of this.controllers.keys())this._destroyController(t)}async _tryLoadProfiles(t,e,n=0){if(n>=e.length)return null;if(!this._pendingInputSources.has(t))return null;const o=await this._loadProfile(t,e[n]);return o||this._tryLoadProfiles(t,e,n+1)}async _onInputSourceAdd(t){if(!t.profiles?.length)return void console.warn("XrControllers: No profiles available for input source");this._pendingInputSources.add(t);const e=await this._tryLoadProfiles(t,t.profiles);if(this._pendingInputSources.has(t))if(this._pendingInputSources.delete(t),e){const{asset:n}=e,o=n.resource.instantiateRenderEntity();this.app.root.addChild(o),o.enabled=this._visible;const i=new Map;if(t.hand)for(const e of t.hand.joints){const t=o.findByName(e.id);t&&i.set(e,t)}this.controllers.set(t,{entity:o,jointMap:i,asset:n}),this.app.fire("xr:controller:add",t,o)}else console.warn("XrControllers: No compatible profiles found for input source");else e?.asset&&(this.app.assets.remove(e.asset),e.asset.unload())}async _loadProfile(t,e){const n=`${this.basePath}/${e}/profile.json`;try{const o=await fetch(n);if(!o.ok)return null;const i=await o.json(),s=i.layouts[t.handedness]?.assetPath||"",a=`${this.basePath}/${i.profileId}/${t.handedness}${s.replace(/^\/?(left|right)/,"")}`,r=await new Promise((t,e)=>{this.app.assets.loadFromUrl(a,"container",(n,o)=>{n?e(n):t(o)})});return{profileId:e,asset:r}}catch(t){return null}}_onInputSourceRemove(t){this._pendingInputSources.delete(t),this._destroyController(t)}set visible(t){if(this._visible!==t){this._visible=t;for(const[,e]of this.controllers)e.entity.enabled=t}}get visible(){return this._visible}update(t){if(this.app.xr?.active&&this._visible)for(const[t,{entity:e,jointMap:n}]of this.controllers)if(t.hand)for(const[t,e]of n)e.setPosition(t.getPosition()),e.setRotation(t.getRotation());else{const n=t.getPosition(),o=t.getRotation();n&&e.setPosition(n),o&&e.setRotation(o)}}}class tn extends e{static scriptName="xrNavigation";enableTeleport=!0;enableMove=!0;movementSpeed=1.5;rotateSpeed=45;movementThreshold=.1;rotateThreshold=.5;rotateResetThreshold=.25;maxTeleportDistance=10;teleportIndicatorRadius=.2;teleportIndicatorSegments=16;validTeleportColor=new n(0,1,0);invalidTeleportColor=new n(1,0,0);controllerRayColor=new n(1,1,1);enableSnapVertical=!0;snapVerticalHeight=.5;snapVerticalBoostHeight=2;snapVerticalThreshold=.5;snapVerticalResetThreshold=.25;inputSources=new Set;activePointers=new Map;inputHandlers=new Map;lastRotateValue=0;lastVerticalValue=0;tmpVec2A=new o;tmpVec2B=new o;tmpVec3A=new i;tmpVec3B=new i;validColor=new n;invalidColor=new n;rayColor=new n;cameraEntity=null;initialize(){if(!this.app.xr)return void console.error("XrNavigation script requires XR to be enabled on the application");const t=[];this.enableTeleport&&t.push("teleportation"),this.enableMove&&t.push("smooth movement"),this.enableSnapVertical&&t.push("snap vertical"),console.log(`XrNavigation: Enabled methods - ${t.join(", ")}`),this.enableTeleport||this.enableMove||this.enableSnapVertical||console.warn("XrNavigation: All navigation methods are disabled. Navigation will not work."),this.validColor.copy(this.validTeleportColor),this.invalidColor.copy(this.invalidTeleportColor),this.rayColor.copy(this.controllerRayColor);const e=this.entity.findComponent("camera");if(this.cameraEntity=e?e.entity:null,!this.cameraEntity){console.warn("XrNavigation: Camera entity not found. Looking for camera in children...");const t=this.entity.findByName("camera");if(this.cameraEntity=t,!this.cameraEntity)for(const t of this.entity.children){const e=t;if(e.camera){this.cameraEntity=e;break}}this.cameraEntity||console.error("XrNavigation: No camera entity found. Movement calculations may not work correctly.")}this.app.xr.input.on("add",t=>{const e=()=>{this.activePointers.set(t,!0)},n=()=>{this.activePointers.set(t,!1),this.tryTeleport(t)};t.on("selectstart",e),t.on("selectend",n),this.inputHandlers.set(t,{handleSelectStart:e,handleSelectEnd:n}),this.inputSources.add(t)}),this.app.xr.input.on("remove",t=>{const e=this.inputHandlers.get(t);e&&(t.off("selectstart",e.handleSelectStart),t.off("selectend",e.handleSelectEnd),this.inputHandlers.delete(t)),this.activePointers.delete(t),this.inputSources.delete(t)})}findPlaneIntersection(t,e){if(Math.abs(e.y)<1e-5)return null;const n=-t.y/e.y;return n<0?null:new i(t.x+e.x*n,0,t.z+e.z*n)}tryTeleport(t){const e=t.getOrigin(),n=t.getDirection(),o=this.findPlaneIntersection(e,n);if(o){if(this.cameraEntity){const t=this.cameraEntity.getLocalPosition();o.x-=t.x,o.z-=t.z}const t=this.entity.getPosition().y;o.y=t,this.entity.setPosition(o)}}update(t){this.enableMove&&this.handleSmoothLocomotion(t),this.enableSnapVertical&&this.handleSnapVertical(),this.enableTeleport&&this.handleTeleportation(),this.renderControllerRays()}handleSmoothLocomotion(t){if(this.cameraEntity)for(const e of this.inputSources)if(e.gamepad)if("left"===e.handedness){if(this.tmpVec2A.set(e.gamepad.axes[2],e.gamepad.axes[3]),this.tmpVec2A.length()>this.movementThreshold){this.tmpVec2A.normalize();const e=this.cameraEntity.forward;this.tmpVec2B.x=e.x,this.tmpVec2B.y=e.z,this.tmpVec2B.normalize();const n=Math.atan2(this.tmpVec2B.x,this.tmpVec2B.y)-Math.PI/2,o=this.tmpVec2A.x*Math.sin(n)-this.tmpVec2A.y*Math.cos(n);this.tmpVec2A.y=this.tmpVec2A.y*Math.sin(n)+this.tmpVec2A.x*Math.cos(n),this.tmpVec2A.x=o,this.tmpVec2A.mulScalar(this.movementSpeed*t),this.entity.translate(this.tmpVec2A.x,0,this.tmpVec2A.y)}}else"right"===e.handedness&&this.handleSnapTurning(e)}handleSnapTurning(t){const e=-t.gamepad.axes[2];(this.lastRotateValue>0&&e<this.rotateResetThreshold||this.lastRotateValue<0&&e>-this.rotateResetThreshold)&&(this.lastRotateValue=0),0===this.lastRotateValue&&Math.abs(e)>this.rotateThreshold&&(this.lastRotateValue=Math.sign(e),this.cameraEntity&&(this.tmpVec3A.copy(this.cameraEntity.getLocalPosition()),this.entity.translateLocal(this.tmpVec3A),this.entity.rotateLocal(0,Math.sign(e)*this.rotateSpeed,0),this.entity.translateLocal(this.tmpVec3A.mulScalar(-1))))}handleSnapVertical(){let t=null;for(const e of this.inputSources)if(e.gamepad&&"right"===e.handedness){t=e;break}if(!t||!t.gamepad)return;const e=-t.gamepad.axes[3];if((this.lastVerticalValue>0&&e<this.snapVerticalResetThreshold||this.lastVerticalValue<0&&e>-this.snapVerticalResetThreshold)&&(this.lastVerticalValue=0),0===this.lastVerticalValue&&Math.abs(e)>this.snapVerticalThreshold){this.lastVerticalValue=Math.sign(e);const n=t.gamepad.buttons[1]?.pressed,o=n?this.snapVerticalBoostHeight:this.snapVerticalHeight;this.entity.translate(0,Math.sign(e)*o,0)}}handleTeleportation(){for(const t of this.inputSources){if(!this.activePointers.get(t))continue;const e=t.getOrigin(),n=t.getDirection(),o=this.findPlaneIntersection(e,n);o&&this.isValidTeleportDistance(o)?(this.app.drawLine(e,o,this.validColor),this.drawTeleportIndicator(o)):(this.tmpVec3B.copy(n).mulScalar(this.maxTeleportDistance).add(e),this.app.drawLine(e,this.tmpVec3B,this.invalidColor))}}renderControllerRays(){if(this.enableMove)for(const t of this.inputSources){if(this.activePointers.get(t))continue;const e=t.getOrigin();this.tmpVec3B.copy(t.getDirection()).mulScalar(2).add(e),this.app.drawLine(e,this.tmpVec3B,this.rayColor)}}isValidTeleportDistance(t){return t.distance(this.entity.getPosition())<=this.maxTeleportDistance}drawTeleportIndicator(t){const e=this.teleportIndicatorSegments,n=this.teleportIndicatorRadius;for(let o=0;o<e;o++){const i=o/e*Math.PI*2,s=(o+1)/e*Math.PI*2,a=t.x+Math.cos(i)*n,r=t.z+Math.sin(i)*n,l=t.x+Math.cos(s)*n,c=t.z+Math.sin(s)*n;this.tmpVec3A.set(a,.01,r),this.tmpVec3B.set(l,.01,c),this.app.drawLine(this.tmpVec3A,this.tmpVec3B,this.validColor)}}}const en={auto:t.GSPLAT_RENDERER_AUTO??0,"raster-cpu":t.GSPLAT_RENDERER_RASTER_CPU_SORT??1,"raster-gpu":t.GSPLAT_RENDERER_RASTER_GPU_SORT??2,compute:t.GSPLAT_RENDERER_COMPUTE??3},nn=Object.entries(en).reduce((t,[e,n])=>(t[String(n)]=e,t),{});function on(t,e,n){const o=t.scene.gsplat,i=o?.currentRenderer,s=nn[String(i)]??`unknown(${i})`,a=t.graphicsDevice,r=!0===a?.isWebGPU,l=r?"webgpu":a?.isWebGL2?"webgl2":a?.isWebGL1?"webgl1":"unknown",c=!!n&&"auto"!==n&&n!==s;console.log(`[SPLAT][RENDERER] ${e}`,{requested:n??"(none — engine default)",active:s,activeRaw:i,fellBack:c,device:l,webgpu:r,gpu:a?.deviceName??a?.gpuAdapter?.name??"unknown",mobile:"undefined"!=typeof navigator&&/Mobi|Android|iPhone|iPad/.test(navigator.userAgent)})}function sn(t){if(t)return{mode:t.mode,handleOut:t.handleOut?{x:t.handleOut.x,y:t.handleOut.y,z:-t.handleOut.z}:void 0,handleInNext:t.handleInNext?{x:t.handleInNext.x,y:t.handleInNext.y,z:-t.handleInNext.z}:void 0}}function an(t,e){return t.map((t,n)=>({position:t,curveOut:sn(e?.[n]?.curveOut)}))}class rn{constructor(){this.listeners=new Map,this.internalListeners=new Map}on(t,e){this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(e)}onInternal(t,e){this.internalListeners.has(t)||this.internalListeners.set(t,new Set),this.internalListeners.get(t).add(e)}off(t,e){this.listeners.get(t)?.delete(e),this.internalListeners.get(t)?.delete(e)}emit(t,...e){this.listeners.get(t)?.forEach(t=>t(...e)),this.internalListeners.get(t)?.forEach(t=>t(...e))}listenerCount(t){return this.listeners.get(t)?.size||0}}function ln(){const t=navigator.userAgent||navigator.vendor||window.opera||"";return/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(t)}const cn={"desktop-max":{range:[0,5],lodBaseDistance:15,lodMultiplier:2},desktop:{range:[0,2],lodBaseDistance:15,lodMultiplier:2},"mobile-max":{range:[1,2],lodBaseDistance:15,lodMultiplier:2},mobile:{range:[2,5],lodBaseDistance:15,lodMultiplier:2}};function dn(t){const e=navigator.deviceMemory,n=navigator.hardwareConcurrency||4,o=window.devicePixelRatio||1;let i;if(t){i=null!=e&&e>=6&&n>=6||null==e&&n>=6&&o>=3?"mobile-max":"mobile"}else{i=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}function pn(t){const e=t.includes("lod-meta.json");return e&&console.log("[SPLAT] Detected LOD streaming format (lod-meta.json)"),e}function hn(e,n,o={}){if(console.log("[StorySplat Viewer] BUILD Apr 28, 22:40:57"),o.lazyLoad??n.uiOptions?.lazyLoad){let dl,pl=null;const hl=[],ul=o.lazyLoadThumbnail||n.uiOptions?.lazyLoadThumbnailUrl||n.thumbnailUrl,ml=o.lazyLoadButtonText||n.uiOptions?.lazyLoadButtonText||x(n.uiOptions?.buttonLabels,"startExperience"),gl=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;M(s,"minimal",void 0,t.id),t.classList.add("storysplat-viewer-container");const r=document.createElement("div");r.className="storysplat-lazy-load-container";const l=(n?j(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:ul,thumbnailType:o.lazyLoadThumbnailType||n.uiOptions?.lazyLoadThumbnailType,buttonText:ml,uiColor:gl,onStart:()=>{console.log("[StorySplat Viewer] User clicked start, initializing viewer..."),pl=hn(e,n,{...o,lazyLoad:!1}),dl&&(pl.setButtonLabels(dl),dl=void 0);for(const{event:t,callback:e,kind:n}of hl)"onInternal"===n&&pl.onInternal?pl.onInternal(t,e):pl.on(t,e);hl.length=0}});return{app:null,canvas:null,goToWaypoint:t=>pl?.goToWaypoint(t),nextWaypoint:()=>pl?.nextWaypoint(),prevWaypoint:()=>pl?.prevWaypoint(),getCurrentWaypointIndex:()=>pl?.getCurrentWaypointIndex()??0,getWaypointCount:()=>pl?.getWaypointCount()??0,setPosition:(t,e,n)=>pl?.setPosition(t,e,n),setRotation:(t,e,n)=>pl?.setRotation(t,e,n),getPosition:()=>pl?.getPosition()??{x:0,y:0,z:0},getRotation:()=>pl?.getRotation()??{x:0,y:0,z:0},capturePhoto:()=>pl?pl.capturePhoto():Promise.reject(new Error("[StorySplat Viewer] Viewer has not started yet.")),shareCurrentView:()=>pl?pl.shareCurrentView():Promise.reject(new Error("[StorySplat Viewer] Viewer has not started yet.")),play:()=>pl?.play(),pause:()=>pl?.pause(),stop:()=>pl?.stop(),isPlaying:()=>pl?.isPlaying()??!1,isFrameSequencePlaying:()=>pl?.isFrameSequencePlaying()??!1,setCameraMode:t=>pl?.setCameraMode(t),getCameraMode:()=>pl?.getCameraMode()??"tour",resumeTourAtCurrentCamera:()=>pl?.resumeTourAtCurrentCamera(),setExploreMode:t=>pl?.setExploreMode(t),setExploreModeSettings:t=>{pl?pl.setExploreModeSettings(t):(void 0!==t.enabledExploreModes&&(n.enabledExploreModes=t.enabledExploreModes??void 0),void 0!==t.initialSplatExploreMode&&(n.initialSplatExploreMode=t.initialSplatExploreMode??void 0))},goToOriginalSplat:()=>pl?.goToOriginalSplat(),goToSplat:async t=>pl?.goToSplat(t),getCurrentSplatUrl:()=>pl?.getCurrentSplatUrl()??"",isShowingOriginalSplat:()=>pl?.isShowingOriginalSplat()??!0,getAdditionalSplats:()=>pl?.getAdditionalSplats()??[],isCompareMode:()=>pl?.isCompareMode()??!1,getComparePosition:()=>pl?.getComparePosition()??.5,setComparePosition:t=>pl?.setComparePosition(t),setProgress:(t,e)=>pl?.setProgress(t,e),getProgress:()=>pl?.getProgress()??0,muteAll:()=>pl?.muteAll(),unmuteAll:()=>pl?.unmuteAll(),isMuted:()=>pl?.isMuted()??!1,getHotspots:()=>pl?.getHotspots()??[],triggerHotspot:t=>pl?.triggerHotspot(t),closeHotspot:()=>pl?.closeHotspot(),destroy:()=>{pl?pl.destroy():(e.querySelectorAll(".storysplat-lazy-load-container, .storysplat-viewer-container").forEach(t=>t.remove()),e.classList.remove("storysplat-viewer-container"))},resize:()=>pl?.resize(),navigateToScene:async t=>{if(pl)return pl.navigateToScene(t)},setButtonLabels:t=>{pl?pl.setButtonLabels(t):dl={...dl,...t}},setPanoramaMode:t=>pl?.setPanoramaMode(t),isPanoramaMode:()=>pl?.isPanoramaMode()??!1,on:(t,e)=>{pl?pl.on(t,e):hl.push({event:t,callback:e,kind:"on"})},off:(t,e)=>{if(pl)pl.off(t,e);else{const n=hl.findIndex(n=>n.event===t&&n.callback===e);-1!==n&&hl.splice(n,1)}},onInternal:(t,e)=>{pl&&pl.onInternal?pl.onInternal(t,e):hl.push({event:t,callback:e,kind:"onInternal"})}}}const i=new rn;let s=o.__sessionRecorderHandle??null;if(!o.allowParentStyles){const fl=e.style.width,yl=e.style.height;e.style.all="initial",e.style.position="relative",e.style.display="block",e.style.width=fl||"100%",e.style.height=yl||"100%",e.style.overflow="hidden",e.style.fontFamily="system-ui, -apple-system, sans-serif"}const a=!0===o.editor;a&&e.setAttribute("data-storysplat-editor","true"),i.on("error",t=>{console.error("[StorySplat Viewer] Error event:",t.message),a||function(t,e){if(t.hasAttribute("data-storysplat-editor"))return;t.querySelector(".storysplat-error-popup")?.remove();const n=t.querySelector(".storysplat-preloader");if(n&&P(n),e.includes("Failed to load splat from any URL")){const e=document.createElement("div");e.className="storysplat-outdated-banner",e.style.cssText='position:absolute;top:0;left:0;right:0;z-index:10000;display:flex;align-items:center;justify-content:center;gap:12px;padding:10px 20px;background:rgba(204,88,51,0.9);color:white;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:500;backdrop-filter:blur(8px);';const n=document.createElement("span");n.textContent="⚠️ This scene was created with an older version of StorySplat. Some features may not be available.",e.appendChild(n);const o=document.createElement("button");return o.textContent="Dismiss",o.style.cssText="background:rgba(255,255,255,0.2);border:1px solid rgba(255,255,255,0.3);color:white;padding:4px 12px;border-radius:4px;cursor:pointer;font-size:12px;font-weight:600;white-space:nowrap;",o.addEventListener("click",()=>e.remove()),e.appendChild(o),setTimeout(()=>{e.parentNode&&e.remove()},8e3),void t.appendChild(e)}const o=document.createElement("div");o.className="storysplat-error-popup";const i=document.createElement("div");i.className="storysplat-error-popup-icon",i.textContent="❌",o.appendChild(i);const s=document.createElement("h3");s.className="storysplat-error-popup-title",s.textContent="Failed to Load Scene",o.appendChild(s);const a=document.createElement("p");a.className="storysplat-error-popup-message",a.textContent=e||"An error occurred while loading this scene. Please try refreshing the page.",o.appendChild(a),t.appendChild(o)}(e,t.message)});const r=v(y(n));console.log("[StorySplat Viewer] Creating viewer with config:",r),console.log("[StorySplat Viewer] Scale config:",r.scale),console.log("[StorySplat Viewer] Raw scene data:",{splatScale:n.splatScale,scale:n.scale});let l=null;const c=a?!o.editorSkipUI&&!1!==o.showUI:!1!==o.showUI,d=r.uiColor||"#CC5833",p=r.uiOptions||{},h=o.template||p.uiType||"minimal";let u=p.buttonLabels;const m=t=>"first-person"===t?"tour":"drone"===t?"explore":t,g=r.collisionMeshesData&&r.collisionMeshesData.length>0||!!r.voxelCollisionUrl,f=Array.isArray(r.allowedCameraModes);let w=(r.allowedCameraModes||["orbit","first-person","drone"]).map(m).filter((t,e,n)=>n.indexOf(t)===e);f||!g||w.includes("walk")||w.push("walk"),!g&&w.includes("walk")&&(w=w.filter(t=>"walk"!==t));let S=m(r.defaultCameraMode||"orbit");w.includes(S)||(S=w[0]||"tour");const _=t=>{const e=(t?.length?t:["orbit","fly"]).filter(t=>"orbit"===t||"fly"===t).filter((t,e,n)=>n.indexOf(t)===e);return e.length>0?e:["orbit","fly"]};let C=_(r.enabledExploreModes);const E=t=>C.includes(t),L=t=>t&&E(t)?t:C.includes("fly")?"fly":C[0],R=!(!r.narrationTrack?.enabled||!r.narrationTrack?.audioUrl),z=!!(R||r.audioEmitters?.length||r.hotspots?.some(t=>t.audioUrl)||r.hotspots?.some(t=>"video"===t.type&&!0!==t.videoMuted)||r.waypoints?.some(t=>t.interactions?.some(t=>"audio"===t.type)));let D={};c&&(D=T(e,r,{uiColor:d,showScrollControls:!p.hideNavigator&&r.waypoints&&r.waypoints.length>0,showModeToggle:w.length>1,showFullscreenButton:!p.hideFullscreenButton,showHelpButton:!p.hideHelpButton&&!p.hideInfoButton,showMuteButton:!p.hideMuteButton&&z,showPreloader:!0,allowedCameraModes:w,enabledExploreModes:C,defaultCameraMode:S,buttonLabels:u,customPreloaderLogoUrl:p.customPreloaderLogoUrl,preloaderLogoSize:p.preloaderLogoSize,hideWatermark:p.hideWatermark,watermarkText:p.watermarkText,watermarkLink:p.watermarkLink,watermarkImageUrl:p.watermarkImageUrl,watermarkImageHeight:p.watermarkImageHeight,sceneId:n.sceneId,showWaypointList:p.showWaypointList,template:h,debugMode:p.debugMode,hideProgressText:p.hideProgressText,viewerTheme:p.viewerTheme,showRelightingToggle:!!(!1!==r.splatRelighting?.enabled&&r.splatRelighting?.allowViewerToggle&&r.lights&&r.lights.length>0),showFisheyeButton:!1,showSceneMenu:!!(r.portals&&r.portals.length>0||r.uiOptions?.sceneMenuLinks&&r.uiOptions.sceneMenuLinks.length>0),sceneMenuLinks:r.uiOptions?.sceneMenuLinks,measurementsEnabled:r.uiOptions?.measurementsEnabled,sceneScale:r.uiOptions?.sceneScale,sceneScaleUnit:r.uiOptions?.sceneScaleUnit,measurementColor:r.uiOptions?.measurementColor,showShareButton:!p.hideShareButton}),W(D));const I=document.createElement("canvas");let U;I.id="storysplat-viewer-canvas",I.style.width="100%",I.style.height="100%",I.style.display="block",e.appendChild(I);const X={antialias:!1,alpha:!1,powerPreference:"high-performance"};try{U=new t.Application(I,{graphicsDeviceOptions:X,mouse:new t.Mouse(I),touch:new t.TouchDevice(I),keyboard:new t.Keyboard(window)}),console.log("[StorySplat Viewer] Graphics initialized successfully")}catch(vl){console.warn("[StorySplat Viewer] WebGL2 initialization failed, trying WebGL1 fallback:",vl);try{U=new t.Application(I,{graphicsDeviceOptions:{...X,preferWebGl2:!1},mouse:new t.Mouse(I),touch:new t.TouchDevice(I),keyboard:new t.Keyboard(window)}),console.log("[StorySplat Viewer] WebGL1 fallback successful")}catch(bl){console.error("[StorySplat Viewer] WebGL initialization failed completely:",bl);const xl=document.createElement("div");xl.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 wl=document.createElement("h3");wl.style.cssText="margin:0 0 10px 0;",wl.textContent=x(u,"errorWebGLTitle");const Sl=document.createElement("p");throw Sl.style.cssText="margin:0;",Sl.textContent=x(u,"errorWebGLMessage"),xl.appendChild(wl),xl.appendChild(Sl),e.appendChild(xl),new Error("WebGL initialization failed - browser may not support WebGL")}}I.addEventListener("webglcontextlost",t=>{t.preventDefault(),console.error("[StorySplat Viewer] WebGL context lost"),i.emit("error",new Error("WebGL context lost"))},!1),I.addEventListener("webglcontextrestored",()=>{console.log("[StorySplat Viewer] WebGL context restored")},!1),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 _l=r.skybox?.url||r.skyboxUrl;if(_l){const Cl=new Image;Cl.crossOrigin="anonymous",Cl.src=_l,console.log("[StorySplat Viewer] Preloading skybox image in parallel with splat:",_l)}}catch(Ml){console.warn("[StorySplat Viewer] Skybox preload failed (non-fatal):",Ml)}try{const El=U.scene.layers.getLayerByName("World"),Pl=El&&Object.getPrototypeOf(El);if(Pl&&!Pl._splitLightsPatched){const kl=Object.getOwnPropertyDescriptor(Pl,"splitLights");if(kl?.get){const Tl=kl.get;Object.defineProperty(Pl,"splitLights",{get(){return this._splitLightsDirty&&(this._lights=this._lights.filter(t=>void 0!==t._type&&t._type>=0&&t._type<=2)),Tl.call(this)},configurable:!0}),Pl._splitLightsPatched=!0}}}catch(Al){console.warn("[StorySplat Viewer] Could not patch Layer.splitLights:",Al)}const q=ln(),Y=r.lodSettings;let Z,K,Q,J;if(Y&&"auto"!==Y.preset)if("custom"===Y.preset){K=[Y.lodRangeMin??0,Y.lodRangeMax??5];let Ll=15,Rl=2;Y.lodDistances&&!Y.lodBaseDistance&&(Ll=Y.lodDistances[0]||15,Y.lodDistances[1]&&Y.lodDistances[0]&&(Rl=Y.lodDistances[1]/Y.lodDistances[0])),Q=Y.lodBaseDistance??Ll,J=Y.lodMultiplier??Rl,Z="desktop",console.log("[SPLAT] Using custom LOD settings from scene")}else{Z=Y.preset;const zl=cn[Z];K=[...zl.range],Q=zl.lodBaseDistance,J=zl.lodMultiplier,console.log("[SPLAT] Using scene-configured LOD preset:",Z)}else{q&&Y?.mobilePreset?(Z=Y.mobilePreset,console.log("[SPLAT] Using scene mobile override preset:",Z)):!q&&Y?.desktopPreset?(Z=Y.desktopPreset,console.log("[SPLAT] Using scene desktop override preset:",Z)):Z=dn(q);const Dl=cn[Z];K=[...Dl.range],Q=Dl.lodBaseDistance,J=Dl.lodMultiplier}if(console.log("[SPLAT] Initializing LOD system for device:",q?"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=K[0],U.scene.gsplat.lodRangeMax=K[1],U.scene.gsplat.colorUpdateDistance=1,U.scene.gsplat.colorUpdateAngle=4,U.scene.gsplat.colorUpdateDistanceLodScale=2,U.scene.gsplat.colorUpdateAngleLodScale=2;const Il=Y?.splatBudget??0;Il>0&&(U.scene.gsplat.splatBudget=Il,console.log("[SPLAT] Global splat budget set:",Il));const Fl=Y?.renderer;Fl&&Fl in en&&(U.scene.gsplat.renderer=en[Fl]),on(U,"init",Fl),console.log("[SPLAT] LOD system configured:",{preset:Y?.preset??"auto",lodRangeMin:K[0],lodRangeMax:K[1],lodBaseDistance:Q,lodMultiplier:J,splatBudget:Il,isMobile:q})}else console.warn("[SPLAT] GSplat scene settings not available - LOD may not work optimally");let et=0,nt=!1,ot=null,it=null,st=null,ut=null;const mt=new Set;let gt=!1,ft=!1,vt=!1;const bt=t=>new Promise((e,n)=>{try{t.toBlob(t=>{t?e(t):n(new Error("[StorySplat Viewer] Canvas capture returned no image data."))},"image/jpeg",.92)}catch(t){n(t)}}),_t=async()=>{if(vt)throw new Error("[StorySplat Viewer] Cannot capture after viewer is destroyed.");const t=U.graphicsDevice;if(!t.isWebGPU&&"function"==typeof t.readPixels)try{return await new Promise((e,n)=>{const o=()=>{window.clearTimeout(i);try{const o=Math.max(1,Math.floor(t.width||I.width)),i=Math.max(1,Math.floor(t.height||I.height)),s=new Uint8Array(o*i*4);t.readPixels(0,0,o,i,s),((t,e,n)=>{const o=document.createElement("canvas");o.width=e,o.height=n;const i=o.getContext("2d");if(!i)throw new Error("[StorySplat Viewer] Could not create capture canvas.");const s=i.createImageData(e,n),a=4*e;for(let e=0;e<n;e++){const o=(n-1-e)*a,i=e*a;s.data.set(t.subarray(o,o+a),i)}return i.putImageData(s,0,0),bt(o)})(s,o,i).then(e,n)}catch(t){n(t)}},i=window.setTimeout(()=>{U.off("postrender",o),n(new Error("[StorySplat Viewer] Timed out waiting for a rendered frame."))},5e3);U.once("postrender",o),U.renderNextFrame=!0})}catch(t){console.warn("[StorySplat Viewer] readPixels capture failed, falling back to canvas capture:",t)}return await new Promise((t,e)=>{const n=()=>{window.clearTimeout(o),t()},o=window.setTimeout(()=>{U.off("postrender",n),e(new Error("[StorySplat Viewer] Timed out waiting for a rendered frame."))},5e3);U.once("postrender",n),U.renderNextFrame=!0}),await new Promise(t=>requestAnimationFrame(()=>t())),bt(I)},Et=()=>n.name||"StorySplat Scene",Tt=async()=>{const t=await _t(),n=`storysplat-${Et().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,48)||"scene"}-${(new Date).toISOString().slice(0,10)}.jpg`,o="undefined"!=typeof window?window.location.href:"",i=Et(),s="Share StorySplat",a=`Check out "${i}" on StorySplat.`,r="undefined"!=typeof navigator?navigator:null,l="undefined"!=typeof File?new File([t],n,{type:"image/jpeg"}):null,c=l?[l]:[],d=!!(r?.share&&l&&"function"==typeof r.canShare&&r.canShare({files:c}));!function(t,e){t.querySelector(".storysplat-photo-share-backdrop")?.remove();const{blob:n,fileName:o,shareUrl:i,shareText:s,title:a,buttonLabels:r,sharePhoto:l}=e,c=a||"Share StorySplat",d=s||"Check out this StorySplat.",p=encodeURIComponent(i),h=encodeURIComponent(d),u=encodeURIComponent(c),m=encodeURIComponent(`${d}\n\n${i}`),g=URL.createObjectURL(n);let f=g;const y=document.createElement("div");y.className="storysplat-photo-share-backdrop";const v=document.createElement("div");v.className="storysplat-photo-share-panel",v.setAttribute("role","dialog"),v.setAttribute("aria-modal","true"),v.setAttribute("aria-label",c);const b=document.createElement("div");b.className="storysplat-photo-share-header";const w=document.createElement("span");w.className="storysplat-photo-share-title",w.textContent=c;const S=document.createElement("button");S.className="storysplat-photo-share-close",S.type="button",S.setAttribute("aria-label",x(r,"close")),S.textContent="×",b.appendChild(w),b.appendChild(S);const _=document.createElement("img");_.className="storysplat-photo-share-preview",_.src=g,_.alt=c,_.addEventListener("error",()=>{const t=new FileReader;t.onload=()=>{"string"==typeof t.result&&(_.src=t.result,f&&(URL.revokeObjectURL(f),f=null))},t.readAsDataURL(n)},{once:!0});const C=document.createElement("div");C.className="storysplat-photo-share-socials",[{label:"X / Twitter",url:`https://twitter.com/intent/tweet?text=${h}&url=${p}`},{label:"Email",url:`mailto:?subject=${u}&body=${m}`}].forEach(({label:t,url:e})=>{const n=document.createElement("a");n.className="storysplat-photo-share-social",n.href=e,n.textContent=t,e.startsWith("mailto:")||(n.target="_blank",n.rel="noopener noreferrer"),C.appendChild(n)});const M=document.createElement("div");if(M.className="storysplat-photo-share-actions",l){const e=document.createElement("button");e.className="storysplat-photo-share-action primary",e.type="button",e.textContent=x(r,"share"),e.addEventListener("click",async()=>{e.disabled=!0;try{await l()}catch(e){console.warn("[StorySplat Viewer] Native photo share failed:",e),k(t,x(r,"shareFailed"))}finally{e.disabled=!1}}),M.appendChild(e)}const E=document.createElement("button");E.className="storysplat-photo-share-action"+(l?"":" primary"),E.type="button",E.textContent=x(r,"downloadPhoto");const P=document.createElement("button");P.className="storysplat-photo-share-action",P.type="button",P.textContent=x(r,"copyLink"),M.appendChild(E),M.appendChild(P),v.appendChild(b),v.appendChild(_),v.appendChild(C),v.appendChild(M),y.appendChild(v),t.appendChild(y);const T=()=>{f&&(URL.revokeObjectURL(f),f=null),y.remove()};S.addEventListener("click",T),y.addEventListener("click",t=>{t.target===y&&T()}),E.addEventListener("click",()=>{const e=document.createElement("a");e.href=g,e.download=o,e.rel="noopener",document.body.appendChild(e),e.click(),e.remove(),k(t,x(r,"photoDownloaded"))}),P.addEventListener("click",async()=>{try{await navigator.clipboard.writeText(i),k(t,x(r,"linkCopied"))}catch{window.prompt("Copy this link:",i)}})}(e,{blob:t,fileName:n,shareUrl:o,shareText:a,title:s,buttonLabels:u,sharePhoto:d?async()=>{try{await r.share({title:s,text:a,url:o,files:c})}catch(t){if((t=>"object"==typeof t&&null!==t&&"name"in t&&"AbortError"===t.name)(t))return;await r.share({title:s,text:a,files:c})}}:void 0})};let At=null;let zt=r.additionalSplats||[];const Dt=r.keepMeshesInMemory??!1;let It=L(r.initialSplatExploreMode),Ft=r.refocusTapMode||"single",Bt=null,Vt=null,$t=!1;const Ut=new Map;let Ot=null,Nt=-1,Wt=-1,Ht=0,Gt="compare"===r.splatSwapMode&&zt.length>0,jt=null,Xt=(r.compareSettings?.initialPosition??50)/100,Yt=null,Zt=null,Kt=null,Jt=null;const te=r.compareSettings||{};let ee=0,ne=!1,oe=!1,ae=null,le=null,ce=null,de=null;const he=new t.Entity("camera");let me=new t.Color(.1,.1,.1);if(r.backgroundColor){const Bl=r.backgroundColor.replace("#","");if(6===Bl.length){const Vl=parseInt(Bl.substring(0,2),16)/255,$l=parseInt(Bl.substring(2,4),16)/255,Ul=parseInt(Bl.substring(4,6),16)/255;me=new t.Color(Vl,$l,Ul),console.log("[StorySplat Viewer] Background color set from config:",r.backgroundColor)}}he.addComponent("camera",{clearColor:me,fov:r.fov||60,nearClip:r.nearClip||.1,farClip:r.farClip||1e3}),he.addComponent("audiolistener");class fe extends t.PostEffect{constructor(e){super(e),this.fxaaShader=t.ShaderUtils.createShader(e,{uniqueName:"StorySplatFxaa",attributes:{aPosition:t.SEMANTIC_POSITION},vertexGLSL:t.PostEffect.quadVertexShader,fragmentGLSL:"\n uniform sampler2D uColorBuffer;\n uniform vec2 uResolution;\n\n #define FXAA_REDUCE_MIN (1.0/128.0)\n #define FXAA_REDUCE_MUL (1.0/8.0)\n #define FXAA_SPAN_MAX 8.0\n\n void main() {\n vec2 fragCoord = gl_FragCoord.xy * uResolution;\n vec4 rgbaM = texture2D(uColorBuffer, fragCoord);\n vec3 rgbM = rgbaM.rgb;\n float opacity = rgbaM.a;\n\n vec3 rgbNW = textureOffset(uColorBuffer, fragCoord, ivec2(-1, -1)).rgb;\n vec3 rgbNE = textureOffset(uColorBuffer, fragCoord, ivec2( 1, -1)).rgb;\n vec3 rgbSW = textureOffset(uColorBuffer, fragCoord, ivec2(-1, 1)).rgb;\n vec3 rgbSE = textureOffset(uColorBuffer, fragCoord, ivec2( 1, 1)).rgb;\n\n vec3 luma = vec3(0.299, 0.587, 0.114);\n float lumaNW = dot(rgbNW, luma);\n float lumaNE = dot(rgbNE, luma);\n float lumaSW = dot(rgbSW, luma);\n float lumaSE = dot(rgbSE, luma);\n float lumaM = dot(rgbM, luma);\n float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));\n float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));\n\n vec2 dir;\n dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));\n dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));\n float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);\n float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);\n dir = min(vec2(FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX), dir * rcpDirMin)) * uResolution;\n\n vec3 rgbA = 0.5 * (\n texture2D(uColorBuffer, fragCoord + dir * (1.0/3.0 - 0.5)).rgb +\n texture2D(uColorBuffer, fragCoord + dir * (2.0/3.0 - 0.5)).rgb);\n vec3 rgbB = rgbA * 0.5 + 0.25 * (\n texture2D(uColorBuffer, fragCoord + dir * -0.5).rgb +\n texture2D(uColorBuffer, fragCoord + dir * 0.5).rgb);\n float lumaB = dot(rgbB, luma);\n gl_FragColor = (lumaB < lumaMin || lumaB > lumaMax)\n ? vec4(rgbA, opacity)\n : vec4(rgbB, opacity);\n }\n "}),this.resolution=new Float32Array(2)}render(t,e,n){this.resolution[0]=1/t.width,this.resolution[1]=1/t.height,this.device.scope.resolve("uResolution").setValue(this.resolution),this.device.scope.resolve("uColorBuffer").setValue(t.colorBuffer),this.drawQuad(e,this.fxaaShader,n)}}let ye=null;const ve=t=>({r:parseInt(t.slice(1,3),16)/255,g:parseInt(t.slice(3,5),16)/255,b:parseInt(t.slice(5,7),16)/255}),be={lighting:t.SSAOTYPE_LIGHTING??1,combine:t.SSAOTYPE_COMBINE??2},xe={linear:t.TONEMAP_LINEAR??0,filmic:t.TONEMAP_FILMIC??1,hejl:t.TONEMAP_HEJL??2,aces:t.TONEMAP_ACES??3,aces2:t.TONEMAP_ACES2??4,neutral:t.TONEMAP_NEUTRAL??5},we={linear:t.FOG_LINEAR??"linear",exp:t.FOG_EXP??"exp",exp2:t.FOG_EXP2??"exp2"},Se=t.FOG_NONE??"none";void 0===t.SSAOTYPE_LIGHTING&&console.warn("[StorySplat Viewer] PlayCanvas SSAO type constants not found, using fallback values"),void 0===t.TONEMAP_LINEAR&&console.warn("[StorySplat Viewer] PlayCanvas tone mapping constants not found, using fallback values"),void 0===t.FOG_LINEAR&&console.warn("[StorySplat Viewer] PlayCanvas fog type constants not found, using fallback values");const _e=(e,n)=>{const o=n.colorEnhance,i=n.vignette,s=n.bloom,a=n.ssao,r=n.dof,l=n.fringing,c=n.grading,d=n.taa;if(o?.enabled?(e.colorEnhance.enabled=!0,e.colorEnhance.shadows=o.shadows??0,e.colorEnhance.highlights=o.highlights??0,e.colorEnhance.vibrance=o.vibrance??0,e.colorEnhance.midtones=o.midtones??0,e.colorEnhance.dehaze=o.dehaze??0):e.colorEnhance.enabled=!1,i?.enabled){if(e.vignette.intensity=i.intensity??.4,e.vignette.inner=i.inner??.5,e.vignette.outer=i.outer??1,e.vignette.curvature=i.curvature??1.5,i.color){const t=ve(i.color);e.vignette.color.set(t.r,t.g,t.b)}}else e.vignette.intensity=0;if(s?.enabled?(e.bloom.intensity=s.intensity??.02,e.bloom.blurLevel=s.blurLevel??16):e.bloom.intensity=0,a?.enabled?(e.ssao.type=be[a.type]??be.lighting,e.ssao.intensity=a.intensity??.5,e.ssao.radius=a.radius??30,e.ssao.power=a.power??6,e.ssao.samples=a.samples??12,e.ssao.minAngle=a.minAngle??10,e.ssao.blurEnabled=!1!==a.blurEnabled):e.ssao.type=t.SSAOTYPE_NONE??0,r?.enabled?(e.dof.enabled=!0,e.dof.focusDistance=r.focusDistance??100,e.dof.focusRange=r.focusRange??10,e.dof.blurRadius=r.blurRadius??3,e.dof.blurRings=r.blurRings??4,e.dof.nearBlur=r.nearBlur??!1,e.dof.highQuality=!1!==r.highQuality):e.dof.enabled=!1,e.fringing.intensity=l?.enabled?l.intensity??10:0,c?.enabled){if(e.grading.enabled=!0,e.grading.brightness=c.brightness??1,e.grading.contrast=c.contrast??1,e.grading.saturation=c.saturation??1,c.tint){const t=ve(c.tint);e.grading.tint.set(t.r,t.g,t.b)}}else e.grading.enabled=!1;d?.enabled?(e.taa.enabled=!0,e.taa.jitter=d.jitter??1):e.taa.enabled=!1;const p=n.toneMapping;e.rendering.toneMapping=p?.enabled?xe[p.type]??xe.linear:xe.linear;const h=n.sharpness;e.rendering.sharpness=h?.enabled?h.value??.5:0;const u=n.renderScale;e.rendering.renderTargetScale=u?.enabled?u.value??1:1,e.update()},Ce=t=>{if(t?.enabled){U.scene.fog.type=we[t.type]??we.linear;const e=ve(t.color??"#b8c4d0");U.scene.fog.color.set(e.r,e.g,e.b),U.scene.fog.density=t.density??.05,U.scene.fog.start=t.start??10,U.scene.fog.end=t.end??100,U.scene.gsplat&&(U.scene.gsplat.useFog=!0)}else U.scene.fog.type=Se,U.scene.gsplat&&(U.scene.gsplat.useFog=!1)},Me=t=>{U.scene.exposure=t?.enabled?t.value??1:1};let Ee,Pe=0;const ke=t=>{const e=Math.max(0,Math.min(1,t||0));if(e===Pe)return;const n=Pe>0,o=!n&&e>0,i=n&&0===e;Pe=e,U.scene.gsplat&&(U.scene.gsplat.fisheye=e,o?(Ee=U.scene.gsplat.radialSorting,U.scene.gsplat.radialSorting=!0):i&&void 0!==Ee&&(U.scene.gsplat.radialSorting=Ee,Ee=void 0));const s=U.scene.sky;s&&(s.fisheye=e),console.log("[StorySplat Viewer] Fisheye set to",e)};let Te=null,De=null;let Fe=null,Be=null;const $e=()=>{Be=null;const t=Fe;if(Fe=null,t&&!vt){Te&&(Te.destroy(),Te=null,De=null);try{Te=new Pt(U,t.opts),U.root.addChild(Te.entity),De=t.key,ne&&(Te.entity.enabled=!1,Te.entity.__panoramaHidden=!0)}catch(t){console.warn("[StorySplat Viewer] Weather setup failed:",t)}}},Ue=t=>{if(!t?.enabled)return Be&&(clearTimeout(Be),Be=null,Fe=null),void(Te&&(Te.destroy(),Te=null,De=null));const e="custom"===t.preset?"snow":t.preset,n=kt[e]??kt.snow,o=(t=>{if(!t)return;let e=t.replace("#","");if(3===e.length&&(e=e[0]+e[0]+e[1]+e[1]+e[2]+e[2]),6!==e.length)return;const n=parseInt(e,16);return Number.isNaN(n)?void 0:[(n>>16&255)/255,(n>>8&255)/255,(255&n)/255]})(t.color)??n.color,i={...n,speed:t.speed??n.speed,drift:t.drift??n.drift,opacity:t.opacity??n.opacity,color:o,particleMinSize:t.particleMinSize??n.particleMinSize,particleMaxSize:t.particleMaxSize??n.particleMaxSize,elongate:t.elongate??n.elongate,density:t.density??n.density,extents:t.extents,followEntity:he},s=t.extents??{x:12,y:12,z:12},a=`${s.x},${s.y},${s.z}|${i.density}`;Te&&De===a?Te.applyOptions(i):(Te&&Te.applyOptions(i),Fe={opts:i,key:a},Be&&clearTimeout(Be),Be=setTimeout($e,150))},Oe=t=>{Te&&!ne&&Te.update(t)};U.on("update",Oe);const Ne=t=>t?.colorEnhance?.enabled||t?.vignette?.enabled||t?.bloom?.enabled||t?.ssao?.enabled||t?.dof?.enabled||t?.fringing?.enabled||t?.grading?.enabled||t?.taa?.enabled||t?.toneMapping?.enabled||t?.sharpness?.enabled||t?.renderScale?.enabled;let We=null;const He=r.postProcessing;if(Ne(He)&&he.camera)try{We=new t.CameraFrame(U,he.camera),We.enabled=!0,_e(We,He),console.log("[StorySplat Viewer] Post-processing enabled:",Object.keys(He).filter(t=>He[t]?.enabled))}catch(Ol){console.warn("[StorySplat Viewer] CameraFrame setup failed (WebGPU may be required):",Ol)}if(!1!==He?.antialiasing?.enabled&&he.camera)if(We)We.rendering.sharpness=Math.max(We.rendering.sharpness,.5),console.log("[StorySplat Viewer] CameraFrame active, using sharpness for edge enhancement");else try{ye=new fe(U.graphicsDevice),he.camera.postEffects.addEffect(ye),console.log("[StorySplat Viewer] FXAA antialiasing enabled")}catch(Nl){console.warn("[StorySplat Viewer] FXAA setup failed:",Nl)}Ce(He?.fog),Me(He?.exposure),ke(0),r.weather?.enabled&&Ue(r.weather),console.log("[StorySplat Viewer] Camera settings:",{fov:r.fov,nearClip:r.nearClip,farClip:r.farClip,playerHeight:r.playerHeight});const je=r.playerHeight||1.6;if(r.waypoints&&r.waypoints.length>0){const Wl=r.waypoints[0];if(console.log("[StorySplat Viewer] First waypoint raw:",Wl),Wl.position){const Hl=Wl.position;he.setPosition(Hl.x,Hl.y,-Hl.z),console.log("[StorySplat Viewer] Camera position (Z negated):",{x:Hl.x,y:Hl.y,z:-Hl.z})}else he.setPosition(0,je,5);if(Wl.rotation){const Gl=Jo(Wl.rotation);he.setRotation(Gl),console.log("[StorySplat Viewer] Camera rotation set from waypoint")}}else n.frameSequence&&n.frameSequence.frameUrls&&n.frameSequence.frameUrls.length>0?(he.setPosition(0,je,5),he.lookAt(new t.Vec3(0,1,0)),console.log("[StorySplat Viewer] Camera set for frame sequence (orbit around origin)")):(he.setPosition(0,je,5),he.lookAt(new t.Vec3(0,0,0)),console.log("[StorySplat Viewer] Camera set to default position (0, playerHeight, 5)"));let Xe=r.orbitCameraSettings;const qe=()=>{if(!Xe?.cameraPosition||!Xe.pivotPoint)return null;const e=Xe.cameraPosition,n=Xe.pivotPoint,o=new t.Vec3(n.x,n.y,n.z),i=new t.Vec3(e.x,e.y,e.z),s=i.distance(o);return{cameraPosition:i,pivot:o,radius:Math.max(s,0)}},Ye=()=>{const t=qe();return t?(he.setPosition(t.cameraPosition),he.lookAt(t.pivot),t):null};if(Xe?.cameraPosition&&Xe?.pivotPoint&&"orbit"===It){const jl=Ye();console.log("[StorySplat Viewer] Camera set from orbitCameraSettings:",jl?.cameraPosition,"pivot:",jl?.pivot,"radius:",jl?.radius)}if("walk"===S&&r.walkSpawnOverride&&r.walkCameraSettings?.cameraPosition){const Xl=r.walkCameraSettings.cameraPosition;if(he.setPosition(Xl.x,Xl.y,Xl.z),r.walkCameraSettings.rotation){const ql=r.walkCameraSettings.rotation;he.setEulerAngles(ql.x,ql.y,ql.z)}console.log("[StorySplat Viewer] Initial camera set from walkCameraSettings:",Xl)}const Ze=new t.Entity("camera-rig");U.root.addChild(Ze),Ze.addChild(he);const nn=new t.Entity("scene-content");U.root.addChild(nn);const sn=5*(r.cameraMovementSpeed??1),un=new tt(he,U,{moveSpeed:sn,moveFastSpeed:2.5*sn,moveSlowSpeed:.5*sn,rotateSpeed:800/(r.cameraRotationSensitivity||4e3),enableOrbit:E("orbit"),enableFly:E("fly"),enablePan:!0,invertRotation:r.invertCameraRotation,moveDamping:r.cameraDamping??.75,rotateDamping:r.cameraDamping??.75,zoomDamping:.8});if(Xe?.rotationLimits){const Yl=qe();Yl&&(un.syncFromCamera(Yl.pivot,!0,!0),un.setFocusDistance(Yl.pivot,Yl.radius,!0)),un.setOrbitRotationLimits(Xe.rotationLimits,!0)}const gn=()=>{const e=Xe?.zoomLimits;if(e?.enabled){const n=Number.isFinite(e.min)?Math.max(.01,e.min):.01,o=Number.isFinite(e.max)&&e.max>n?e.max:1/0;un.zoomRange=new t.Vec2(n,o)}else un.zoomRange=new t.Vec2(.01,1/0)};gn();const fn=e=>{Xe=e??void 0,r.orbitCameraSettings=Xe,(()=>{const e=Xe?.rotationLimits??null;if(e?.enabled&&Xe?.cameraPosition&&Xe?.pivotPoint){const e=Xe.cameraPosition,n=Xe.pivotPoint;un.setOrbitRotationLimitAnchor(new t.Vec3(e.x,e.y,e.z),new t.Vec3(n.x,n.y,n.z))}un.setOrbitRotationLimits(e,!(Xe?.cameraPosition&&Xe?.pivotPoint)),gn()})()},yn=()=>{if(!Xe?.cameraPosition||!Xe.pivotPoint)return!1;un.setOrbitRotationLimits(null,!1);const t=Ye();return!!t&&("orbit"===un.mode?(un.syncFromCamera(t.pivot,!0,!0),un.setFocusDistance(t.pivot,t.radius,!0)):un.setOrbitModeFromFocus(t.pivot,t.radius,!0),un.setOrbitRotationLimits(Xe.rotationLimits??null,!0),gn(),!0)};let vn=null;const bn=r.collisionMeshesData&&r.collisionMeshesData.length>0;if(bn||!!r.voxelCollisionUrl){const Zl=null!=r.walkSpeed?r.walkSpeed:sn;vn=new at(he,U,{moveSpeed:Zl,sprintMultiplier:2,lookSensitivity:1/(r.cameraRotationSensitivity||4e3)*10,playerHeight:r.playerHeight||1.6,gravity:20,jumpVelocity:8,collisionRadius:.3,stepHeight:.3}),bn&&vn.createCollisionMeshes(r.collisionMeshesData).then(()=>{console.log("[StorySplat Viewer] Collision meshes loaded for walk mode"),un.setCollisionEntities(vn.collisionMeshEntities)}).catch(t=>{console.error("[StorySplat Viewer] Failed to load collision meshes:",t)}),r.voxelCollisionUrl&&vn.initVoxelCollision(r.voxelCollisionUrl).then(()=>{const t=vn.voxelCollisionInstance;t&&un.setVoxelCollision(t,ot)}).catch(t=>{console.warn("[StorySplat Viewer] Voxel collision init failed:",t)})}let xn=null,wn=null;if(r.segmentDataUrl&&r.enableSegmentHover){let Kl=0;const Ql=()=>{if(!ot?.gsplat)return++Kl>50?void console.warn("[StorySplat Viewer] Gave up waiting for splat entity for segments"):void setTimeout(Ql,200);ge(ot,r.segmentDataUrl,r.segmentMetaUrl).then(t=>{xn=t}).catch(t=>{console.warn("[StorySplat Viewer] Segment loading failed:",t)})};Ql()}vn&&null!=r.headBobEnabled&&(vn.headBobEnabled=r.headBobEnabled),null!=r.doubleTapMoveSpeed&&(un.autoMoveSpeedFactor=r.doubleTapMoveSpeed,vn&&(vn.autoMoveSpeedFactor=r.doubleTapMoveSpeed));let Sn=S,_n=!0;"tour"===S&&un.disable();const Cn=new t.Picker(U,1,1,!0),Mn=new t.Picker(U,1,1,!0),En=new t.Layer({name:"Particles",clearDepthBuffer:!0});U.scene.layers.push(En);const Pn=new t.Layer({name:"VoxelDebug",clearDepthBuffer:!0});U.scene.layers.push(Pn);const kn=new t.Layer({name:"Reticle"});U.scene.layers.push(kn);const Tn=he.camera.layers;he.camera.layers=[...Tn,En.id,Pn.id,kn.id];const An=new t.Entity("reticle"),Ln=new t.StandardMaterial;Ln.emissive=new t.Color(1,1,1),Ln.diffuse=new t.Color(0,0,0),Ln.useLighting=!1,Ln.blendType=t.BLEND_NORMAL,Ln.opacity=1,Ln.depthTest=!1,Ln.depthWrite=!1,Ln.cull=t.CULLFACE_NONE,Ln.update();const Rn=new t.TorusGeometry({tubeRadius:.048,ringRadius:.193,segments:24,sides:10}),zn=t.Mesh.fromGeometry(U.graphicsDevice,Rn),Dn=new t.MeshInstance(zn,Ln);Dn.drawOrder=9999,An.addComponent("render",{meshInstances:[Dn],castShadows:!1,layers:[kn.id]}),An.enabled=!1,U.root.addChild(An);let In=0,Fn=0,Bn=new t.Vec3,Vn=new t.Vec3,$n=!1,Un=0,On=!1,Nn=new t.Vec3(0,1,0),Wn=new t.Vec3(0,1,0);const Hn=new t.Quat,Gn=new t.Vec3,jn=new t.Vec3,Xn=new t.Vec3,qn=new t.Vec3,Yn=new t.Vec3;let Zn=!0,Kn=!1,Qn=0,Jn=null;let to,eo=!1,no=null,oo=!1,io=!1,so=0,ao=!1,ro=null,lo=null,co="",po=!1,ho=!1,uo=null,mo=null;function go(t){if("function"==typeof t?.isRunning)try{return!!t.isRunning()}catch(t){Re("XR8.isRunning() threw; falling back to internal AR session state",t)}return ao}function fo(){return io||oo||"ar"===no||e.classList.contains("storysplat-ar-mode")}function yo(){const t=lo;t&&requestAnimationFrame(()=>{vt||fo()||lo!==t||(lo=null,t())})}function vo(){if(!ho)return;const t=window;try{po?t.pc=to:delete t.pc}catch(t){Re("Failed to restore previous window.pc after 8th Wall AR exit",t)}finally{to=void 0,po=!1,ho=!1}}function bo(t){if(t.PlayCanvas?.stop)try{return void t.PlayCanvas.stop()}catch(t){Re("XR8.PlayCanvas.stop() failed; falling back to XR8.stop()",t)}t.stop()}function xo(){"ar"===no&&eo?Le("Ignoring duplicate AR enter signal"):(eo=!0,oo=!0,no="ar",D.arButton?.classList.add("active"),D.arButton.textContent=x(u,"exitAr"),e.classList.add("storysplat-ar-mode"),e.querySelector(".storysplat-photo-share-backdrop")?.remove(),D.skinsDropdown?.classList.remove("open"),function(){if(!mo)return;U.scene.setSkybox(void 0),U.scene.envAtlas=null;const t=mo.fallbackSkyboxEntity??U.root.findByName("skybox-fallback");t&&(mo.fallbackSkyboxEntity||(mo.fallbackSkyboxEntity=t,mo.fallbackSkyboxEnabled=t.enabled),t.enabled=!1)}(),un.disable(),vn&&vn.disable(),uo=U.autoRender,U.autoRender=!0,Le("Entered 8th Wall AR state",{autoRenderBeforeAR:uo,canvas:{width:I.width,height:I.height,clientWidth:I.clientWidth,clientHeight:I.clientHeight}}),i.emit("xrStart",{type:"ar"}))}function wo(){const t="ar"===no||oo||e.classList.contains("storysplat-ar-mode");t||null!==mo?(eo=!1,oo=!1,ko(),D.arButton?.classList.remove("active"),D.arButton&&(D.arButton.textContent=x(u,"ar")),e.classList.remove("storysplat-ar-mode"),no=null,"explore"===Sn?un.enable():"walk"===Sn&&vn&&vn.enable(),function(t){if(!mo)return;const e=mo;mo=null,Ze.setPosition(e.cameraRigPosition),Ze.setRotation(e.cameraRigRotation),he.setPosition(e.cameraPosition),he.setRotation(e.cameraRotation),nn.setLocalPosition(e.scenePosition),nn.setLocalRotation(e.sceneRotation),nn.setLocalScale(e.sceneScale);const n=he.camera;n.clearColor=e.clearColor,n.clearColorBuffer=e.clearColorBuffer,n.fov=e.fov,n.nearClip=e.nearClip,n.farClip=e.farClip,n.aspectRatio=e.aspectRatio,n.horizontalFov=e.horizontalFov,n.calculateProjection=e.calculateProjection,U.scene.skybox=e.skybox,U.scene.envAtlas=e.envAtlas,U.scene.skyboxMip=e.skyboxMip,U.scene.skyboxIntensity=e.skyboxIntensity,U.scene.skyboxRotation=e.skyboxRotation,e.fallbackSkyboxEntity&&(e.fallbackSkyboxEntity.enabled=e.fallbackSkyboxEnabled),un.syncFromCamera(),U.renderNextFrame=!0,Le("Restored pre-AR viewer state",{reason:t,cameraPosition:e.cameraPosition,scenePosition:e.scenePosition,sceneScale:e.sceneScale})}("exitARState"),null!==uo&&(U.autoRender=uo,uo=null),U.renderNextFrame=!0,Le("Exited 8th Wall AR state",{autoRender:U.autoRender}),yo(),vo(),t&&i.emit("xrEnd",{})):Le("Ignoring duplicate AR exit signal")}function So(t){e.querySelector(".storysplat-ar-error")?.remove();const n=document.createElement("div");n.className="storysplat-ar-error",n.style.cssText='position:absolute;bottom:60px;left:50%;transform:translateX(-50%);z-index:10001;background:rgba(200,40,40,0.92);color:white;padding:12px 20px;border-radius:10px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;max-width:90%;text-align:center;backdrop-filter:blur(8px);box-shadow:0 4px 12px rgba(0,0,0,0.3);cursor:pointer;',n.textContent=t,n.addEventListener("click",()=>n.remove()),e.appendChild(n),setTimeout(()=>{n.parentNode&&n.remove()},8e3)}async function _o(){if(io||oo||"ar"===no)return void Re("Ignoring AR start request because AR is already starting or active",{isStarting8thWallAR:io,isIn8thWallAR:oo,xrSessionType:no});io=!0;const n=++so;co="";try{Le("Starting 8th Wall AR",{mobile:ln(),devicePixelRatio:window.devicePixelRatio,canvas:{width:I.width,height:I.height,clientWidth:I.clientWidth,clientHeight:I.clientHeight}});const o=await Ie(e,r.eighthWallBaseUrl);if(vt||n!==so)return void Le("Ignoring stale 8th Wall AR start after async load",{isDestroyed:vt,startGeneration:n,currentGeneration:so});if(!o.PlayCanvas?.run)throw new Error("8th Wall PlayCanvas wrapper is not available on XR8.PlayCanvas.run");const i=function(t){const{app:e,camera:n,onDisableControls:o,onRestoreControls:i,onARStart:s,onAREnd:a,onTrackingStatus:r,onException:l}=t;let c=!1,d=0,p=!1,h="",u="";const m=(t,i)=>{c?Ae("StorySplat XR8 pipeline enter ignored because it is already attached",{source:t}):(c=!0,d=0,p=!1,h="",u="",Le("StorySplat XR8 pipeline attached",{source:t,canvas:i?.canvas instanceof HTMLCanvasElement?{width:i.canvas.width,height:i.canvas.height,clientWidth:i.canvas.clientWidth,clientHeight:i.canvas.clientHeight}:void 0,appAutoRender:e.autoRender,cameraPosition:n.getPosition()}),o(),s?.())},g=t=>{c?(c=!1,Le("StorySplat XR8 pipeline detached",{source:t,frames:d,lastTrackingStatus:h,lastTrackingReason:u}),i(),a?.()):Ae("StorySplat XR8 pipeline leave ignored because it is already detached",{source:t})};return{name:"storysplat-ar",onAttach(t){m("onAttach",t)},onStart({canvas:t,GLctx:e}){m("onStart",{canvas:t})},onUpdate({processCpuResult:t}){d+=1;const e=t?.reality;e?(p||(p=!0,Le("XR8 reality frame received",{frameCount:d,hasPosition:!!e.position,hasRotation:!!e.rotation,hasIntrinsics:!!e.intrinsics,hasRealityTexture:!!e.realityTexture,trackingStatus:e.trackingStatus,trackingReason:e.trackingReason})),!e.trackingStatus||e.trackingStatus===h&&(e.trackingReason??"")===u||(h=e.trackingStatus,u=e.trackingReason??"",Le("XR8 tracking status changed",{status:h,reason:u||void 0,frameCount:d}),r?.(e.trackingStatus,e.trackingReason))):d<=5&&Ae("XR8 update without reality payload",{frameCount:d})},onDetach(){g("onDetach")},onStop(){g("onStop")},onException(t){ze("XR8 runtime exception",t),l?.(t)},listeners:[{event:"reality.trackingstatus",process:t=>{const e=t.status,n=t.reason;Le("XR8 tracking status event",{status:e,reason:n}),r?.(e,n)}}]}}({app:U,camera:he,sceneRoot:nn,onDisableControls:()=>{un.disable(),vn&&vn.disable()},onRestoreControls:()=>{"explore"===Sn?un.enable():"walk"===Sn&&vn&&vn.enable()},onARStart:()=>{xo()},onAREnd:()=>{wo(),ro&&(ro.stop(),ro=null)},onTrackingStatus:(t,e)=>{co=t,Le("Tracking status callback",{status:t,reason:e}),ro?.setTrackingStatus(t)},onException:t=>{So(`AR error: ${t.message}`),Co()}});go(o)&&(Re("XR8 was already running before AR start; stopping stale session first"),bo(o),ao=!1),function(){if(mo)return;const t=he.camera,e=U.root.findByName("skybox-fallback");mo={cameraPosition:he.getPosition().clone(),cameraRotation:he.getRotation().clone(),cameraRigPosition:Ze.getPosition().clone(),cameraRigRotation:Ze.getRotation().clone(),scenePosition:nn.getLocalPosition().clone(),sceneRotation:nn.getLocalRotation().clone(),sceneScale:nn.getLocalScale().clone(),clearColor:t.clearColor.clone(),clearColorBuffer:t.clearColorBuffer,fov:t.fov,nearClip:t.nearClip,farClip:t.farClip,aspectRatio:t.aspectRatio,horizontalFov:t.horizontalFov,calculateProjection:t.calculateProjection,skybox:U.scene.skybox,envAtlas:U.scene.envAtlas,skyboxMip:U.scene.skyboxMip,skyboxIntensity:U.scene.skyboxIntensity,skyboxRotation:U.scene.skyboxRotation.clone(),fallbackSkyboxEntity:e,fallbackSkyboxEnabled:e?.enabled??!1},Le("Captured pre-AR viewer state",{cameraPosition:mo.cameraPosition,scenePosition:mo.scenePosition,sceneScale:mo.sceneScale,autoRender:U.autoRender})}(),o.XrController.configure({scale:"absolute"});const s=U.graphicsDevice.canvas,a=U.graphicsDevice,l={canvas:s,ownRunLoop:!1,webgl2:!(!a.webgl2&&!a.isWebGL2),cameraConfig:o.XrConfig?.camera?{direction:o.XrConfig.camera().BACK}:void 0};if(Le("Running XR8.PlayCanvas integration",{runConfig:{ownRunLoop:l.ownRunLoop,webgl2:l.webgl2,hasCameraConfig:!!l.cameraConfig},modules:["reality","storysplat-ar"]}),vt||n!==so)return void Le("Ignoring stale 8th Wall AR start before runtime run",{isDestroyed:vt,startGeneration:n,currentGeneration:so});if(function(){const e=window;ho||(po=Object.prototype.hasOwnProperty.call(e,"pc"),to=e.pc);try{e.pc=t,ho=!0}catch(t){throw new Error(`8th Wall requires window.pc to point at this viewer's PlayCanvas runtime: ${t instanceof Error?t.message:String(t)}`)}if(e.pc!==t)throw new Error("8th Wall requires window.pc to point at this viewer's PlayCanvas runtime")}(),o.PlayCanvas.run({pcCamera:he,pcApp:U},[o.XrController.pipelineModule(),i],l),vt||n!==so)return void Le("Ignoring stale 8th Wall AR start after runtime run",{isDestroyed:vt,startGeneration:n,currentGeneration:so});ao=!0,ro=new Ve({app:U,sceneRoot:nn,canvas:s,tapMode:Ft,shouldIgnoreTap:(t,e)=>{const n=s.getBoundingClientRect(),o=t-n.left,i=e-n.top;return!!Sr(o,i)||!!$r(o,i)},onPlaced:t=>{Le("Scene placement callback",{position:t})}}),co&&ro.setTrackingStatus(co),ro.start()}catch(t){if(vt||n!==so)return void Re("Ignoring stale 8th Wall AR start error",t);ze("Failed to start 8th Wall AR",t);So(`AR failed to start: ${t instanceof Error?t.message:String(t)}`),wo(),vo()}finally{n===so&&(io=!1)}}function Co(){so+=1,io=!1;const t=go(window.XR8);if(!(t||oo||"ar"===no||io||mo))return yo(),void vo();Le("Stopping 8th Wall AR",{isIn8thWallAR:oo,xrSessionType:no,xr8Running:t});try{t&&window.XR8&&bo(window.XR8)}catch(t){ze("Error stopping 8th Wall AR",t)}finally{ao=!1}ro&&(ro.stop(),ro=null),wo()}let Mo=null,Eo=null,Po=!1;function ko(){Mo&&(Mo.enabled=!1,Po=!1)}let To=0;U.on("update",e=>{var n,o;D.fpsCounter&&(To+=e,To>=.5&&(To=0,o=1/e,(n=D).fpsCounter&&(n.fpsCounter.textContent=`${Math.round(o)} FPS`))),"walk"===Sn&&vn?vn.update(e):un.update(e),_n||function(){const t=he.getPosition();Cs.forEach(e=>{const n=e.hotspotData;if(!n)return;const o=e.mediaTriggerMode||"click",i="proximity"===o,s="proximity"===(n.audioTriggerMode||("proximity"===o?"proximity":"click"))&&e.hotspotAudioSlotId;if(!i&&!s)return;const a=e.getPosition(),r=t.distance(a)<=(e.proximityDistance||5),l=e.wasInProximity||!1;if(r&&!l){if(i&&("video"===n.type&&e.videoElement?vr(e,n):"gif"===n.type&&(e.enabled=!0)),s&&e.hotspotAudioReady){const t=e.sound?.slot(e.hotspotAudioSlotId);t&&!t.isPlaying&&(t.play(),e.hotspotAudioPlaying=!0,console.log("[Audio] Hotspot audio started (proximity):",n.title))}e.wasInProximity=!0}else if(!r&&l){if(i&&("video"===n.type&&e.videoElement?!1!==n.pauseOnLeaveProximity&&br(e):"gif"===n.type&&(e.enabled=!1)),s){if(n.audioStopOnExit??!0){const t=e.sound?.slot(e.hotspotAudioSlotId);t&&t.isPlaying&&(t.stop(),e.hotspotAudioPlaying=!1,console.log("[Audio] Hotspot audio stopped (proximity):",n.title))}}e.wasInProximity=!1}})}(),function(){if(0===Zs.size)return;const t=he.getPosition();Zs.forEach((e,n)=>{const{entity:o,config:i,slotId:s,assetReady:a}=e;if(!a)return;const r=o.sound?.slot(s);if(!r)return;if(!i.triggerMode||"proximity"!==i.triggerMode)return;const l=e.playing||r.isPlaying,c=o.getPosition(),d=t.distance(c),p=i.proximityRadius??i.maxDistance??1,h=i.stopOnExit??!0;d<=p&&!l?(console.log(`[Audio] Proximity play: ${n}, distance=${d.toFixed(2)}, radius=${p}`),r.play(),e.playing=!0):d>p&&l&&h&&(console.log(`[Audio] Proximity stop: ${n}, distance=${d.toFixed(2)}`),r.stop(),e.playing=!1)})}(),function(){if(0===rr.size)return;const t=he.getPosition();rr.forEach((e,n)=>{const{entity:o,config:i,slotId:s,assetReady:a}=e;if(!a)return;const r=o.sound?.slot(s);if(!r)return;const l=e.playing||r.isPlaying,c=o.getPosition(),d=t.distance(c),p=i.triggerMode||(!1!==i.autoplay?"autoplay":"proximity"),h=i.proximityRadius??i.maxDistance??10;if(d<=h&&!l&&("proximity"!==p||r.isPlaying||(r.play(),e.playing=!0)),d>h&&l&&"proximity"===p){(i.stopOnExit??!0)&&(r.stop(),e.playing=!1)}})}(),function(){if(0===sa.size)return;const t=he.getPosition();sa.forEach(e=>{const{entity:n,config:o,audioSlotId:i}=e,s=o.interaction;if(!s?.playAudio||!i||!e.audioAssetReady)return;if("proximity"!==(s.audioTriggerMode||s.activationMode||"click"))return;const a=n.sound?.slot(i);if(!a)return;const r=e.audioPlaying||a.isPlaying,l=t.distance(n.getPosition()),c=s.audioProximityRadius??s.audioMaxDistance??1,d=s.audioStopOnExit??!0;l<=c&&!r?(a.play(),e.audioPlaying=!0):l>c&&r&&d&&(a.stop(),e.audioPlaying=!1)})}(),function(){if(!r.waypoints?.length)return;const e=he.getPosition();r.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?lr.has(o)||(lr.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=>{})}(n)):lr.has(o)&&(lr.delete(o),console.log(`[StorySplat] Waypoint ${o} exited`),function(t){if(!t.interactions?.length)return;t.interactions.forEach(t=>{})}(n))})}(),eo&&Po&&function(){if(Mo?.enabled&&he){const t=he.forward.clone(),e=he.getPosition(),n=e.clone().add(t.mulScalar(1.5));n.y-=.1,Mo.setPosition(n),Mo.lookAt(e),Mo.rotateLocal(90,0,0)}}(),function(t){if(0===Wa.length)return;const e=[];for(let n=0;n<Wa.length;n++){const o=Wa[n];if(o.age+=t,o.age>=o.lifetime){e.push(n);continue}o.velocity.y-=ja*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=tr(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*Xa,Math.abs(o.velocity.y)<.3&&(o.velocity.y=0))}er(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--)nr(Wa[e[t]]),Wa.splice(e[t],1)}(e),il&&!a&&il.update(e,ki),eo?($n=!1,Qn=0,An.enabled=!1):(!Zn||q||"explore"!==Sn&&"walk"!==Sn)&&($n=!1),Kn&&($n=!1),Ir&&($n=!1);const i=$n?1:0;if(Qn+=(i-Qn)*Math.min(1,8*e),Math.abs(Qn-i)<.01&&(Qn=i),Qn>0){Vn.distance(Bn)<.001?Vn.copy(Bn):Vn.lerp(Vn,Bn,Math.min(1,15*e)),Wn.lerp(Wn,Nn,Math.min(1,10*e)),Wn.normalize();const n=he.getPosition().distance(Vn),o=Jn?.035:.12,i=Jn?.08:.24,s=Math.max(i,n*o);Yn.copy(Vn).addScaled(Wn,.01),An.setPosition(Yn),An.setLocalScale(s,.45*s,s),Ln.opacity=Qn,Jn?Ln.emissive.copy(Jn):Ln.emissive.set(1,1,1),Ln.update(),Hn.setFromDirections(t.Vec3.UP,Wn),An.setRotation(Hn),An.enabled=!0}else An.enabled=!1;gt&&mt.size>0&&(!function(){if(0===mt.size)return;Ra.length=0;for(const t of Ca){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;Ia||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;Ra.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})}!Ia&&Ra.length>0&&(Ia=!0,console.log("[Relighting] Total lights synced:",Ra.length));for(const t of mt)t.enabled&&t.updateLights(Ra)}(),!ft&&ut&&(ft=!0,console.log("[StorySplat Viewer] Relighting sync active, lights:",Ra.length,"fade:",ut.relightFade,"ambient:",[ut.ambientR,ut.ambientG,ut.ambientB],"material:",!!ut.material,"totalScripts:",mt.size)))}),U.on("postrender",()=>{if(eo)return;const e=Zn&&!q&&("explore"===Sn||"walk"===Sn),n=U.graphicsDevice.canvas;if(!e||On||!n)return;if(Un++,Un<4)return;Un=0,On=!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(In*o),l=a?Math.floor(.5*n.clientHeight*o):Math.floor(Fn*o);try{Mn.resize(i,s);const e=U.scene.layers.getLayerByName("World");if(!e)return void(On=!1);Mn.prepare(he.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([Mn.getWorldPointAsync(r,l),Mn.getWorldPointAsync(o,l),Mn.getWorldPointAsync(r,c),Mn.getWorldPointAsync(a,l),Mn.getWorldPointAsync(r,d)]).then(e=>{if(On=!1,vt)return;const n=he.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){Bn.copy(i);let s=!1;const a=e.filter((t,e)=>t!==i&&o(t));a.length>=2&&(Gn.sub2(a[0],i),jn.sub2(a[1],i),Gn.lengthSq()>1e-8&&jn.lengthSq()>1e-8&&(Xn.cross(Gn,jn).normalize(),qn.sub2(n,i),Xn.dot(qn)<0&&Xn.mulScalar(-1),Xn.lengthSq()>.5&&(Nn.copy(Xn),s=!0))),!s&&Nn.lengthSq()<.5&&Nn.copy(t.Vec3.UP),$n=!0}}).catch(()=>{On=!1})}catch(t){On=!1}});const Ao=60;U.on("joystick:left",(t,e,n,o)=>{if(t<0||e<0)V(D,!1,0,0,Ao),"walk"===Sn&&vn&&vn.setJoystickMove(0,0);else{const i=n-t,s=o-e;V(D,!0,i,s,Ao),"walk"===Sn&&vn&&vn.setJoystickMove(i/Ao,-s/Ao)}}),U.on("joystick:right",(t,e,n,o)=>{t<0||e<0?($(D,!1),"walk"===Sn&&vn&&vn.setJoystickLook(0,0)):($(D,!0),"walk"===Sn&&vn&&vn.setJoystickLook((n-t)/Ao,(o-e)/Ao))});const Lo=D.scrollControls?.querySelectorAll(".storysplat-explore-btn");let Ro=It||"fly",zo=null,Do=!0,Io=!1;const Fo=t=>{Ro=t,Lo?.forEach(e=>{const n=e.getAttribute("data-explore-mode");e.classList.toggle("selected",n===t)})},Bo=t=>{E(t)?("walk"===Sn&&Vo("explore"),"orbit"===t&&yn()||un.setMode(t),Fo(t),q?B(D,"fly"===t):(O(D,"fly"===t),N(D,"orbit"===t)),H(D)):console.warn(`[StorySplat Viewer] Explore ${t} mode is disabled for this scene.`)};function Vo(e){ne&&!oe&&qo(),"tour"===Sn&&"tour"!==e&&nt&&Ss(),An.enabled=!1,$n=!1,Qn=0,"walk"===Sn&&vn?(vn.disable(),ln()&&(un.mobileInputLayout="joystick-touch")):"explore"===Sn&&un.disable(),Sn=e,console.log("[StorySplat Viewer] Switching to mode:",e);const n=ln();if("walk"===e&&vn){_n=!1,un.disable(),n?(un.detachInputSources(),un.reattachMobileInput(),un.mobileInputLayout="joystick-joystick"):un.detachInputSources();const t=r.walkCameraSettings;if(r.walkSpawnOverride&&t?.cameraPosition){const e=t.cameraPosition;if(he.setPosition(e.x,e.y,e.z),t.rotation){const e=t.rotation;he.setEulerAngles(e.x,e.y,e.z)}console.log("[StorySplat Viewer] Walk mode: positioned from walkCameraSettings",e)}vn.enable(),Fo("walk"),n&&B(D,!0),O(D,!1),N(D,!1),H(D)}else if("explore"===e){vn&&vn.disable(),qi=0,Yi=0,Zi=!1,_n=!1,un.disable(),un.enable(),un.enableOrbit=E("orbit"),un.enableFly=E("fly"),un.enablePan=!0;const e=It||"fly",o=Do;Do&&"orbit"===e&&(Xe?.cameraPosition&&Xe?.pivotPoint?(Ye(),console.log("[StorySplat Viewer] Orbit first entry: positioned from saved settings")):ts.length>0&&(he.setPosition(ts[0]),he.setRotation(es[0]),console.log("[StorySplat Viewer] Orbit first entry: positioned at waypoint[0]")));const i=he.getPosition().clone(),s=he.getRotation().clone();un.syncFromPose(i,s);const a=async()=>{try{const t=.25;Cn.resize(Math.floor(Mr.clientWidth*t),Math.floor(Mr.clientHeight*t));const e=U.scene.layers.getLayerByName("World");if(e){Cn.prepare(he.camera,U.scene,[e]);const n=Math.floor(.5*Mr.clientWidth*t),o=Math.floor(.5*Mr.clientHeight*t),i=await Cn.getWorldPointAsync(n,o);if(i){const t=he.getPosition().distance(i);t>.5&&t<500&&(un.setFocusDistance(i,t),console.log("[StorySplat Viewer] Updated focus distance:",t.toFixed(2)))}}}catch(t){}};At||Do||Xe?.pivotPoint||a();const r=Do?e:L(zo??("walk"===Ro?void 0:Ro));if(zo=null,Do=!1,"orbit"===r&&yn()?console.log("[StorySplat Viewer] Restored saved orbit view"):un.setMode(r),!Xe?.cameraPosition&&o&&"orbit"===r&&Xe?.pivotPoint){const e=Xe.pivotPoint;un.syncFromCamera(new t.Vec3(e.x,e.y,e.z),!0,!0),un.setOrbitRotationLimits(Xe.rotationLimits??null,!0),gn(),console.log("[StorySplat Viewer] Re-applied saved orbit pivot after setMode:",e)}Fo(r),n?B(D,"fly"===r):(O(D,"fly"===r),N(D,"orbit"===r))}else un.enable(),un.disable(),vn&&vn.disable(),_n=!0,0===ts.length&&r.waypoints&&r.waypoints.length>0&&(r.waypoints.forEach(e=>{const n=e.position?new t.Vec3(e.position.x,e.position.y,-e.position.z):new t.Vec3(0,r.playerHeight||1.6,0);ts.push(n);const o=e.rotation?Jo(e.rotation):new t.Quat;es.push(o);let i=e.fov||os;i<3.5&&(i*=180/Math.PI),ns.push(i)}),console.log("[StorySplat Viewer] Rebuilt waypoint arrays from config:",ts.length)),as(ki),Gi&&ji&&(he.setPosition(Gi),he.setRotation(ji),he.camera&&ns.length>0&&(he.camera.fov=is)),B(D,!1),O(D,!1),N(D,!1),H(D);i.emit("modeChange",{mode:e}),Gs&&Xs&&"tour"!==e&&Gs.pause()}function $o(e){if(e!==ne&&!oe)if(oe=!0,console.log("[StorySplat Viewer] Panorama mode:",e?"entering":"exiting"),e){ae=Sn,le=Ro,ce=un.pitchRange.clone(),de=un.zoomRange.clone(),"walk"===Sn&&vn&&vn.disable(),_n=!1,qi=0,Yi=0,Zi=!1;const e=he.getPosition().clone(),n=he.getRotation().clone();un.disable(),un.enable(),un.enableOrbit=!0,un.setMode("orbit"),un.syncFromPose(e,n),un.setFocusDistance(e,.001,!0),un.enablePan=!1,un.enableFly=!1,un.zoomRange=new t.Vec2(.001,.001),un.pitchRange=new t.Vec2(-85,85),Sn="explore",B(D,!1),O(D,!1),N(D,!1),H(D);for(let t=0;t<nn.children.length;t++){const e=nn.children[t];e!==ot&&e!==fa&&e.enabled&&(e.enabled=!1,e.__panoramaHidden=!0)}for(const[,t]of Ut)t.enabled&&(t.enabled=!1,t.__panoramaHidden=!0);Te&&Te.entity.enabled&&(Te.entity.enabled=!1,Te.entity.__panoramaHidden=!0),ot&&ai(ot,-1,()=>{ot&&(ot.enabled=!1)}),ne=!0,oe=!1,i.emit("panoramaModeChange",{enabled:!0}),console.log("[StorySplat Viewer] Panorama mode active")}else{ot&&(ot.enabled=!0);for(let t=0;t<nn.children.length;t++){const e=nn.children[t];e.__panoramaHidden&&(e.enabled=!0,delete e.__panoramaHidden)}for(const[,t]of Ut)t.__panoramaHidden&&(t.enabled=!0,delete t.__panoramaHidden);if(Te&&Te.entity.__panoramaHidden&&(Te.entity.enabled=!0,delete Te.entity.__panoramaHidden),ot&&ai(ot,1),un.enablePan=!0,un.enableOrbit=E("orbit"),un.enableFly=E("fly"),un.pitchRange=ce||new t.Vec2(-89,89),un.zoomRange=de||new t.Vec2(.01,1/0),ce=null,de=null,ae&&"explore"!==ae)Vo(ae);else{const t=le||"fly";if("walk"===t)Vo("walk");else{const e=L(t);un.setMode(e),Fo(e);ln()?B(D,"fly"===e):(O(D,"fly"===e),N(D,"orbit"===e))}}ne=!1,oe=!1,ae=null,le=null,i.emit("panoramaModeChange",{enabled:!1})}}Lo?.forEach(t=>{t.addEventListener("click",()=>{if("explore"!==Sn&&"walk"!==Sn)return;const e=t.getAttribute("data-explore-mode");"walk"===e?("walk"!==Sn&&Vo("walk"),Fo("walk")):("walk"===Sn&&Vo("explore"),Bo(e))})}),U.on("cameracontrols:modechange",e=>{if("orbit"!==e&&"fly"!==e||(Fo(e),"explore"!==Sn&&"walk"!==Sn||(q?B(D,"fly"===e):(O(D,"fly"===e),N(D,"orbit"===e)),H(D))),!("orbit"!==e||Xe?.cameraPosition&&Xe.pivotPoint)){let e=!1;if($n&&Bn){const n=he.getPosition(),o=n.distance(Bn);if(o>.3&&o<500){const i=he.forward,s=new t.Vec3(n.x+i.x*o,n.y+i.y*o,n.z+i.z*o);un.syncFromCamera(s),e=!0}}if(!e&&!At)try{const t=.25,e=Math.floor(.5*Mr.clientWidth*t),n=Math.floor(.5*Mr.clientHeight*t);Cn.resize(Math.floor(Mr.clientWidth*t),Math.floor(Mr.clientHeight*t));const o=U.scene.layers.getLayerByName("World");o&&(Cn.prepare(he.camera,U.scene,[o]),Cn.getWorldPointAsync(e,n).then(t=>{if(!vt&&t&&isFinite(t.x)&&isFinite(t.y)&&isFinite(t.z)){const e=he.getPosition().distance(t);e>.3&&e<500&&un.syncFromCamera(t)}}).catch(()=>{}))}catch(t){}}});let Uo=null,Oo=null,No=null,Wo=!1,Ho=null;const Go=new Map;function jo(t){const e=Go.get(t);if(e)return Promise.resolve(e);const n=new Image;n.crossOrigin="anonymous",n.src=t;const o=(n.decode?n.decode():new Promise((t,e)=>{n.complete&&n.naturalWidth>0?t():(n.onload=()=>t(),n.onerror=e)})).then(()=>(Go.set(t,n),n)).catch(e=>{throw Go.delete(t),e});return Go.set(t,o),o}function Xo(t){if(ne||oe||Wo)return void console.warn("[Portal] Panorama activation skipped — already active or in-flight",{panoramaModeEnabled:ne,panoramaModeTransitioning:oe,panoramaLoadPending:Wo});const n=t.panoramaUrl;if(!n)return void console.warn("[Portal] Panorama portal has no panoramaUrl",t);console.log("[StorySplat Viewer] Panorama portal activated:",t.title||t.id,n),Uo=t,Wo=!0,Ho&&(clearTimeout(Ho),Ho=null);const o=Go.get(n);if(!(o instanceof HTMLImageElement&&o.complete&&o.naturalWidth>0)){const t=document.createElement("div");t.className="storysplat-panorama-loading";const n=document.createElement("div");n.className="storysplat-panorama-loading-spinner",t.appendChild(n),e.appendChild(t),No=t}const i=()=>{if(!No)return;const t=No;No=null,t.classList.add("storysplat-panorama-loading--hiding"),setTimeout(()=>t.remove(),200)};jo(n).then(o=>{if(Wo=!1,vt||Uo!==t)return;var s,a;s=n,a=o,r.skybox={url:s,rotation:0,intensity:1,enableIBL:!1},r.skyboxUrl=s,r.skyboxRotation=0,xa(a),$o(!0),i();const l=document.createElement("button");l.className="storysplat-panorama-exit-btn",l.textContent=x(u,"exitPanorama"),l.setAttribute("aria-label",x(u,"exitPanorama")),l.addEventListener("click",()=>qo()),e.appendChild(l),Oo=l;const c=t=>{"Escape"===t.key&&qo()};document.addEventListener("keydown",c),l._escHandler=c}).catch(t=>{Wo=!1,console.warn("[Portal] Failed to load panorama image",n,t),i(),Uo=null})}function qo(){if(!ne&&!oe)return;$o(!1);const t=()=>{Ho=null,va?_a(va,ba):wa()};if(Ho&&(clearTimeout(Ho),Ho=null),ot?Ho=setTimeout(t,1200):t(),Oo){const t=Oo._escHandler;t&&document.removeEventListener("keydown",t),Oo.remove(),Oo=null}Uo=null,console.log("[StorySplat Viewer] Exited panorama portal mode")}const Yo=(t,e)=>{D.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||b.loading} ${Math.round(a)}%`)}(D.preloader,t,e,x(u,"loading")),i.emit("progress",{progress:t,text:e})};function Zo(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 Ko(e,n,o){const i=new t.Entity("splat");i.addComponent("gsplat",{asset:e,unified:!0});const s=i.gsplat;s&&pn(n)&&(s.lodBaseDistance=Q,s.lodMultiplier=J);const a=r.scale||{x:1,y:1,z:1},l=r.invertXScale||!1,c=r.invertYScale||!1,d={x:l?-a.x:a.x,y:c?a.y:-a.y,z:l!==c?a.z:-a.z};i.setLocalScale(d.x,d.y,d.z);const p=r.position||[0,0,0];i.setPosition(p[0],p[1],-p[2]);const h=r.rotation||[0,0,0];return fi(i,[h[0]||0,h[1]||0,h[2]||0],o),nn.addChild(i),i.gsplat?.material&&i.gsplat.material.setParameter("alphaClip",.01),i}function Qo(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 Jo(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}Lt(o.revealEffect||n.revealEffect||r.revealEffect||"none",o.revealStyle||n.revealStyle||r.revealStyle||"bloom");const ti=.75,ei=1.5,ni=50,oi=-50,ii=1;function si(e,n,o,i){e.script||e.addComponent("script");let s=e.script?.gsplatWipeTransition;if(!s){const n=function(){if(wt)return wt;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 mirrorClipSplat(modifiedCenter, 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 mirrorClipSplat(modifiedCenter, scale);\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?ht:pt;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}else o=("wgsl"===n?lt:rt)+o;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()}}),wt=e,e}();s=e.script?.create?.(n)??void 0}s?(s.enabled=!1,s.mode=n,s.duration=ti,s.bandWidth=ei,s.wipeTop=o?ni:oi,s.wipeBottom=o?oi:ni,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 ai(e,n,o){e.script||e.addComponent("script");let i=e.script?.gsplatDissolveTransition;if(!i){const n=function(){if(St)return St;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 mirrorClipSplat(modifiedCenter, 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 mirrorClipSplat(modifiedCenter, scale);\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?ht:pt;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}else o=("wgsl"===n?lt:rt)+o;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()}}),St=e,e}();i=e.script?.create?.(n)??void 0}i?(i.enabled=!1,i.mode=n,i.duration=ii,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 ri=r.swapTransitionType||"dissolve";let li=!1!==r.enableSwapTransition;function ci(t,e,n){li?"scanline"===ri?function(t,e,n=!0){si(t,-1,n,()=>{t.enabled=!1,console.log("[WipeTransition] Old splat hidden after wipe")}),si(e,1,n),console.log(`[WipeTransition] Dual wipe swap started (${n?"forward":"backward"})`)}(t,e,n):function(t,e){ai(t,-1,()=>{t.enabled=!1}),ai(e,1),console.log("[DissolveTransition] Cross-dissolve swap started")}(t,e):t.enabled=!1}function di(t,e){li&&("scanline"===ri?si(t,1,e):ai(t,1))}function pi(t){t.enabled=!1,console.log("[SplatSwap] Splat hidden")}function hi(t){const e=Ut.get(t);if(e){const n=e.script?.gsplatRelighting;n&&mt.delete(n),e.destroy(),Ut.delete(t),console.log("[SplatSwap] Splat disposed:",t)}}function ui(t){const e=Ut.get(t);return!!e&&(e.enabled=!0,Bt=t,console.log("[SplatSwap] Splat shown:",t),!0)}function mi(){if(Bt){const t=Ut.get(Bt);if(t&&t.enabled)return t}return ot&&ot.enabled?ot:null}async function gi(t){if(!pn(t))return null;try{const e=await fetch(t);if(!e.ok)return null;const n=(await e.json()).storySplatCoordinateCorrection;if(n&&Array.isArray(n.eulerDegrees)&&3===n.eulerDegrees.length&&n.eulerDegrees.every(t=>"number"==typeof t&&Number.isFinite(t)))return n}catch(t){console.warn("[SPLAT] Could not read LOD coordinate correction metadata:",t)}return null}function fi(e,n,o){const i=[n[0]*(180/Math.PI),n[1]*(180/Math.PI),-n[2]*(180/Math.PI)];if(o?.eulerDegrees){const n=(new t.Quat).setFromEulerAngles(i[0],i[1],i[2]),s=(new t.Quat).setFromEulerAngles(o.eulerDegrees[0],o.eulerDegrees[1],o.eulerDegrees[2]);return e.setRotation((new t.Quat).mul2(n,s)),void console.log("[SPLAT] Applied coordinate correction:",o)}e.setEulerAngles(i[0],i[1],i[2])}async function yi(e,n){if(!Ut.has(e)&&e!==Bt&&!vt){console.log("[SplatSwap] Preloading splat:",e);try{const o=new t.Asset("splat-preload-"+Date.now(),"gsplat",{url:e});await new Promise((i,s)=>{o.ready(()=>{if(vt)return void s(new Error("Viewer destroyed"));const a=new t.Entity("splat-preload");a.addComponent("gsplat",{asset:o});const l=n?.scale||r.scale||{x:1,y:1,z:1},c=r.invertXScale||!1,d=r.invertYScale||!1,p={x:c?-l.x:l.x,y:d?l.y:-l.y,z:c!==d?l.z:-l.z};a.setLocalScale(p.x,p.y,p.z);const h=n?.position||r.position||[0,0,0];a.setPosition(h[0],h[1],-h[2]);const u=n?.rotation||r.rotation||[0,0,0],m=[u[0]*(180/Math.PI),u[1]*(180/Math.PI),-u[2]*(180/Math.PI)];a.setEulerAngles(m[0],m[1],m[2]),a.enabled=!1,nn.addChild(a),Da(a),Ut.set(e,a),console.log("[SplatSwap] Preload complete:",e),i()}),o.on("error",t=>{console.error("[SplatSwap] Preload error:",t),s(t)}),U.assets.add(o),U.assets.load(o)})}catch(t){console.error("[SplatSwap] Error preloading:",e,t)}}}function vi(t,e=!1){if("explore"!==Sn)return;const n=L(e?It:t||It);if(n&&un){un.mode!==n&&(un.setMode(n),console.log(`[SplatSwap] Switching explore sub-mode to: ${n}`),Fo(n),q?B(D,"fly"===n):(O(D,"fly"===n),N(D,"orbit"===n)),H(D))}}async function bi(e,n=!0,o){if(e===Bt)return;if($t)return;if(vt)return;let s=!1;$t=!0,console.log(`[SplatSwap] Switching to splat (${n?"fwd":"bwd"}):`,e);try{const a=mi();if(Ut.has(e)){ui(e),i.emit("splatChange",{url:e,isOriginal:!1});const t=Ut.get(e);a&&a!==t?ci(a,t,n):di(t,n)}else{const s=new t.Asset("splat-swap-"+Date.now(),"gsplat",{url:e});await new Promise((l,c)=>{s.ready(()=>{if(vt)return void c(new Error("Viewer destroyed"));const d=new t.Entity("splat-swap");d.addComponent("gsplat",{asset:s});const p=o?.scale||r.scale||{x:1,y:1,z:1},h=r.invertXScale||!1,u=r.invertYScale||!1,m={x:h?-p.x:p.x,y:u?p.y:-p.y,z:h!==u?p.z:-p.z};d.setLocalScale(m.x,m.y,m.z);const g=o?.position||r.position||[0,0,0];d.setPosition(g[0],g[1],-g[2]);const f=o?.rotation||r.rotation||[0,0,0],y=[f[0]*(180/Math.PI),f[1]*(180/Math.PI),-f[2]*(180/Math.PI)];d.setEulerAngles(y[0],y[1],y[2]),nn.addChild(d),Da(d),Ut.set(e,d),Bt=e;const v=100*ki,b=$i(ki),x=zt.find(t=>t.url===e);!x||(-1!==x.waypointIndex?b>=x.waypointIndex:-1===x.percentage||v>=x.percentage)?(a?ci(a,d,n):di(d,n),i.emit("splatChange",{url:e,isOriginal:!1}),console.log("[SplatSwap] New splat loaded and shown:",e)):(d.enabled=!1,console.log("[SplatSwap] Load completed but user already past swap point - skipping forward transition:",e)),l()}),s.on("error",t=>{console.error("[SplatSwap] Load error:",t),c(t)}),U.assets.add(s),U.assets.load(s)})}const l=zt.findIndex(t=>t.url===e);-1!==l&&async function(t){if(!zt||0===zt.length)return;const e=(t+1)%zt.length,n=zt[e];n&&n.url&&await yi(n.url,n)}(l),Ot=null,s=!0}catch(t){Ot=e,console.error("[SplatSwap] Error switching splat:",t)}finally{$t=!1,Nt=-1,Wt=-1,s&&wi()}}function xi(){if(Vt)return Vt;const t=ln()||!0===r.forceMobilePreview,e=Boolean(r.mobileLodMetaUrl||r.mobileSogUrl||r.mobileSplatUrl||r.mobileCompressedPlyUrl),n=t&&e,o=n?r.mobileLodMetaUrl:r.lodMetaUrl,i=n?r.mobileSogUrl:r.sogUrl,s=n?r.mobileCompressedPlyUrl:void 0,a=n?r.mobileSplatUrl:r.splatUrl;return o||(i||(s||(a||(r.fallbackUrls&&r.fallbackUrls.length>0?r.fallbackUrls[0]:""))))}function wi(){if(Gt)return;if(!zt||0===zt.length)return;const t=100*ki,e=$i(ki);if(Math.abs(t-Nt)<.1&&e===Wt)return;Nt=t,Wt=e;let n=null,o=null,s=-1/0,a=-1/0;for(const i of zt)-1!==i.waypointIndex?e>=i.waypointIndex&&i.waypointIndex>s&&(s=i.waypointIndex,n=i):-1!==i.percentage&&t>=i.percentage&&i.percentage>a&&(a=i.percentage,o=i);const r=o||n,l=r&&"__ORIGINAL__"===r.url,c=xi(),d=r?l?c:r.url:c;if(Ot&&d!==Ot&&(Ot=null),(!d||d!==Ot||d===Bt)&&d&&d!==Bt){const e=t>=Ht;if(Ht=t,d===c&&ot&&!Bt)Bt=c;else if(d===c&&ot){const t=mi();ot.enabled=!0,Bt=c,t&&t!==ot?(ci(t,ot,e),Ut.forEach((e,n)=>{n!==c&&e!==t&&(Dt?pi(e):hi(n))})):(di(ot,e),Ut.forEach((t,e)=>{e!==c&&(Dt?pi(t):hi(e))})),i.emit("splatChange",{url:c,isOriginal:!0}),console.log("[SplatSwap] Returned to primary splat"),vi(void 0,!0)}else{bi(d,e,r&&(r.position||r.rotation||r.scale)?{position:r.position,rotation:r.rotation,scale:r.scale}:void 0),r&&vi(r.defaultExploreMode,!1)}r&&r.skyboxUrl&&!l?Pi(r.skyboxUrl,r.skyboxRotation||0):l&&va&&Pi(va,ba)}}function Si(e,n){e.script||e.addComponent("script");let o=e.script?.gsplatCompareClip;if(!o){const n=function(){if(Mt)return Mt;const e=t.createScript("gsplatCompareClip");return Object.assign(e.prototype,{material:null,_effectInitialized:!1,_shadersNeedApplication:!1,side:-1,position:.5,softness:.001,orientation:0,initialize(){this.material=null,this._effectInitialized=!1,this._shadersNeedApplication=!1,this.on("enable",()=>{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.enabled){let t=!1;const e=this.entity.gsplat;if(e){const n=!0===e.unified?this.app.scene.gsplat?.material??null:e.material;if(n&&n!==this.material)this.material=null,t=!0;else if(this.material){const e=this.app.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl",o=this.material.getShaderChunks(n).get("gsplatModifyVS");o&&o.includes("uComparePosition")||(t=!0)}}t&&(this._shadersNeedApplication=!0,this._applyShaders(),this.material&&(this._shadersNeedApplication=!1))}this.material&&(this._updateUniforms(),this.material.update())}else this.entity.gsplat&&(this._effectInitialized=!0,this.enabled&&this._shadersNeedApplication&&(this._applyShaders(),this.material&&(this._shadersNeedApplication=!1)))},_updateUniforms(){this._setUniform("uComparePosition",this.position),this._setUniform("uCompareSide",this.side),this._setUniform("uCompareSoftness",this.softness),this._setUniform("uCompareOrientation",this.orientation),this._cameraEntity||(this._cameraEntity=this.app.root.findByName("camera")??null);const e=this._cameraEntity?.camera;if(e)if(this._mvpMat||(this._mvpMat=new t.Mat4,this._vpMat=new t.Mat4),this._vpMat.mul2(e.projectionMatrix,e.viewMatrix),!0===this.entity.gsplat?.unified)this._setUniform("uCompareVP",this._vpMat.data);else{const t=this.entity.getWorldTransform();this._mvpMat.mul2(this._vpMat,t),this._setUniform("uCompareVP",this._mvpMat.data)}},updatePosition(t){this.position=t},updateOrientation(t){this.orientation=t},getShaderGLSL:()=>"\nuniform float uComparePosition; // 0.0 to 1.0 screen-space position of the divider\nuniform float uCompareSide; // -1.0 = show left/top of line, +1.0 = show right/bottom\nuniform float uCompareSoftness; // Soft edge width in screen space (~0.001 for crisp)\nuniform float uCompareOrientation; // 0.0 = vertical (clip on X), 1.0 = horizontal (clip on Y)\nuniform mat4 uCompareVP; // model-view-projection matrix (uploaded per-frame by script)\n\nvoid modifySplatCenter(inout vec3 center) {\n // No position modification\n}\n\nvoid modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) {\n mirrorClipSplat(modifiedCenter, scale);\n\n // Project center to clip space using our own MVP uniform\n // (engine uniforms matrix_model/view/projection are not in scope for gsplatModifyVS chunks)\n vec4 clipPos = uCompareVP * vec4(modifiedCenter, 1.0);\n float ndcX = clipPos.x / clipPos.w; // -1 to +1\n float ndcY = clipPos.y / clipPos.w; // -1 to +1\n\n // Convert to 0-1 screen space\n float screenX = ndcX * 0.5 + 0.5;\n float screenY = 1.0 - (ndcY * 0.5 + 0.5); // Flip Y: 0 = top, 1 = bottom\n\n // Pick axis based on orientation\n float screenPos = mix(screenX, screenY, uCompareOrientation);\n\n // Signed distance from divider, accounting for which side this splat belongs to\n float dist = (screenPos - uComparePosition) * uCompareSide;\n\n if (dist < -uCompareSoftness) {\n // Wrong side of the divider — hide\n scale = vec3(0.0);\n } else if (dist < uCompareSoftness) {\n // Soft edge band — scale down for anti-aliasing\n float t = smoothstep(-uCompareSoftness, uCompareSoftness, dist);\n scale *= t;\n }\n // else: fully visible\n}\n\nvoid modifySplatColor(vec3 center, inout vec4 color) {\n // No color modification — clean split with no edge tint\n}\n",getShaderWGSL:()=>"\nuniform uComparePosition: f32;\nuniform uCompareSide: f32;\nuniform uCompareSoftness: f32;\nuniform uCompareOrientation: f32;\nuniform uCompareVP: mat4x4f;\n\nfn modifySplatCenter(center: ptr<function, vec3f>) {\n // No position modification\n}\n\nfn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr<function, vec4f>, scale: ptr<function, vec3f>) {\n mirrorClipSplat(modifiedCenter, scale);\n\n // Project center to clip space using our own MVP uniform\n let clipPos = uniform.uCompareVP * vec4f(modifiedCenter, 1.0);\n let ndcX = clipPos.x / clipPos.w;\n let ndcY = clipPos.y / clipPos.w;\n\n let screenX = ndcX * 0.5 + 0.5;\n let screenY = 1.0 - (ndcY * 0.5 + 0.5);\n\n let screenPos = mix(screenX, screenY, uniform.uCompareOrientation);\n let dist = (screenPos - uniform.uComparePosition) * uniform.uCompareSide;\n\n if (dist < -uniform.uCompareSoftness) {\n *scale = vec3f(0.0);\n } else if (dist < uniform.uCompareSoftness) {\n let t = smoothstep(-uniform.uCompareSoftness, uniform.uCompareSoftness, dist);\n *scale *= t;\n }\n}\n\nfn modifySplatColor(center: vec3f, color: ptr<function, vec4f>) {\n // No color modification\n}\n",_applyShaders(){const t=this.entity.gsplat;if(!t)return;const e=this.entity.script&&this.entity.script.gsplatRevealBloom,n=this.entity.script&&this.entity.script.gsplatRevealRadial;if(e&&e.enabled&&(e.enabled=!1),n&&n.enabled&&(n.enabled=!1),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?ht:pt;o="wgsl"===n?t+"\n"+o.replace(/fn modifySplatColor\(center: vec3f, color: ptr<function, vec4f>\) \{\s*\/\/ No color modification\s*\}/,"fn modifySplatColor(center: vec3f, color: ptr<function, vec4f>) {\n applyRelighting(center, color);\n}"):t+"\n"+o.replace(/void modifySplatColor\(vec3 center, inout vec4 color\) \{\s*\/\/ No color modification[\s\S]*?\}/,"void modifySplatColor(vec3 center, inout vec4 color) {\n applyRelighting(center, color);\n}"),i._shaderManagedExternally=!0}else o=("wgsl"===n?lt:rt)+o;t.getShaderChunks(n).set("gsplatModifyVS",o),t.update(),t.setParameter("uComparePosition",this.position),t.setParameter("uCompareSide",this.side),t.setParameter("uCompareSoftness",this.softness),t.setParameter("uCompareOrientation",this.orientation),t.setParameter("uCompareVP",new Float32Array([1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]))},_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()}}),Mt=e,e}();o=e.script?.create?.(n)??void 0}return o&&(o.side=n,o.position=Xt,o.orientation="horizontal"===te.orientation?1:0,o.enabled=!0),o||null}function _i(t){Xt=Math.max(0,Math.min(1,t)),Yt&&Yt.updatePosition(Xt),Zt&&Zt.updatePosition(Xt),i.emit("comparePositionChange",{position:Math.round(100*Xt)})}async function Ci(t,n){const o=++ee;if(console.log("[CompareMode] Initializing with after splat:",t),await yi(t,n),o!==ee||vt)return void console.log("[CompareMode] Init cancelled (superseded or destroyed)");const i=Ut.get(t);if(!i)return console.error("[CompareMode] Failed to load after splat:",t),void(Gt=!1);if(i.enabled=!0,Kt=i,Jt=t,ot)Yt=Si(ot,-1);else{console.warn("[CompareMode] Primary splat entity not ready yet — will attach before-clip when available");const t=setInterval(()=>{if(vt||o!==ee)return clearInterval(t),void clearTimeout(e);ot&&!Yt&&(Yt=Si(ot,-1),console.log("[CompareMode] Attached before-clip to primary splat (deferred)"),clearInterval(t),clearTimeout(e))},100),e=setTimeout(()=>clearInterval(t),1e4)}Zt=Si(i,1),jt&&(jt.destroy(),jt=null),e.querySelectorAll(".ss-compare-overlay").forEach(t=>t.remove()),jt=new Rt({container:e,config:te,onPositionChange:t=>{_i(t)}}),console.log("[CompareMode] Initialized successfully")}function Mi(){if(ee++,Yt&&(Yt.enabled=!1,Yt=null),Zt&&(Zt.enabled=!1,Zt=null),Kt){const t=Kt.script?.gsplatRelighting;t&&mt.delete(t),Kt.destroy(),Jt&&Ut.delete(Jt),Kt=null,Jt=null}jt&&(jt.destroy(),jt=null)}function Ei(){const t=xi();if(Bt===t)return;const e=mi();ot&&(ot.enabled=!0,e&&e!==ot?(ci(e,ot,!1),Ut.forEach((n,o)=>{o!==t&&n!==e&&(Dt?pi(n):hi(o))})):(di(ot,!1),Ut.forEach((e,n)=>{n!==t&&(Dt?pi(e):hi(n))}))),Bt=t,Ht=0,i.emit("splatChange",{url:t,isOriginal:!0}),console.log("[SplatSwap] Manually returned to original splat"),vi(void 0,!0)}function Pi(t,e=0){console.log("[SplatSwap] Applying skybox:",t,"rotation:",e),_a(t,e)}let ki=0,Ti=0,Ai=!1,Li=null,Ri=null;const zi=r.waypoints?.reduce((t,e)=>t+(e.duration||2e3),0)||1,Di=r.loopMode;let Ii;Ii=!0===Di?"loop":!1===Di?"none":"loop"===Di||"pingpong"===Di||"none"===Di?Di:"loop";const Fi=(t=Ii,e=r.waypoints?.length||1)=>e<2?1:"loop"===t?e:e-1,Bi=(t,e=Ii)=>"loop"===e?(t%1+1)%1:Math.max(0,Math.min(1,t)),Vi=t=>{const e=r.waypoints?.length||1;if(e<2)return 0;return Math.max(0,Math.min(t,e-1))/Fi(Ii,e)},$i=t=>{const e=r.waypoints?.length||1;if(e<2)return 0;const n=Fi(Ii,e),o=Bi(t)*n,i=Math.round(o);return"loop"===Ii?(i%e+e)%e:Math.max(0,Math.min(e-1,i))},Ui=(t,e,n)=>{if(e===n)return Bi(t,n);const o=r.waypoints?.length||1,i=Fi(e,o),s=Fi(n,o),a=Bi(t,e)*i;return Bi(a/s,n)},Oi=r.waypoints?.length||1,Ni=Math.max(1,20*Fi(Ii,Oi));let Wi=void 0!==r.autoplaySpeed?60*r.autoplaySpeed/Ni:1e3/zi,Hi=1;console.log("[StorySplat Viewer] Playback config:",{loopMode:Ii,playbackSpeed:Wi,totalDuration:zi,autoPlay:r.autoPlay,autoplaySpeed:r.autoplaySpeed,rawLoopMode:r.loopMode});let Gi=null,ji=null;const Xi=.01+.1*(r.transitionSpeed||1);let qi=0,Yi=0;let Zi=!1,Ki=!1,Qi=0,Ji=0;const ts=[],es=[],ns=[],os=r.fov||60;let is=os;function ss(){const t=function(t){if(ts.length<2)return null;const e=an(ts,r.waypoints),n=Fi(Ii,ts.length),o=Qe(e,20,Ii);if(o.length<2)return null;let i=1/0,s=0;for(let e=0;e<o.length-1;e++){const a=o[e],r=o[e+1],l=r.x-a.x,c=r.y-a.y,d=r.z-a.z,p=l*l+c*c+d*d;if(0===p)continue;let h=((t.x-a.x)*l+(t.y-a.y)*c+(t.z-a.z)*d)/p;h<0?h=0:h>1&&(h=1);const u=t.x-(a.x+l*h),m=t.y-(a.y+c*h),g=t.z-(a.z+d*h),f=Math.sqrt(u*u+m*m+g*g);if(f<i){i=f;const t=(a.segmentIndex+a.t)/n;s=t+((r.segmentIndex+r.t)/n-t)*h}}return i===1/0?null:{progress:s,distance:i}}(he.getPosition().clone());t&&(ki=t.progress,Ti=t.progress),Vo("tour")}function as(n){if(!_n||ts.length<2)return;const o=ts.length,s="loop"===Ii,a=s?o:o-1,l=(n=s?(n%1+1)%1:Math.max(0,Math.min(1,n)))*a,c=Math.min(Math.floor(l),a-1),d=l-c,p=an(ts,r.waypoints),h=Ge(p,c,Ii),u=Ke(p,c,d,Ii);if(!h||!u)return;const m=es[h.startIndex],g=es[h.endIndex],f=ns[h.startIndex],y=ns[h.endIndex];Gi=new t.Vec3(u.x,u.y,u.z),ji=new t.Quat,ji.slerp(m,g,d),is=mn(f,y,d);const v=$i(n);if(v!==et){const n=et;et=v;const o=r.waypoints[v],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(ts[v].x,ts[v].y,ts[v].z)),i.emit("waypointChange",{index:v,waypoint:o,prevIndex:n,cameraMode:s}),function(t){if(0===Zs.size)return;Zs.forEach((e,n)=>{const{config:o,slotId:i,assetReady:s,waypointIndex:a}=e;if(!s)return;if(o.triggerMode)return;const r=e.entity.sound?.slot(i);if(r)if(a===t)e.playing||r.isPlaying||(console.log(`[Audio] Waypoint-entry play (legacy): ${n}, wp=${t}`),r.play(),e.playing=!0);else{const t=o.stopOnExit??!0;(e.playing||r.isPlaying)&&t&&(console.log(`[Audio] Waypoint-exit stop (legacy): ${n}, wp=${a}`),r.stop(),e.playing=!1)}})}(v),Ks(),Qs(e);const a=e.querySelector(".storysplat-hotspot-popup");a&&a.classList.remove("visible")}i.emit("progressUpdate",{progress:Math.max(0,Math.min(1,n)),index:et}),function(t){if(!Gs||!Xs)return;if(!nt||"tour"!==Sn)return void(Gs.paused||Gs.pause());const{time:e,shouldPlay:n,volume:o}=Ys(t);if(!n)return void(Gs.paused||Gs.pause());const i=Ws.get(Gs)??1;if(Gs.volume=Ns?0:i*o,Gs.paused)Gs.currentTime=e,Ns||Gs.play().catch(()=>{});else{Math.abs(Gs.currentTime-e)>.5&&(Gs.currentTime=e)}}(Math.max(0,Math.min(1,n))),wi()}function rs(t,e=!1){const n=Math.max(0,Math.min(1,t));Ti=n,Xs&&nt&&Gs&&(fs(),ys()),e?cs(n):(ki=n,as(ki))}r.waypoints&&r.waypoints.length>0&&(r.waypoints.forEach(e=>{const n=e.position?new t.Vec3(e.position.x,e.position.y,-e.position.z):new t.Vec3(0,r.playerHeight||1.6,0);ts.push(n);const o=e.rotation?Jo(e.rotation):new t.Quat;es.push(o);let i=e.fov||os;i<3.5&&(i*=180/Math.PI),ns.push(i)}),ts.length>0&&(Gi=ts[0].clone(),ji=es[0].clone(),is=ns[0])),U.on("update",function(){if(!_n)return;if(!Ai){let t=Ti-ki;"loop"===Ii&&(t>.5?t-=1:t<-.5&&(t+=1)),Math.abs(t)>1e-4&&(ki+=t*Xi,"loop"===Ii&&(ki=(ki%1+1)%1),as(ki))}if(!Gi||!ji)return;const e=he.getPosition(),n=new t.Vec3;n.lerp(e,Gi,Xi),he.setPosition(n.x,n.y,n.z);const o=he.camera;if(o&&ns.length>0){const t=mn(o.fov,is,Xi);o.fov=t}Zi||(qi*=.95,Yi*=.95,Math.abs(qi)<.01&&(qi=0),Math.abs(Yi)<.01&&(Yi=0));const i=new t.Quat;i.setFromEulerAngles(Yi,qi,0);const s=new t.Quat;s.mul2(ji,i);const a=he.getRotation(),r=new t.Quat;r.slerp(a,s,Xi),he.setRotation(r)});let ls=500*(r.transitionSpeed||1);function cs(t,e=ls){null!==Li&&(cancelAnimationFrame(Li),Li=null);const n=ki;let o=t-n;"loop"===Ii&&(o>.5?o-=1:o<-.5&&(o+=1));const i=performance.now();Ai=!0;const s=()=>{const t=performance.now()-i,a=Math.min(t/e,1);let r=n+o*(a<.5?2*a*a:(4-2*a)*a-1);"loop"===Ii&&(r=(r%1+1)%1),ki=r,as(ki),a<1?Li=requestAnimationFrame(s):(Li=null,Ai=!1,Ri=null)};Li=requestAnimationFrame(s)}function ds(t){if(!r.waypoints||t<0||t>=r.waypoints.length)return;if(!_n)return;const e=Vi(t);Ti=e,cs(e)}function ps(){if(!r.waypoints||0===r.waypoints.length)return;nt&&Ss();const t=r.scrollButtonMode||"waypoint";if("percentage"===t||"continuous"===t||"incremental"===t){const t=r.scrollAmount||10;let e=Ti+t/100;e>1&&(e="loop"===Ii?0:1),Ti=e,cs(e)}else{let t=(null!==Ri?Ri:et)+1;t>=r.waypoints.length&&(t="loop"===Ii?0:r.waypoints.length-1),Ri=t,ds(t)}}function hs(){if(!r.waypoints||0===r.waypoints.length)return;nt&&Ss();const t=r.scrollButtonMode||"waypoint";if("percentage"===t||"continuous"===t||"incremental"===t){const t=r.scrollAmount||10;let e=Ti-t/100;e<0&&(e="loop"===Ii?1:0),Ti=e,cs(e)}else{let t=(null!==Ri?Ri:et)-1;t<0&&(t="loop"===Ii?r.waypoints.length-1:0),Ri=t,ds(t)}}let us=!1,ms=null;const gs=1e3;function fs(){Xs&&Gs&&nt&&(us=!0,Gs.paused||Gs.pause(),ms&&(clearTimeout(ms),ms=null))}function ys(){us&&(ms&&clearTimeout(ms),ms=setTimeout(()=>{if(ms=null,us=!1,Gs&&Xs&&nt&&"tour"===Sn){const{time:t,shouldPlay:e}=Ys(ki);e&&(Gs.currentTime=t,Ns||Gs.play().catch(()=>{}))}},gs))}let vs=0,bs=null;function xs(t){if(!nt)return;0===vs&&(vs=t);const e=(t-vs)/1e3;vs=t;const n=Xs&&Gs&&!Gs.paused&&!us?js.find(t=>{const e=100*ki;return e>=t.progressStart&&e<=t.progressEnd}):null;if(n&&Gs){const t=n.audioEnd-n.audioStart;if(t>0){const e=(Gs.currentTime-n.audioStart)/t,o=(n.progressEnd-n.progressStart)/100,i=n.progressStart/100+e*o;ki=Math.max(0,Math.min(1,i)),Ti=ki}}else{const t=(Xs?function(t,e){if(!Xs||!js.length)return e;const n=100*t,o=js.find(t=>n>=t.progressStart&&n<=t.progressEnd);if(!o)return e;const i=(o.progressEnd-o.progressStart)/100,s=o.audioEnd-o.audioStart;return s<=0?e:i/s}(ki,Wi):Wi)*e*Hi;ki+=t,Ti+=t}if(ki>=1)switch(Ii){case"loop":ki%=1,Ti%=1;break;case"pingpong":ki=1,Ti=1,Hi=-1;break;default:return ki=1,Ti=1,Ss(),void i.emit("playbackComplete")}else if(ki<=0)if("pingpong"===Ii)ki=0,Ti=0,Hi=1;else ki=0,Ti=0;as(ki),bs=requestAnimationFrame(xs)}function ws(){if(At)return At.play(),void i.emit("playbackStart");nt||!r.waypoints||r.waypoints.length<2||(nt=!0,vs=0,Hi=1,i.emit("playbackStart"),bs=requestAnimationFrame(xs))}function Ss(){if(At)return At.pause(),void i.emit("playbackStop");nt=!1,bs&&(cancelAnimationFrame(bs),bs=null),us=!1,ms&&(clearTimeout(ms),ms=null),Gs&&!Gs.paused&&Gs.pause(),i.emit("playbackStop")}const _s=U.graphicsDevice.canvas;_s.addEventListener("wheel",t=>{if(!_n)return;t.preventDefault(),Xs&&nt&&Gs&&(fs(),ys());const e=r.scrollSpeed||.1,n=r.scrollAmount||100,o=r.waypoints?.length||2,i="loop"===Ii?o:o-1,s=Math.max(20,20*i),a=100*(Math.abs(t.deltaY)/100)*e*(n/100)/s,l=t.deltaY>0?a:-a;Ti="loop"===Ii?((Ti+l)%1+1)%1:Math.max(0,Math.min(1,Ti+l))},{passive:!1}),_s.addEventListener("pointerdown",t=>{_n&&!Ki&&(Zi=!0,Qi=t.clientX,Ji=t.clientY)},{capture:!0}),_s.addEventListener("pointermove",t=>{if(!_n||!Zi||Ki)return;const e=t.clientX-Qi,n=t.clientY-Ji;Qi=t.clientX,Ji=t.clientY;qi+=.3*-e,Yi+=.3*-n,Yi=Math.max(-60,Math.min(60,Yi))},{capture:!0}),_s.addEventListener("pointerup",()=>{Zi=!1},{capture:!0}),_s.addEventListener("pointerleave",()=>{Zi=!1},{capture:!0});const Cs=[],Ms=[],Es=new Map,Ps=new Float32Array([0,0,0,0]);let ks=!1,Ts=!1;U.graphicsDevice.scope.resolve("uMirrorClipPlane").setValue(Ps);const As=new t.Layer({name:"MirrorMeshLayer"}),Ls=new t.Layer({name:"HotspotMeshLayer"}),Rs=U.scene.layers.getOpaqueIndex(U.scene.layers.getLayerById(t.LAYERID_WORLD));if(Rs>=0?(U.scene.layers.insert(As,Rs+1),U.scene.layers.insert(Ls,Rs+2)):(U.scene.layers.push(As),U.scene.layers.push(Ls)),he.camera){const Jl=[...he.camera.layers],tc=Jl.indexOf(t.LAYERID_WORLD);tc>=0?Jl.splice(tc+1,0,As.id,Ls.id):Jl.push(As.id,Ls.id),he.camera.layers=Jl}function zs(e,n){const o=e.id||`mirror-${Date.now()}-${n}`,i=e.resolution??.5,s=e.intensity??1,a=e.tint||"#ffffff",r=parseInt(a.slice(1,3),16)/255,l=parseInt(a.slice(3,5),16)/255,c=parseInt(a.slice(5,7),16)/255,d=new t.Entity(o);d.setPosition(e.position.x,e.position.y,-e.position.z);const p=(e.rotation?.x??0)*(180/Math.PI),h=(e.rotation?.y??0)*(180/Math.PI),u=-(e.rotation?.z??0)*(180/Math.PI);d.setEulerAngles(p,h,u),d.setLocalScale(e.scale?.x??2,e.scale?.y??1,e.scale?.z??2);const m=new t.Entity(`${o}-mesh`);m.setLocalEulerAngles(90,0,0),m.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1,layers:[As.id]}),d.addChild(m);const g=Math.max(64,Math.floor(U.graphicsDevice.width*i)),f=Math.max(64,Math.floor(U.graphicsDevice.height*i)),y=new t.Texture(U.graphicsDevice,{width:g,height:f,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}),v=new t.RenderTarget({colorBuffer:y,depth:!0}),b=new t.Entity(`${o}-reflcam`);b.addComponent("camera",{priority:-100-n,renderTarget:v,layers:[t.LAYERID_WORLD,t.LAYERID_SKYBOX],flipFaces:!0}),U.root.addChild(b);const x=new t.ShaderMaterial({uniqueName:`MirrorShader_${o}`,vertexGLSL:"\n attribute vec3 aPosition;\n attribute vec3 aNormal;\n uniform mat4 matrix_model;\n uniform mat4 matrix_viewProjection;\n varying vec4 vScreenPos;\n varying vec3 vWorldNormal;\n varying vec3 vWorldPos;\n void main() {\n vec4 worldPos = matrix_model * vec4(aPosition, 1.0);\n vWorldPos = worldPos.xyz;\n vWorldNormal = normalize((matrix_model * vec4(aNormal, 0.0)).xyz);\n gl_Position = matrix_viewProjection * worldPos;\n vScreenPos = gl_Position;\n }\n ",fragmentGLSL:"\n precision highp float;\n varying vec4 vScreenPos;\n varying vec3 vWorldNormal;\n varying vec3 vWorldPos;\n uniform sampler2D uReflectionMap;\n uniform float uIntensity;\n uniform vec3 uTint;\n uniform vec3 uCameraPos;\n void main() {\n // Check if viewing the back face — show black (non-reflective side)\n vec3 viewDir = normalize(uCameraPos - vWorldPos);\n vec3 normal = normalize(vWorldNormal);\n float facing = dot(viewDir, normal);\n if (facing < 0.0) {\n gl_FragColor = vec4(0.02, 0.02, 0.02, 1.0);\n return;\n }\n // Screen-space UVs from clip coordinates with horizontal flip\n vec2 screenUV = vScreenPos.xy / vScreenPos.w * 0.5 + 0.5;\n screenUV.x = 1.0 - screenUV.x;\n vec4 reflColor = texture2D(uReflectionMap, screenUV);\n // Fresnel: stronger reflection at grazing angles\n float fresnel = 1.0 - abs(facing);\n fresnel = mix(0.6, 1.0, fresnel * fresnel);\n vec3 finalColor = reflColor.rgb * uTint * uIntensity * fresnel;\n gl_FragColor = vec4(finalColor, 1.0);\n }\n ",attributes:{aPosition:t.SEMANTIC_POSITION,aNormal:t.SEMANTIC_NORMAL}});x.setParameter("uReflectionMap",y),x.setParameter("uIntensity",s),x.setParameter("uTint",[r,l,c]),x.setParameter("uCameraPos",[0,0,0]),x.cull=t.CULLFACE_NONE,x.depthTest=!0,x.depthWrite=!0,x.update(),m.render&&m.render.meshInstances.forEach(t=>{t.material=x}),d._mirrorMaterial=x,d._mirrorReflCam=b,d._mirrorRenderTarget=v,d._mirrorReflTexture=y,d._mirrorData=e;const w=new t.Vec3,S=new t.Vec3,_=new t.Vec3,C=new t.Vec3,M=new t.Mat4,E=new Float32Array(4),P=()=>{if(!d.enabled)return;const t=he.camera,e=b.camera;e.fov=t.fov,e.nearClip=t.nearClip,e.farClip=t.farClip;const n=d.getPosition(),o=d.forward;w.set(-o.x,-o.y,-o.z);const i=w.dot(n);M.setReflection(w,-i);const s=he.getPosition();M.transformPoint(s,S),b.setPosition(S),_.copy(s).add(he.forward),M.transformPoint(_,_);const a=he.up;C.set(a.x,a.y,a.z),M.transformVector(C,C),b.lookAt(_,C),e.calculateProjection=t=>{const n=U.graphicsDevice.width/U.graphicsDevice.height;t.setPerspective(e.fov,n,e.nearClip,e.farClip),E[0]=w.x,E[1]=w.y,E[2]=w.z,E[3]=i,U.graphicsDevice.scope.resolve("uMirrorClipPlane").setValue(E);const o=b.camera.viewMatrix.data,s=o[0]*w.x+o[4]*w.y+o[8]*w.z,a=o[1]*w.x+o[5]*w.y+o[9]*w.z,r=o[2]*w.x+o[6]*w.y+o[10]*w.z,l=S.x*w.x+S.y*w.y+S.z*w.z-i;if(r>=0)return;const c=t.data,d=s*((Math.sign(s)+c[8])/c[0])+a*((Math.sign(a)+c[9])/c[5])+-1*r+l*((1+c[10])/c[14]);if(Math.abs(d)<1e-6)return;const p=1/d;c[2]=s*p,c[6]=a*p,c[10]=r*p,c[14]=l*p},x.setParameter("uCameraPos",[s.x,s.y,s.z]),function(){const t=U.scene.gsplat?.material;if(!t)return;const e=U.graphicsDevice,n=e?.isWebGPU?"wgsl":"glsl",o=t.getShaderChunks(n);o.has("gsplatModifyVS")||(o.set("gsplatModifyVS","wgsl"===n?dt:ct),t.update());ks||(e.scope.resolve("uMirrorClipPlane").setValue(Ps),ks=!0)}(),function(){if(Ts||!he.camera)return;Ts=!0;const t=he.camera,e=t.calculateProjection;t.calculateProjection=n=>{if(e)e(n);else{const e=U.graphicsDevice.width/U.graphicsDevice.height;n.setPerspective(t.fov,e,t.nearClip,t.farClip)}U.graphicsDevice.scope.resolve("uMirrorClipPlane").setValue(Ps)}}()};return U.on("prerender",P),Es.set(o,()=>{U.off("prerender",P)}),nn.addChild(d),Ms.push(d),console.log(`[StorySplat Viewer] Created mirror plane: ${e.name||o} (resolution: ${i}, intensity: ${s})`),d}function Ds(t){const e=Es.get(t);e&&(e(),Es.delete(t));const n=ol.get(t)||Ms.find(e=>e.name===t);if(n){const e=n._mirrorReflCam,o=n._mirrorRenderTarget,i=n._mirrorReflTexture,s=n._mirrorMaterial;e&&e.destroy(),o&&o.destroy(),i&&i.destroy(),s&&s.destroy(),n.destroy(),ol.delete(t);const a=Ms.indexOf(n);a>=0&&Ms.splice(a,1)}}const Is=[],Fs=D.portalPopup;let Bs=null;const Vs=()=>{Fs&&(Fs.classList.remove("visible"),Bs=null)};if(Fs){const ec=Fs.querySelector(".storysplat-portal-popup-confirm"),nc=Fs.querySelector(".storysplat-portal-popup-cancel");ec?.addEventListener("click",()=>{if(Bs){const t=Bs;Vs(),_r(t)}}),nc?.addEventListener("click",()=>{Vs()})}function $s(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 Us=[],Os=[];let Ns=!1;const Ws=new Map;function Hs(t,e){t&&(t.on("play",()=>e(!0)),t.on("resume",()=>e(!0)),t.on("pause",()=>e(!1)),t.on("stop",()=>e(!1)),t.on("end",()=>e(!1)))}let Gs=null,js=r.narrationTrack?.segments||[],Xs=R;function qs(t){if(Gs){Gs.pause(),Gs.src="";const t=Os.indexOf(Gs);t>=0&&Os.splice(t,1),Ws.delete(Gs),Gs=null}js=t.segments||[],Xs=t.enabled&&!!t.audioUrl,Xs&&t.audioUrl&&(Gs=new Audio(t.audioUrl),Gs.preload="auto",Gs.volume=t.volume??1,Os.push(Gs),Ws.set(Gs,Gs.volume))}function Ys(t){const e=100*t,n=js.find(t=>e>=t.progressStart&&e<=t.progressEnd);if(!n)return{time:0,shouldPlay:!1,volume:1};const o=n.progressEnd-n.progressStart;if(o<=0)return{time:n.audioStart,shouldPlay:!0,volume:n.volume??1};const i=(e-n.progressStart)/o;return{time:n.audioStart+i*(n.audioEnd-n.audioStart),shouldPlay:!0,volume:n.volume??1}}Xs&&r.narrationTrack&&qs(r.narrationTrack);const Zs=new Map;function Ks(){Cs.forEach(t=>{const e=t.hotspotAudioSlotId;if(e){const n=t.hotspotData;if("autoplay"===n?.audioTriggerMode)return;const o=t.sound?.slot(e);o&&(o.isPlaying||o.isPaused)&&(o.stop(),t.hotspotAudioPlaying=!1)}if(t.wasInProximity=!1,t._audioHoverPlaying=!1,t.videoElement)if("autoplay"===t.mediaTriggerMode);else{const e=t.videoElement;e.paused||e.pause(),t.isVideoPlaying=!1}t.wasInProximity=!1})}function Qs(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 Js=new Map,ta=new Map,ea=new Set(["flare","circle","spark","rain","smoke","snowflake"]);function na(e){const n=ta.get(e);if(n)return n;const o=128,i=document.createElement("canvas");i.width=o,i.height=o;const s=i.getContext("2d");if(!s)throw new Error("Could not get 2D canvas context for particle texture");!function(t,e,n){switch(e){case"circle":{const e=t.createRadialGradient(n/2,n/2,0,n/2,n/2,n/2);return e.addColorStop(0,"rgba(255,255,255,1)"),e.addColorStop(.5,"rgba(255,255,255,0.5)"),e.addColorStop(1,"rgba(255,255,255,0)"),t.fillStyle=e,void t.fillRect(0,0,n,n)}case"flare":{const e=t.createRadialGradient(n/2,n/2,0,n/2,n/2,n/2);return e.addColorStop(0,"rgba(255,255,255,1)"),e.addColorStop(.1,"rgba(255,250,220,0.95)"),e.addColorStop(.35,"rgba(255,200,110,0.5)"),e.addColorStop(1,"rgba(255,150,50,0)"),t.fillStyle=e,void t.fillRect(0,0,n,n)}case"spark":{t.translate(n/2,n/2);const e=t.createRadialGradient(0,0,0,0,0,n/3);e.addColorStop(0,"rgba(255,255,255,1)"),e.addColorStop(.25,"rgba(255,255,255,0.55)"),e.addColorStop(1,"rgba(255,255,255,0)"),t.fillStyle=e,t.fillRect(-n/2,-n/2,n,n),t.globalCompositeOperation="lighter";const o=(e,n,o)=>{t.save(),t.rotate(e);const i=t.createLinearGradient(-n,0,n,0);i.addColorStop(0,"rgba(255,255,255,0)"),i.addColorStop(.5,"rgba(255,255,255,1)"),i.addColorStop(1,"rgba(255,255,255,0)"),t.fillStyle=i,t.fillRect(-n,-o/2,2*n,o),t.restore()};return o(0,n/2,2),o(Math.PI/2,n/2,2),o(Math.PI/4,n/3,1.5),void o(-Math.PI/4,n/3,1.5)}case"rain":{const e=t.createLinearGradient(0,0,0,n);e.addColorStop(0,"rgba(210,230,255,0)"),e.addColorStop(.5,"rgba(225,240,255,1)"),e.addColorStop(1,"rgba(210,230,255,0)"),t.fillStyle=e;const o=Math.max(4,n/32);t.fillRect(n/2-o/2,0,o,n),t.globalCompositeOperation="destination-in";const i=t.createLinearGradient(0,0,n,0);return i.addColorStop(0,"rgba(255,255,255,0)"),i.addColorStop(.5,"rgba(255,255,255,1)"),i.addColorStop(1,"rgba(255,255,255,0)"),t.fillStyle=i,void t.fillRect(0,0,n,n)}case"smoke":{const e=t.createRadialGradient(n/2,n/2,0,n/2,n/2,n/2);e.addColorStop(0,"rgba(255,255,255,0.8)"),e.addColorStop(.3,"rgba(255,255,255,0.4)"),e.addColorStop(.7,"rgba(255,255,255,0.15)"),e.addColorStop(1,"rgba(255,255,255,0)"),t.fillStyle=e,t.fillRect(0,0,n,n);const o=[[.4*n,.45*n,.28*n,.18],[.6*n,.55*n,.24*n,.14],[.5*n,.35*n,.18*n,.1]];for(const[e,i,s,a]of o){const o=t.createRadialGradient(e,i,0,e,i,s);o.addColorStop(0,`rgba(255,255,255,${a})`),o.addColorStop(1,"rgba(255,255,255,0)"),t.fillStyle=o,t.fillRect(0,0,n,n)}return}case"snowflake":{t.translate(n/2,n/2);const e=t.createRadialGradient(0,0,0,0,0,n/2);e.addColorStop(0,"rgba(255,255,255,0.35)"),e.addColorStop(.5,"rgba(255,255,255,0.08)"),e.addColorStop(1,"rgba(255,255,255,0)"),t.fillStyle=e,t.fillRect(-n/2,-n/2,n,n),t.strokeStyle="rgba(255,255,255,0.95)",t.lineCap="round";const o=.42*n;for(let e=0;e<6;e++)t.save(),t.rotate(e*Math.PI/3),t.lineWidth=3,t.beginPath(),t.moveTo(0,0),t.lineTo(o,0),t.stroke(),t.lineWidth=2,t.beginPath(),t.moveTo(.55*o,0),t.lineTo(.75*o,.22*o),t.moveTo(.55*o,0),t.lineTo(.75*o,.22*-o),t.stroke(),t.restore();return}default:{const e=t.createRadialGradient(n/2,n/2,0,n/2,n/2,n/2);e.addColorStop(0,"rgba(255,255,255,1)"),e.addColorStop(1,"rgba(255,255,255,0)"),t.fillStyle=e,t.fillRect(0,0,n,n)}}}(s,e,o);const a=new t.Texture(U.graphicsDevice,{name:`particle-${e}`,width:o,height:o,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});return a.setSource(i),ta.set(e,a),a}function oa(e,n){return new Promise((o,i)=>{const s=ta.get(e);if(s)return void o(s);const a=new t.Asset(e,"texture",{url:n});a.on("load",()=>{if(vt)return void i(new Error("Viewer destroyed"));const t=a.resource;ta.set(e,t),o(t)}),a.on("error",t=>{console.error(`[Particle] Failed to load custom texture: ${e}`,t),i(t)}),U.assets.add(a),U.assets.load(a)})}function ia(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,b=a.y||0,x=-(a.z||0),w=v*h,S=b*h,_=x*h,C=180/Math.PI,M=(e.minAngularSpeed??0)*C,E=(e.maxAngularSpeed??e.angularSpeed??0)*C,P=(e.minInitialRotation??0)*C,k=(e.maxInitialRotation??0)*C,T=.1*(e.minSize??.1),A=.1*(e.maxSize??.5),L=e.minScaleX??1,R=e.maxScaleX??1,z=e.minScaleY??1,D=e.maxScaleY??1,I=e.minEmitPower??1,F=e.maxEmitPower??2,B=d.x,V=d.y,$=-d.z,U=p.x,O=p.y,N=-p.z,W=u===t.EMITTERSHAPE_SPHERE,H=e.emitRate||e.rate||50,G=1/H,j=Math.ceil(H*h*2);n.addComponent("particlesystem",{numParticles:e.numParticles||Math.max(j,100),lifetime:h,rate:G,emitterShape:u,emitterExtents:m,emitterRadius:e.emitterRadius||.1,startAngle:P,startAngle2:k,...W?{radialSpeedGraph:new t.Curve([0,I]),radialSpeedGraph2:new t.Curve([0,F])}:{localVelocityGraph:new t.CurveSet([[0,B*I],[0,V*I],[0,$*I]]),localVelocityGraph2:new t.CurveSet([[0,U*F],[0,O*F],[0,N*F]])},velocityGraph:new t.CurveSet([[0,0,1,w],[0,0,1,S],[0,0,1,_]]),scaleGraph:new t.Curve([0,T*Math.min(L,z)]),scaleGraph2:new t.Curve([0,A*Math.max(R,D)]),rotationSpeedGraph:new t.Curve([0,M]),...E!==M?{rotationSpeedGraph2:new t.Curve([0,E])}:{},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,layers:[En.id]}),n.particlesystem&&(n.particlesystem.localSpace=e.localSpace??!1),n.setPosition(s.x+g.x,s.y+g.y,-s.z+g.z);const X=e.renderingGroupId??3;return n.particlesystem&&void 0!==X&&(n.particlesystem.drawOrder=X),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}, ${b}, ${x}), Lifetime: ${h}s`),console.log(`[Particle] Colors: start=${JSON.stringify(r)}, mid=${JSON.stringify(l)}, dead=${JSON.stringify(c)}`),console.log(`[Particle] Angular speed: ${M.toFixed(1)} - ${E.toFixed(1)} deg/s, Initial rotation: ${P.toFixed(1)} - ${k.toFixed(1)} deg`),console.log(`[Particle] Scale: ${T}*${L} - ${A}*${D}, EmitPower: ${I}-${F}, RenderingGroupId: ${X}`),console.log(`[Particle] Rate: ${H} particles/sec → PC rate=${G.toFixed(4)}s/particle, numParticles=${e.numParticles||Math.max(j,100)}`),n}const sa=new Map;function aa(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 ra(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 la(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);o.tags.add("custom-mesh");const 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(ur(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(),a=s.startsWith("blob:")?e.modelName||e.name||"":s.split("?")[0].split("#")[0],r=a.toLowerCase(),l=[".splat",".ply",".sog",".spz",".compressed.ply"].some(t=>r.endsWith(t)),c=l?"gsplat":"container";console.log("[CustomMesh] Loading model from URL:",s,"| format:",c,"| detected from:",a);const d=new t.Asset("mesh-model-"+n,c,{url:s});U.assets.add(d);const p=e.id||`mesh-${n}`,h={entity:o,config:e,modelAsset:d,audioAssetReady:!1,isAnimPlaying:!1,audioPlaying:!1};return sa.set(p,h),l?(d.ready(()=>{sa.has(p)?(console.log("[CustomMesh] Splat loaded as custom mesh:",e.name),o.addComponent("gsplat",{asset:d,unified:!0})):console.log("[CustomMesh] Entity destroyed before splat loaded, skipping:",e.name)}),d.on("error",t=>{console.error("[CustomMesh] Failed to load splat mesh:",e.name,t),U.assets.remove(d)}),U.assets.load(d)):(d.ready(n=>{try{if(!sa.has(p))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 r=0;function l(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&&l(e)})}function c(e){e.render&&(e.render.layers=[Ls.id]),e.children&&e.children.forEach(e=>{e instanceof t.Entity&&c(e)})}if(s.forEach(()=>{r++}),console.log("[CustomMesh] Enabled all children for:",e.name,"- Total nodes:",r),l(s),c(s),"animated"===e.opacityMode&&e.opacityAnimation){const u=e.opacityAnimation.startOpacity??1;ma(o,u),console.log("[CustomMesh] Applied initial animated opacity:",u,"to",e.name)}else void 0!==e.opacity&&e.opacity<1&&(ma(o,e.opacity),console.log("[CustomMesh] Applied static opacity:",e.opacity,"to",e.name));if(e.billboard){const m=o.getEulerAngles().clone();o._billboardActive=!e.billboardRange,U.on("update",()=>{if(!o.enabled)return;if(!o._billboardActive)return void o.setEulerAngles(m.x,m.y,m.z);const t=he.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 d=(i?.animations?.length??0)>0;if(console.log("[CustomMesh] Asset resource for",e.name,":",{hasAnimations:d,animationCount:i?.animations?.length||0,animations:i?.animations?.map(t=>t?.name||t?.resource?.name||"unnamed")||[],interactionConfig:e.interaction}),d||e.interaction&&e.interaction.playModelAnimation){const g=[],f=s.anim;if(f&&(console.log("[CustomMesh] Found modelEntity.anim component - using simple approach"),g.push({type:"pc-anim",component:f,modelEntity:s})),0===g.length){function y(e,n=0){e.anim&&!g.find(t=>t.component===e.anim)&&(g.push({type:"anim",component:e.anim,node:e}),console.log("[CustomMesh] Found existing ANIM component on:",e.name)),e.animation&&(g.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&&y(e,n+1)})}y(s)}if(0===g.length&&d){const v=i?.animations||[];console.log("[CustomMesh] No anim component found - falling back to GLB skeleton approach"),console.log("[CustomMesh] GLB has",v.length,"embedded animations");try{g.push({type:"glb-skeleton",modelEntity:s,asset:n,animations:v,animationNames:v.map(t=>t.resource?.name||t.name||"Animation"),isPlaying:!1,currentTime:0}),console.log("[CustomMesh] GLB skeleton animations stored:",v.map(t=>t.resource?.name||t.name))}catch(b){console.error("[CustomMesh] Error setting up GLB animations:",b)}}if(g.length>0){h.allAnimComponents=g,h.animComponent=g[0].component,console.log("[CustomMesh] Total animation components for",e.name,":",g.length,"- Types:",g.map(t=>t.type).join(", "));const x=e.interaction?.animationAutoPlay;x&&(g.forEach(t=>{aa(t)}),h.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);if(t){t.asset=r.id,Hs(t,t=>{o.audioPlaying=t});const s=i.audioTriggerMode||i.activationMode||"click";if("autoplay"!==s||t.isPlaying||(t.play(),o.audioPlaying=!0),"proximity"===s&&!t.isPlaying){he.getPosition().distance(e.getPosition())<=(i.audioProximityRadius??i.audioMaxDistance??1)&&(t.play(),o.audioPlaying=!0,console.log("[CustomMesh] Audio started (late proximity):",n.name))}}o.audioAssetReady=!0,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,h),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=he.camera.screenToWorld(a,r,he.camera.nearClip),c=he.camera.screenToWorld(a,r,he.camera.farClip),d=(new t.Vec3).sub2(c,l).normalize(),p=e.modelEntity;if(p&&ca(p,l,d))return!0;if(ca(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,p=!!(("click"===r||"hover"===r)&&n.interaction?.triggerUIPopup||("click"===l||"hover"===l)&&n.interaction?.playAudio||("click"===c||"hover"===c)&&n.interaction?.playModelAnimation||("click"===d||"hover"===d)&&n.interaction?.triggerDirectLink);if(!p)return;let h=!1;const u=()=>{if(s.style.cursor="pointer",Wr=!0,Kn=!0,"hover"===r&&n.interaction?.triggerUIPopup&&pa(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=>aa(t)),o.isAnimPlaying=!0,console.log("[CustomMesh] Playing animation on hover for:",n.name))}"hover"===d&&n.interaction?.triggerDirectLink&&ha(n)},m=()=>{if(s.style.cursor="",Wr=!1,Kn=!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=>ra(t)),o.isAnimPlaying=!1,console.log("[CustomMesh] Paused animation on hover out for:",n.name))}},g=()=>{if(console.log("[CustomMesh] Clicked mesh:",n.name),"click"===r&&n.interaction?.triggerUIPopup&&pa(n),"click"===c&&n.interaction?.playModelAnimation){const t=o.allAnimComponents;t&&t.length>0&&(o.isAnimPlaying?(t.forEach(t=>ra(t)),o.isAnimPlaying=!1,console.log("[CustomMesh] Paused",t.length,"animations on click for:",n.name)):(t.forEach(t=>aa(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),i=n.interaction?.audioClickBehavior||"stop-restart";t&&(t.isPlaying?("stop-restart"===i?(t.stop(),console.log("[CustomMesh] Stopped audio on click for:",n.name)):(t.pause(),console.log("[CustomMesh] Paused audio on click for:",n.name)),o.audioPlaying=!1):("pause-resume"===i&&t.isPaused?(t.resume(),console.log("[CustomMesh] Resumed audio on click for:",n.name)):(t.play(),console.log("[CustomMesh] Playing audio on click for:",n.name)),o.audioPlaying=!0))}"click"===d&&n.interaction?.triggerDirectLink&&ha(n)},f=t=>{a(t.clientX,t.clientY)&&g()},y=t=>{const e=a(t.clientX,t.clientY);e&&!h?(h=!0,u()):!e&&h&&(h=!1,m())},v=()=>{h&&(h=!1,m())};s.addEventListener("click",f),s.addEventListener("mousemove",y),s.addEventListener("mouseleave",v),e.meshClickHandler=f,e.meshHoverHandler=y,e.meshLeaveHandler=v}(o,e,h),console.log("[CustomMesh] Mesh fully initialized:",e.name)}catch(w){console.error("[CustomMesh] Error processing loaded mesh:",e.name,w)}}),d.on("error",t=>{console.error("[CustomMesh] Failed to load mesh:",e.name,t),U.assets.remove(d)}),U.assets.load(d)),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},nn.addChild(o),o}function ca(e,n,o){const i=e.render;if(i&&i.meshInstances)for(const t of i.meshInstances)if(t.aabb){if(da(n,o,t.aabb))return!0}const s=e.model;if(s&&s.meshInstances)for(const t of s.meshInstances)if(t.aabb){if(da(n,o,t.aabb))return!0}const a=e.children;if(a)for(const e of a)if(e instanceof t.Entity&&ca(e,n,o))return!0;return!1}function da(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 pa(t){if(!t.interaction)return;const e={id:`mesh-content-${t.id}`,title:t.interaction.title||t.name,information:t.interaction.information,photoUrl:t.interaction.photoUrl,iframeUrl:t.interaction.iframeUrl,externalLinkUrl:t.interaction.externalLinkUrl,externalLinkText:t.interaction.externalLinkText,backgroundColor:t.interaction.backgroundColor||"#000000",textColor:t.interaction.textColor||"#ffffff"};D.showHotspotPopup?D.showHotspotPopup(e):function(t){const e=document.querySelector(".storysplat-mesh-popup");e&&e.remove();const 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=`× ${x(u,"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 ha(t){t.interaction?.triggerDirectLink&&t.interaction.directLinkUrl&&window.open(t.interaction.directLinkUrl,"_blank")}function ua(t,e,n){return e<=n?t>=e&&t<=n:"loop"===Ii&&(t>=e||t<=n)}function ma(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 ga(){const t=U.graphicsDevice.canvas;sa.forEach(e=>{const n=e.entity,o=n.meshClickHandler;o&&t.removeEventListener("click",o),n.destroy()}),sa.clear()}i.on("progressUpdate",()=>{!function(){const t=100*ki,e=$i(ki);sa.forEach(n=>{const{entity:o,config:i}=n,s=o.visibilityRange;if(s){let n=!0;"waypoint"===s.type?n=ua(e,s.start,s.end):"percentage"===s.type&&(n=ua(t,s.start,s.end)),o.enabled=n}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);ma(o,e.startOpacity+(e.endOpacity-e.startOpacity)*n)}}if(i.billboard&&i.billboardRange){const n=i.billboardRange;let s=!1;"percentage"===n.type?s=t>=n.start&&t<=n.end:"waypoint"===n.type&&(s=e>=n.start&&e<=n.end),o._billboardActive=s}else i.billboard&&(o._billboardActive=!0)})}()});let fa=null,ya=null;const va=r.skybox?.url||r.skyboxUrl||null,ba=r.skybox?.rotation??r.skyboxRotation??0;function xa(e){const n=r.skybox?.url||r.skyboxUrl;if(!n)return void console.log("[StorySplat Viewer] No skybox configured");const o=r.skybox?.rotation??r.skyboxRotation??0,i=r.skybox?.intensity??1,s=r.skybox?.enableIBL??!0;if(console.log("[StorySplat Viewer] Creating skybox:",n,"rotation:",o,"rad =",o*(180/Math.PI),"deg","IBL:",s),fo())return lo=()=>xa(e),void Le("Deferring skybox init while 8th Wall AR is active");const a=e??new Image,l=ln(),c=()=>{if(!vt){if(fo())return lo=c,void Le("Deferring skybox apply while 8th Wall AR is active");if(wa(),l)return console.log("[StorySplat Viewer] Mobile detected — using sphere skybox (native pipeline skipped)"),void Sa(n,a,o,i,s);try{const e=new t.Texture(U.graphicsDevice,{width:a.width,height:a.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(a);const n=Math.min(a.width/4,2048),r=new t.Texture(U.graphicsDevice,{width:n,height:n,cubemap:!0,mipmaps:!1,format:t.PIXELFORMAT_RGBA8,minFilter:t.FILTER_LINEAR,magFilter:t.FILTER_LINEAR});t.reprojectTexture(e,r,{numSamples:1024});const l=2*n,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=i,0!==o){const e=new t.Quat;e.setFromEulerAngles(0,-o*(180/Math.PI),0),U.scene.skyboxRotation=e}s&&(U.scene.ambientLight=new t.Color(.3*i,.3*i,.35*i),console.log("[StorySplat Viewer] IBL ambient lighting applied:",i)),console.log("[StorySplat Viewer] Native skybox applied successfully")}catch(t){console.warn("[StorySplat Viewer] Native skybox pipeline failed, using sphere fallback:",t),Sa(n,a,o,i,s)}}};e?c():(a.crossOrigin="anonymous",a.onload=c,a.onerror=t=>{console.error("[StorySplat Viewer] Failed to load skybox texture:",n,t)},a.src=n)}function wa(){if(!vt&&fo())return lo=wa,void Le("Deferring skybox cleanup while 8th Wall AR is active");lo=null,U.scene.envAtlas=null,fa&&(fa.destroy(),fa=null),ya&&(U.scene.off("prerender",ya),ya=null)}function Sa(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),ya=t=>{const e=t.entity.getPosition();a.setPosition(e.x,e.y,e.z)},U.scene.on("prerender",ya),nn.addChild(a),fa=a,s&&(U.scene.ambientLight=new t.Color(.3*i,.3*i,.35*i)),console.log("[StorySplat Viewer] Skybox fallback sphere applied")}function _a(t,e,n){t?(r.skybox={url:t,rotation:e??0},r.skyboxUrl=t,r.skyboxRotation=e??0,xa(n)):(r.skybox=void 0,r.skyboxUrl=void 0,wa())}const Ca=[],Ma=new Map;function Ea(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 Pa(e){const n=new t.Entity(e.name||"Point Light");n.addComponent("light",{type:"point",color:Ea(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 ka(e){const n=new t.Entity(e.name||"Directional Light");n.addComponent("light",{type:"directional",color:Ea(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(ur(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 Ta(e){const n=new t.Entity(e.name||"Hemispheric Light");n.addComponent("light",{type:"directional",color:Ea(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=Ea(e.groundColor),o=Ea(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 Aa(e){const n=Ea(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 La(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:Ea(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(ur(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 Ra=[];function za(){if(!ot)return;const t=r.splatRelighting,e=Ca.length>0;if(console.log("[StorySplat Viewer] initSplatRelighting:",{relightConfig:t,hasLights:e,lightCount:Ca.length,splatEntity:!!ot}),!e)return void console.log("[StorySplat Viewer] No lights, skipping splat relighting");ot.script||ot.addComponent("script");const n=yt(),o=ot.script;if(ut=o?.create?.(n)??null,ut){const e=0,n=t?.ambientColor||"#ffffff";ut.setAmbientColor(n),ut.ambientR*=e,ut.ambientG*=e,ut.ambientB*=e;let o=0;!0===t?.enabled&&(o=a?1:t?.allowViewerToggle?t?.viewerDefaultOn?1:0:1),ut.setRelightFade(o),ut.enabled=!0,gt=!0,mt.add(ut),console.log("[StorySplat Viewer] Splat relighting initialized, startFade:",o)}}function Da(t){if(!gt||!ut)return;if(t.script?.gsplatRelighting)return;t.script||t.addComponent("script");const e=yt(),n=t.script,o=n?.create?.(e)??null;o&&(o.ambientR=ut.ambientR,o.ambientG=ut.ambientG,o.ambientB=ut.ambientB,o.relightFade=ut.relightFade,o.enabled=!0,mt.add(o),console.log("[Relighting] Added relighting script to swap entity:",t.name))}let Ia=!1;let Fa=null,Ba=null;function Va(t){for(const e of Ca){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))}sa.forEach(e=>{$a(e.entity,t)})}function $a(e,n){e.render&&(e.render.castShadows=n);for(const o of e.children)o instanceof t.Entity&&$a(o,n)}function Ua(){Fa&&(Fa.destroy(),Fa=null),Ba=null}function Oa(){const e=r.splatRelighting;if(e?.shadowsEnabled){!function(e,n,o){if(Fa)return Fa.setLocalPosition(0,e,0),Fa.setLocalScale(n,1,n),void(Ba&&(Ba.opacity=o,Ba.update()));Ba=new t.StandardMaterial,Ba.shadowCatcher=!0,Ba.blendType=t.BLEND_MULTIPLICATIVE,Ba.depthWrite=!1,Ba.useSkybox=!1,Ba.diffuse.set(0,0,0),Ba.specular.set(0,0,0),Ba.opacity=o,Ba.update(),Fa=new t.Entity("ShadowCatcher"),Fa.addComponent("render",{type:"plane",material:Ba,castShadows:!1,receiveShadows:!0}),Fa.setLocalPosition(0,e,0),Fa.setLocalScale(n,1,n);const i=Fa.render?.meshInstances;if(i)for(const t of i)t.drawBucket=250;nn.addChild(Fa),console.log("[StorySplat Viewer] Shadow catcher created at Y:",e,"scale:",n)}(e.shadowGroundY??0,e.shadowPlaneScale??50,e.shadowIntensity??.4),Va(!0)}else Ua(),Va(!1)}const Na=[[1,.2,.1],[.1,.6,1],[.2,1,.3],[1,.8,.1],[.8,.2,1],[1,.4,.7],[.1,1,.9]],Wa=[],Ha=[];let Ga=!1;const ja=9.8,Xa=.6,qa=.15;function Ya(e){if(0===Ha.length)for(const[e,n,o]of Na){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(),Ha.push(i)}return Ha[e%Ha.length]}const Za=[[.85,.85,.88],[.83,.69,.22],[.72,.45,.2],[.56,.57,.58],[.66,.66,.68]],Ka=[];function Qa(e){if(0===Ka.length)for(const[e,n,o]of Za){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(),Ka.push(i)}return Ka[e%Ka.length]}function Ja(){return vn?vn.collisionMeshEntities:un._collisionEntities??[]}function tr(e,n){let o=null;for(const t of Ja()){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=vn?.voxelCollisionInstance;if(i&&ot){const s=new t.Mat4;s.copy(ot.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=ot.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 er(e,n,o){for(const t of Ja()){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),b=u+o-Math.abs(f);y>0&&v>0&&b>0&&(y<=v&&y<=b?(e.x+=Math.sign(m)*y,n.x=-n.x*Xa):b<=v?(e.z+=Math.sign(f)*b,n.z=-n.z*Xa):(e.y+=Math.sign(g)*v,n.y=g>0?Math.abs(n.y)*Xa:-Math.abs(n.y)*Xa))}const i=vn?.voxelCollisionInstance;if(i){let s,a,r,l,c,d;if(ot){const n=new t.Mat4;n.copy(ot.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(ot){const t=ot.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+Xa)*s*e,n.y-=(1+Xa)*s*o,n.z-=(1+Xa)*s*i)}}}}function nr(t){const e=Ca.indexOf(t.entity);e>=0&&Ca.splice(e,1),t.entity.destroy()}function or(){if(!Ga){if(Ga=!0,!gt&&ot){r.splatRelighting={...r.splatRelighting,enabled:!0},za();for(const t of mt)t.setRelightFade(1)}console.log("[Playground] Started — press F to spawn light balls, relightingEnabled:",gt,"splatEntity:",!!ot)}}function ir(){if(Ga){Ga=!1;for(const t of Wa)nr(t);Wa.length=0,console.log("[Playground] Stopped")}}window.__storysplat_playground_handler&&window.removeEventListener("keydown",window.__storysplat_playground_handler);const sr=e=>{const n=document.activeElement?.tagName;"INPUT"===n||"TEXTAREA"===n||document.activeElement?.isContentEditable||(e.shiftKey&&(e.ctrlKey||e.metaKey)&&"KeyP"===e.code?(e.preventDefault(),Ga?ir():or()):"KeyF"===e.code&&Ga?(console.log("[Playground] F pressed — spawning light ball"),function(){Wa.length>=16&&nr(Wa.shift());const e=Math.floor(Math.random()*Na.length),[n,o,i]=Na[e],s=he.getPosition(),a=he.forward,l=new t.Vec3(s.x+2*a.x,s.y+2*a.y,s.z+2*a.z);console.log("[Playground] Spawning ball #"+(Wa.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:",l.x.toFixed(2),l.y.toFixed(2),l.z.toFixed(2),"color:",[n.toFixed(1),o.toFixed(1),i.toFixed(1)],"collisionEntities:",Ja().length,"lightEntities:",Ca.length,"relightingEnabled:",gt);const c=new t.Entity("playground-ball");c.addComponent("render",{type:"sphere",material:Ya(e),castShadows:!!r.splatRelighting?.shadowsEnabled}),c.setLocalScale(.3,.3,.3),c.addComponent("light",{type:"point",color:new t.Color(n,o,i),intensity:2,range:8,castShadows:!1}),c.setPosition(l),nn.addChild(c),Ca.push(c);const d=new t.Vec3(8*a.x+2*(Math.random()-.5),8*a.y+3,8*a.z+2*(Math.random()-.5));Wa.push({entity:c,velocity:d,age:0,lifetime:12,baseIntensity:2,radius:qa})}()):"KeyG"===e.code&&Ga&&function(){r.splatRelighting?.shadowsEnabled||(console.log("[Playground] Auto-enabling shadows for shadow ball testing"),r.splatRelighting={...r.splatRelighting,shadowsEnabled:!0},Oa()),Wa.length>=16&&nr(Wa.shift());const e=Math.floor(Math.random()*Za.length),n=he.getPosition(),o=he.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:Qa(e),castShadows:!0}),s.setLocalScale(.3,.3,.3),s.setPosition(i),nn.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));Wa.push({entity:s,velocity:a,age:0,lifetime:12,baseIntensity:0,radius:qa})}())};function ar(t){switch(t){case"linear":return"linear";case"inverse":return"inverse";default:return"exponential"}}window.__storysplat_playground_handler=sr,window.addEventListener("keydown",sr),console.log("[Playground] Toggle registered — Ctrl+Shift+P to start/stop, F = light ball, G = shadow ball");const rr=new Map;const lr=new Set;function cr(){Ns||(Cs.forEach(t=>{const e=t.hotspotAudioSlotId;if(e){const n=t.sound?.slot(e);n&&(n._storedVolume=n.volume,n.volume=0)}}),Os.forEach(t=>{Ws.set(t,t.volume),t.volume=0}),Zs.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);e&&(e._storedVolume=e.volume,e.volume=0)}),rr.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}),Ns=!0,console.log("[Audio] All audio muted"))}function dr(){Ns&&(Cs.forEach(t=>{const e=t.hotspotAudioSlotId;if(e){const n=t.sound?.slot(e);if(n){const t=n._storedVolume;n.volume=void 0!==t?t:1}}}),Os.forEach(t=>{const e=Ws.get(t);t.volume=void 0!==e?e:1}),Zs.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);if(e){const t=e._storedVolume;e.volume=void 0!==t?t:1}}),rr.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}),Ns=!1,console.log("[Audio] All audio unmuted"))}const pr=[];function hr(e,n=!1,o=1,i){const s=new t.StandardMaterial;if(s.blendType=t.BLEND_NORMAL,s.depthTest=!0,s.depthWrite=!0,s.cull=t.CULLFACE_NONE,s.twoSidedLighting=!0,s.alphaTest=.5,n?(s.diffuse=new t.Color(1,1,1),s.specular=new t.Color(0,0,0)):(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 qt(U,e,{autoPlay:!0,onReady:()=>{if(vt)return console.log(`[Hotspot] Ignoring GIF load - viewer was destroyed: ${e}`),void o.destroy();o.texture&&(o.texture.premultiplyAlpha=!1,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()}});pr.push(o)}else{const o=t=>{n?(s.diffuseMap=t,s.opacityMap=t):(s.emissiveMap=t,s.opacityMap=t),s.opacityMapChannel="a",s.update(),console.log(`[Hotspot] Texture loaded for: ${e}, useLighting=${n}`),i&&i()},a=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()};if("function"==typeof createImageBitmap)fetch(e,{mode:"cors"}).then(t=>{if(!t.ok)throw new Error(`HTTP ${t.status} ${t.statusText}`);return t.blob()}).then(t=>createImageBitmap(t,{premultiplyAlpha:"none",colorSpaceConversion:"none"})).then(n=>{if(vt)return console.log(`[Hotspot] Ignoring texture load - viewer was destroyed: ${e}`),void n.close?.();const i=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});i.premultiplyAlpha=!1;const s=U.graphicsDevice.maxAnisotropy;i.anisotropy="number"==typeof s&&s>1?s:16,i.setSource(n),o(i)}).catch(a);else{const n=new Image;n.crossOrigin="anonymous",n.onload=()=>{if(vt)return void console.log(`[Hotspot] Ignoring texture load - viewer was destroyed: ${e}`);const i=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});i.premultiplyAlpha=!1;const s=U.graphicsDevice.maxAnisotropy;i.anisotropy="number"==typeof s&&s>1?s:16,i.setSource(n),o(i)},n.onerror=a,n.src=e}}return s}function ur(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 mr(e,n,o,i){const s=(new t.Quat).setFromEulerAngles(90,0,0),a=ur(n,o,i);e.setRotation((new t.Quat).mul2(a,s))}function gr(e,n){if(!e.leaderLineEnabled||!e.leaderLineTarget)return null;const o=e.leaderLineTarget,i=new t.Vec3(o.x??0,o.y??0,-(o.z??0)),s=$s(e.leaderLineColor||"#ffffff"),a=.005*(e.leaderLineWidth??2),r=(new t.Vec3).add2(n,i).mulScalar(.5),l=(new t.Vec3).sub2(i,n),c=l.length();if(c<.001)return null;const d=new t.StandardMaterial;d.diffuse=new t.Color(0,0,0),d.emissive=s,d.useLighting=!1,d.blendType=t.BLEND_NONE,d.depthTest=!0,d.depthWrite=!0,d.cull=t.CULLFACE_NONE,d.update();const p=new t.Entity(`leader-line-${e.id||"x"}`);p.addComponent("render",{type:"cylinder",castShadows:!1,receiveShadows:!1,layers:[Ls.id]}),p.render.meshInstances[0].material=d,p._leaderMat=d,p.setPosition(r),p.setLocalScale(a,c,a);const h=new t.Vec3(0,1,0),u=l.clone().normalize(),m=new t.Quat;return h.dot(u)<-.9999?m.setFromEulerAngles(180,0,0):m.setFromDirections(h,u),p.setRotation(m),p}function fr(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(ur(p,h,u)),"sphere"===e.type){o.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1,layers:[Ls.id]});const n=new t.StandardMaterial,i=$s(e.color||"#ffffff");!0===e.useLighting?(n.diffuse=i,n.emissive=i.clone(),n.emissive.mulScalar(.3)):(n.diffuse=new t.Color(0,0,0),n.emissive=i,n.useLighting=!1);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,layers:[Ls.id]});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=hr(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),mr(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,layers:[Ls.id]});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_NORMAL,v.alphaTest=.01,d&&(v.opacityMap=y,v.blendType=t.BLEND_NORMAL,v.alphaTest=.01,console.log(`[Hotspot] WebM video with alpha enabled: ${e.title}`));let b=null,x=null;if(a){const n=m(a,!0,!1);b=n.video,x=n.texture,v.opacityMap=x,v.opacityMapChannel="r",v.blendType=t.BLEND_NORMAL,v.alphaTest=.01,f.addEventListener("play",()=>{b&&b.paused&&(b.currentTime=f.currentTime,b.play().catch(console.warn))}),f.addEventListener("pause",()=>{b&&!b.paused&&b.pause()}),f.addEventListener("seeked",()=>{b&&(b.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(),b&&x&&b.readyState===b.HAVE_ENOUGH_DATA&&x.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),mr(o,p,h,u),o.videoElement=f,o.alphaVideoElement=b,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(),b&&x&&(b.currentTime=0,x.upload()),console.log(`[Hotspot] First frame loaded for: ${e.title}`)},{once:!0}),f.load(),b&&b.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);Us.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),he&&he.getPosition){const t=he.getPosition(),e=he.forward,n=he.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,layers:[Ls.id]}),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_NORMAL,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,layers:[Ls.id]});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_NORMAL,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 qt(U,e.gifUrl,{autoPlay:!0,onReady:()=>{if(vt)a.destroy();else if(a.texture){a.texture.premultiplyAlpha=!1,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),mr(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,layers:[Ls.id]});const i=new t.StandardMaterial;i.diffuse=$s(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}});pr.push(a)}else{o.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1,layers:[Ls.id]});const n=new t.StandardMaterial,i=$s(e.color||"#CC5833");!0===e.useLighting?(n.diffuse=i,n.emissive=i.clone(),n.emissive.mulScalar(.3)):(n.diffuse=new t.Color(0,0,0),n.emissive=i,n.useLighting=!1);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(e,n){if(!n.audioUrl)return null;const o=`hotspot-audio-${n.id||Date.now()}`,i=n.audioSpatial||!1,s=ar(n.audioDistanceModel||"linear");e.sound||e.addComponent("sound",{positional:i,refDistance:n.audioRefDistance??1,maxDistance:n.audioMaxDistance??10,rollOffFactor:n.audioRolloffFactor??1,distanceModel:s,volume:n.audioVolume??1}),e.sound?.addSlot(o,{volume:n.audioVolume??1,loop:n.audioLoop||!1,autoPlay:!1,overlap:!1});const a=new t.Asset(`hotspot-audio-${o}`,"audio",{url:n.audioUrl});return a.on("load",()=>{if(vt)return;const t=e.sound?.slot(o);t&&(t.asset=a.id,Hs(t,t=>{e.hotspotAudioPlaying=t})),e.hotspotAudioReady=!0;const i=e.mediaTriggerMode||"click";"proximity"===(n.audioTriggerMode||("proximity"===i?"proximity":"click"))&&e.wasInProximity&&t&&!t.isPlaying&&(t.play(),e.hotspotAudioPlaying=!0,console.log("[Audio] Hotspot audio started (late proximity):",n.title))}),U.assets.add(a),U.assets.load(a),console.log(`[Audio] Hotspot audio setup (PlayCanvas slot): ${n.title}, spatial=${i}, refDist=${n.audioRefDistance??1}, maxDist=${n.audioMaxDistance??10}`),o}(o,e);if(m){if(o.hotspotAudioSlotId=m,console.log(`[StorySplat Viewer] Audio setup for hotspot: ${e.title||"Untitled"}, triggerMode: ${e.audioTriggerMode||"click"}`),"autoplay"===e.audioTriggerMode){let t=0;const e=()=>{if(vt)return;const n=o.sound?.slot(m);n&&void 0!==n.asset&&o.hotspotAudioReady?n.isPlaying||(n.play(),o.hotspotAudioPlaying=!0):t++<300&&setTimeout(e,100)};e()}"hover"===e.audioTriggerMode&&(o._audioHoverMode=!0,Or=!0)}if(e.billboard){const n=o.render?.meshInstances?.[0]?.material;n&&(n.cull=t.CULLFACE_BACK,n.update());const i=o.getRotation().clone(),s=void 0!==e.billboardRangeStart||void 0!==e.billboardRangeEnd;o._billboardActive=!s,o._billboardOriginalRotation=i;const a=()=>{(o.parent||nn.findByName(o.name))&&o._billboardActive&&(o.lookAt(he.getPosition()),o.rotateLocal(90,180,0))};U.on("update",a),o.once("destroy",()=>{U.off("update",a)})}e.visibilityRange&&(o.visibilityRange=e.visibilityRange,o.enabled=!1),nn.addChild(o),e.billboard&&(o.lookAt(he.getPosition()),o.rotateLocal(90,180,0));const g=gr(e,o.getPosition().clone());return g&&(nn.addChild(g),o.leaderLineEntity=g),Cs.push(o),console.log(`[StorySplat Viewer] Created hotspot: ${e.title||"Untitled"}`),o}function yr(){const e=100*ki,n=$i(ki),o=he.getPosition();Cs.forEach(i=>{const s=i.hotspotData;if(!s)return;let a=!0;if(s.alwaysVisible)a=!0;else{const t=i.visibilityRange;t&&("waypoint"===t.type?a=ua(n,t.start,t.end):"percentage"===t.type&&(a=ua(e,t.start,t.end)))}if(i.shouldBeVisible=a,s.billboard&&(void 0!==s.billboardRangeStart||void 0!==s.billboardRangeEnd)){const n=s.billboardRangeStart??0,o=s.billboardRangeEnd??100,a=e>=n&&e<=o,r=i._billboardActive;if(i._billboardActive=a,a!==r){const e=i.render?.meshInstances?.[0]?.material;e&&(e.cull=a?t.CULLFACE_FRONT:t.CULLFACE_NONE,e.update())}!a&&i._billboardOriginalRotation&&i.setRotation(i._billboardOriginalRotation)}if(i.hiddenUntilTextureLoaded?i.enabled=!1:i.enabled=a,i.leaderLineEntity&&(i.leaderLineEntity.enabled=i.enabled),i.videoElement&&"video"===s.type){const t=i.mediaTriggerMode||"click";if("proximity"===t){const t=i.getPosition(),e=o.distance(t),n=i.proximityDistance||5;e<=n&&!i.isVideoPlaying?(vr(i,s),console.log(`[Hotspot] Proximity play: ${s.title}, distance=${e.toFixed(2)}`)):e>n&&i.isVideoPlaying&&!1!==s.pauseOnLeaveProximity&&(br(i),console.log(`[Hotspot] Proximity pause: ${s.title}, distance=${e.toFixed(2)}`))}"autoplay"===t&&a&&!i.isVideoPlaying&&vr(i,s),"scroll"===t&&(a&&!i.isVideoPlaying?(vr(i,s),console.log(`[Hotspot] Scroll play: ${s.title}, scroll=${e.toFixed(1)}%`)):!a&&i.isVideoPlaying&&(br(i),console.log(`[Hotspot] Scroll pause: ${s.title}, scroll=${e.toFixed(1)}%`)))}if("animated"===s.opacityMode&&s.opacityAnimation){if("image"===s.type&&!i.textureLoaded)return;const t=s.opacityAnimation,n=t.startPercent??0,o=t.endPercent??100,a=void 0!==t.startOpacity?t.startOpacity:1,r=void 0!==t.endOpacity?t.endOpacity:1;let l;if(e<=n)l=a;else if(e>=o)l=r;else{l=a+(r-a)*((e-n)/(o-n))}if(l=Math.max(0,Math.min(1,l)),i.hotspotMaterial)i.hotspotMaterial.opacity=l,i.hotspotMaterial.update();else if(i.render&&i.render.material){const t=i.render.material;t.opacity=l,t.update?.()}}})}function vr(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 br(t){const e=t.videoElement,n=t.alphaVideoElement;e&&(e.pause(),n&&n.pause(),t.isVideoPlaying=!1)}function xr(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},r="number"==typeof s?{x:s,y:s,z:s}:s,l=Math.abs(r._x??r.x??1),c=Math.abs(r._y??r.y??1),d=r._z??r.z??1,p=e.rotation||{_x:0,_y:0,_z:0},h=p._x??p.x??0,u=p._y??p.y??0,m=p._z??p.z??0;if(o.setRotation(ur(h,u,m)),"sphere"===e.type){o.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1,layers:[Ls.id]});const n=new t.StandardMaterial,i=$s(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*l,.2*c,.2*d)}else if("image"===e.type&&e.imageUrl){o.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1,layers:[Ls.id]});const t=!0===e.useLighting,n=e.opacity??1;o.textureLoaded=!1,o.hiddenUntilTextureLoaded=!0,o.enabled=!1;const i=hr(e.imageUrl,t,n,()=>{o.textureLoaded=!0,o.visibilityRange&&!o.shouldBeVisible||(o.enabled=!0),o.hiddenUntilTextureLoaded=!1});o.render.material=i,o.setLocalScale(l,d,c),mr(o,h,u,m),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,layers:[Ls.id]});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_NORMAL,s.opacity=e.opacity??1,s.update(),o.render.material=s,o.setLocalScale(l,d,c),mr(o,h,u,m),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,layers:[Ls.id]});const t=!0===e.useLighting,n=e.opacity??1,i=hr(e.gifUrl,t,n,()=>{o.textureLoaded=!0,o.visibilityRange&&!o.shouldBeVisible||(o.enabled=!0),o.hiddenUntilTextureLoaded=!1});o.render.material=i,o.setLocalScale(l,d,c),mr(o,h,u,m),o.textureLoaded=!1,o.hiddenUntilTextureLoaded=!0,o.enabled=!1,o.portalMaterial=i,console.log(`[Portal] Created GIF portal: ${e.title||e.targetSceneName||"Untitled"}`)}else if("plane"===e.type){if(o.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1,layers:[Ls.id]}),a){const e=new t.StandardMaterial;e.diffuse=new t.Color(0,.8,.8),e.emissive=new t.Color(0,.4,.4),e.opacity=1,e.useLighting=!1,e.cull=t.CULLFACE_NONE,e.update(),o.render.material=e,o.render.enabled=!1}else{o.render.enabled=!1;const n=e.title||e.targetSceneName||"";if(n){const e=new t.Entity("portal-popup");e.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1,layers:[Ls.id]});const i=512,s=128,a=document.createElement("canvas");a.width=i,a.height=s;const r=a.getContext("2d"),l=r.createLinearGradient(0,0,i,0);l.addColorStop(0,"rgba(0, 0, 0, 0)"),l.addColorStop(.15,"rgba(0, 0, 0, 0.6)"),l.addColorStop(.5,"rgba(0, 0, 0, 0.75)"),l.addColorStop(.85,"rgba(0, 0, 0, 0.6)"),l.addColorStop(1,"rgba(0, 0, 0, 0)"),r.fillStyle=l,r.fillRect(0,0,i,s),r.fillStyle="#ffffff",r.font='300 36px "Helvetica Neue", Helvetica, Arial, sans-serif',r.textAlign="center",r.textBaseline="middle",r.fillText(n,i/2,s/2);const c=new t.Texture(U.graphicsDevice,{width:i,height:s,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});c.setSource(a);const d=new t.StandardMaterial;d.diffuse=new t.Color(0,0,0),d.emissive=new t.Color(1,1,1),d.emissiveMap=c,d.opacityMap=c,d.opacityMapChannel="a",d.useLighting=!1,d.blendType=t.BLEND_NORMAL,d.depthTest=!0,d.depthWrite=!1,d.cull=t.CULLFACE_NONE,d.opacity=0,d.update(),e.render.material=d,e.setLocalPosition(0,.3,0),e.setLocalScale(.6,1,.15);const p=()=>{e.enabled&&o.portalPopupOpacity>0&&(e.lookAt(he.getPosition()),e.rotateLocal(90,180,0))};U.on("update",p),e.once("destroy",()=>{U.off("update",p)}),o.addChild(e),o.portalPopupEntity=e,o.portalPopupMaterial=d,o.portalPopupOpacity=0}}o.setLocalScale(l,d,c),mr(o,h,u,m),console.log(`[Portal] Created plane portal: ${e.title||e.targetSceneName||"Untitled"}`)}else if("panorama"===e.type){o.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1,layers:[Ls.id]});const n=new t.StandardMaterial;e.panoramaUrl?(n.diffuse=new t.Color(0,0,0),n.emissive=new t.Color(1,1,1),n.emissiveIntensity=1,jo(e.panoramaUrl).then(e=>{if(vt)return;const i=new t.Texture(U.graphicsDevice,{width:e.width,height:e.height,format:t.PIXELFORMAT_RGBA8,mipmaps:!0,minFilter:t.FILTER_LINEAR_MIPMAP_LINEAR,magFilter:t.FILTER_LINEAR,addressU:t.ADDRESS_REPEAT,addressV:t.ADDRESS_CLAMP_TO_EDGE});i.setSource(e),n.emissiveMap=i,n.update(),o.textureLoaded=!0}).catch(()=>{})):(n.diffuse=new t.Color(.15,.55,.85),n.emissive=new t.Color(.1,.35,.55));const i=e.opacity??.9;n.opacity=i,n.blendType=i>=.95?t.BLEND_NONE:t.BLEND_ADDITIVEALPHA,n.depthTest=!0,n.depthWrite=i>=.95,n.update(),o.render.material=n,o.setLocalScale(.25*l,.25*c,.25*d)}else{o.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1,layers:[Ls.id]});const n=new t.StandardMaterial,i=$s(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*l,.2*c,.2*d)}if("plane"!==e.type&&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_BACK,e.update());const n=()=>{o.enabled&&(o.lookAt(he.getPosition()),o.rotateLocal(90,180,0))};U.on("update",n),o.once("destroy",()=>{U.off("update",n)})}e.visibilityRange&&(o.visibilityRange=e.visibilityRange,o.enabled=!1),nn.addChild(o),e.billboard&&(o.lookAt(he.getPosition()),o.rotateLocal(90,180,0));const g=gr(e,o.getPosition().clone());return g&&(nn.addChild(g),o.leaderLineEntity=g),Is.push(o),console.log(`[StorySplat Viewer] Created portal: ${e.title||e.targetSceneName||"Untitled"} -> ${e.targetSceneId}`),o}function wr(){const t=100*ki,e=$i(ki);Is.forEach(n=>{if(!n.portalData)return;let o=!0;const i=n.visibilityRange;i&&("waypoint"===i.type?o=ua(e,i.start,i.end):"percentage"===i.type&&(o=ua(t,i.start,i.end))),n.shouldBeVisible=o,n.hiddenUntilTextureLoaded?n.enabled=!1:n.enabled=o,n.leaderLineEntity&&(n.leaderLineEntity.enabled=n.enabled)})}function Sr(e,n){const o=he.camera.screenToWorld(e,n,he.camera.nearClip),i=he.camera.screenToWorld(e,n,he.camera.farClip);let s=null;Is.forEach(e=>{if(!e.enabled)return;if("plane"===e.portalData?.type)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 _r(t){if("panorama"===t.type||t.panoramaUrl&&!t.targetSceneId)return void Xo(t);if(!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;try{i.emit("portalActivated",{portalId:t.id,targetSceneId:t.targetSceneId,targetSceneName:t.targetSceneName})}catch(t){console.warn("[Portal] External portalActivated handler threw:",t)}if(n)console.log("[Portal] External portal handler detected, deferring navigation");else if(vt)console.log("[Portal] Viewer destroyed during emit, skipping internal 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=x(u,"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 ${d}`,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 i=await fetch(n);if(!i.ok)throw new Error(`Failed to fetch scene: ${i.status} ${i.statusText}`);const a=await i.json(),r=a.data||a;r.name&&function(t,e){const n=t.querySelector(".storysplat-portal-loading-text");n&&(n.textContent=x(u,"loadingScene").replace("{name}",e))}(e,r.name),s?.beginPortalTransition(),function(){console.log("[Portal] Cleaning up current scene for navigation..."),vt=!0,Ss(),Co(),Mi(),il&&(il.destroy(),il=null);Os.forEach(t=>{t.pause(),t.src=""}),Os.length=0,Ws.clear(),Us.forEach(t=>{t.close().catch(()=>{})}),Us.length=0,Zs.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);e&&e.stop(),t.entity.destroy()}),Zs.clear(),rr.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);e&&e.stop(),t.entity.destroy()}),rr.clear(),Cs.forEach(t=>{t.videoElement&&(t.videoElement.pause(),t.videoElement.src=""),t.alphaVideoElement&&(t.alphaVideoElement.pause(),t.alphaVideoElement.src="")}),pr.forEach(t=>t.destroy()),pr.length=0,ga(),Cs.forEach(t=>{const e=t.leaderLineEntity;e&&(e._leaderMat?.destroy(),e.destroy()),t.destroy()}),Cs.length=0,Is.forEach(t=>{const e=t.leaderLineEntity;e&&(e._leaderMat?.destroy(),e.destroy()),t.destroy()}),Is.length=0,Ms.forEach(t=>{Ds(t.name)}),Ms.length=0,Es.clear(),ks=!1,ot&&(ot.destroy(),ot=null);ut=null,mt.clear(),gt=!1,ir(),window.removeEventListener("keydown",sr),Ln.destroy(),zn.destroy(),An.destroy(),Mr.removeEventListener("mousemove",Er),Ua(),Ca.forEach(t=>{t.destroy()}),Ca.length=0,Js.forEach(t=>{t.destroy()}),Js.clear(),Be&&(clearTimeout(Be),Be=null,Fe=null);Te&&(Te.destroy(),Te=null,De=null);U.off("update",Oe),ta.forEach(t=>{t.destroy()}),ta.clear(),fa&&(fa.destroy(),fa=null);ya&&(U.scene.off("prerender",ya),ya=null);At&&(At.destroy(),At=null);vn&&(vn.destroy(),vn=null);const t=U.__htmlMeshManager;t&&t.destroy();const n=U.__customScriptSystem;n&&n.dispose();window.removeEventListener("resize",Zr),D.preloader&&D.preloader.remove();D.scrollControls&&D.scrollControls.remove();D.fullscreenButton&&D.fullscreenButton.remove();D.helpButton&&D.helpButton.remove();D.helpPanel&&D.helpPanel.remove();D.waypointInfo&&D.waypointInfo.remove();D.watermark&&D.watermark.remove();D.wasdHint&&D.wasdHint.remove();D.orbitHint&&D.orbitHint.remove();D.doubleTapHint&&D.doubleTapHint.remove();D.lookZone&&D.lookZone.remove();D.joystick&&D.joystick.remove();D.portalPopup&&D.portalPopup.remove();D.muteButton&&D.muteButton.remove();D.relightingButton&&D.relightingButton.remove();D.waypointListContainer&&D.waypointListContainer.remove();D.sceneMenuContainer&&D.sceneMenuContainer.remove();D.fpsCounter&&D.fpsCounter.remove();D.hotspotPopup&&D.hotspotPopup.remove();D.vrButton&&D.vrButton.remove();D.arButton&&D.arButton.remove();D.modeContainer&&D.modeContainer.remove();D.exploreControls&&D.exploreControls.remove();Ar&&(Ar(),Ar=null);kr&&(kr.destroy(),kr=null);Tr&&(document.removeEventListener("keydown",Tr),Tr=null);Lr&&(document.removeEventListener("click",Lr),Lr=null);Rr&&(zr&&(zr.removeEventListener("pointerdown",Rr),zr.removeEventListener("wheel",Rr)),window.removeEventListener("keydown",Rr),Rr=null,zr=null);Ho&&(clearTimeout(Ho),Ho=null);if(Oo){const t=Oo._escHandler;t&&document.removeEventListener("keydown",t),Oo.remove(),Oo=null}D._cleanup&&(D._cleanup(),D._cleanup=void 0);const o=document.getElementById("storysplat-viewer-styles");o&&o.remove();if(e.classList.remove("storysplat-viewer-container"),ye&&he.camera){try{he.camera.postEffects.removeEffect(ye)}catch(t){}ye=null}We&&(We.destroy(),We=null);U.destroy(),console.log("[Portal] Cleanup complete")}(),await new Promise(t=>setTimeout(t,100)),I&&I.parentNode&&I.remove();const l={lazyLoad:!1,...!0===o.disableAnalytics?{disableAnalytics:!0}:{},...o.analytics?{analytics:{...o.analytics,sceneId:t.targetSceneId}}:{},...o.googleTagId?{googleTagId:o.googleTagId}:{},...s?{__sessionRecorderHandle:s}:{}},c=await hn(e,r,l);return s?.attachViewer(c),Cr(e),void console.log(`[Portal] Successfully navigated to scene: ${t.targetSceneId}`)}catch(t){console.error("[Portal] Navigation failed:",t),Cr(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 Cr(t){const e=t.querySelector(".storysplat-portal-loading");e&&e.remove()}i.on("progressUpdate",()=>{yr()}),setTimeout(()=>{yr()},100),U.on("update",function(){const e=he.getPosition();Is.forEach(n=>{const o=n.portalData;if(!o)return;const i=!1!==n.shouldBeVisible;if("proximity"===o.activationMode&&i){const t=n.getPosition(),i=e.distance(t),s=o.proximityDistance||2;i<=s&&!n.proximityTriggered?(n.proximityTriggered=!0,console.log(`[Portal] Proximity triggered: ${o.title||o.targetSceneName}, navigating to scene ${o.targetSceneId}`),_r(o)):i>s&&(n.proximityTriggered=!1)}if("collision"===o.activationMode&&i){const i=n.getPosition(),s=n.getLocalScale(),a=n.getRotation(),r=(new t.Quat).copy(a).invert(),l=(new t.Vec3).sub2(e,i);r.transformVector(l,l);const c=Math.abs(s.x)/2,d=Math.abs(s.z)/2,p=.5,h=Math.abs(l.x)<=c+p,u=Math.abs(l.z)<=d+p,m=Math.abs(l.y)<=p,g=h&&u&&m;if(n.portalPopupMaterial&&n.portalPopupEntity){const t=e.distance(i),o=Math.max(Math.abs(s.x),Math.abs(s.z)),a=1.5*o+3,r=.5*o+.8;let l=0;t<=r?l=1:t<a&&(l=1-(t-r)/(a-r));const c=n.portalPopupOpacity||0,d=c+(l-c)*.08;n.portalPopupOpacity=d,n.portalPopupMaterial.opacity=d,n.portalPopupMaterial.update(),n.portalPopupEntity.enabled=d>.01}g&&!n.proximityTriggered?(n.proximityTriggered=!0,console.log(`[Portal] Collision triggered: ${o.title||o.targetSceneName}, navigating to scene ${o.targetSceneId}`),_r(o)):g||(n.proximityTriggered=!1)}})}),i.on("progressUpdate",()=>{wr()}),setTimeout(()=>{wr()},100);const Mr=U.graphicsDevice.canvas,Er=t=>{const e=Mr.getBoundingClientRect();In=t.clientX-e.left,Fn=t.clientY-e.top};q||Mr.addEventListener("mousemove",Er);let Pr=!1,kr=null,Tr=null,Ar=null,Lr=null,Rr=null,zr=null,Dr=!1,Ir=!1,Fr=0,Br=0;Mr.addEventListener("pointerdown",t=>{0===t.button&&(Ir=!0,Dr=!1,Fr=t.clientX,Br=t.clientY)}),Mr.addEventListener("pointermove",t=>{if(!Ir)return;const e=t.clientX-Fr,n=t.clientY-Br;e*e+n*n>25&&(Dr=!0)});const Vr=()=>{Ir=!1};function $r(e,n){const o=he.camera.screenToWorld(e,n,he.camera.nearClip),i=he.camera.screenToWorld(e,n,he.camera.farClip);let s=null;Cs.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}Mr.addEventListener("pointerup",Vr),Mr.addEventListener("pointercancel",Vr);let Ur=null,Or=!1,Nr=!1,Wr=!1;const Hr=e.querySelector(".storysplat-hotspot-popup"),Gr=e.querySelector(".storysplat-hotspot-overlay");function jr(t,e){eo||(Io||(Io=!0,H(D)),"fly"!==un.mode&&O(D,!1),"walk"!==Sn?"explore"!==Sn&&"orbit"!==Sn||async function(t,e){if("explore"!==Sn&&"orbit"!==Sn)return;if(Pr)return;console.log("[StorySplat Viewer] Double-click focus at:",t,e);const n=$r(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"===un.mode?un.flyTo(t):un.focus(t,!1))}try{const n=.25;Cn.resize(Math.floor(Mr.clientWidth*n),Math.floor(Mr.clientHeight*n));const o=U.scene.layers.getLayerByName("World");if(!o)return void console.warn("[StorySplat Viewer] World layer not found");Cn.prepare(he.camera,U.scene,[o]);const i=Math.floor(t*n),s=Math.floor(e*n),a=await Cn.getWorldPointAsync(i,s);if(a)if(isFinite(a.x)&&isFinite(a.y)&&isFinite(a.z)){const t=he.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"===un.mode?un.flyTo(a):un.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):async function(t,e){if(!vn)return;try{const n=.25;Cn.resize(Math.floor(Mr.clientWidth*n),Math.floor(Mr.clientHeight*n));const o=U.scene.layers.getLayerByName("World");if(!o)return;Cn.prepare(he.camera,U.scene,[o]);const i=Math.floor(t*n),s=Math.floor(e*n),a=await Cn.getWorldPointAsync(i,s);if(a&&isFinite(a.x)&&isFinite(a.y)&&isFinite(a.z)){const t=he.getPosition().distance(a);t>.3&&t<500&&vn.walkTo(a)}}catch(t){console.warn("[StorySplat Viewer] Walk-to pick failed:",t)}}(t,e))}Hr&&(Hr.addEventListener("mouseenter",()=>{Nr=!0}),Hr.addEventListener("mouseleave",()=>{Nr=!1,Ur&&"hover"===Ur.activationMode&&(Hr.classList.remove("visible"),Gr&&Gr.classList.remove("visible"),Ur=null)})),Mr.addEventListener("mousemove",n=>{const o=Mr.getBoundingClientRect(),i=n.clientX-o.left,s=n.clientY-o.top,r=Sr(i,s);if(r&&r.portal){const t=r.portal.activationMode||"click";return Mr.style.cursor="click"===t?"pointer":"default",void(Kn=!0)}const c=$r(i,s);if(c&&c.hotspot){const t=c.entity,n=c.hotspot,o=n.activationMode||"click";if("click"===o||"hover"===o||"video"===n.type?(Mr.style.cursor="pointer",Kn=!0):(Mr.style.cursor="default",Kn=!1),"hover"===n.activationMode&&Ur!==n){Ur=n;!(a&&l===n.id)&&"none"!==n.contentType&&(n.information||n.photoUrl||n.popupVideoUrl||n.iframeUrl||n.externalLinkUrl||n.modelUrl)&&F(e,n,u)}if(Or&&Cs.forEach(e=>{if(e!==t&&e._audioHoverPlaying&&e.hotspotAudioSlotId){const t=e.sound?.slot(e.hotspotAudioSlotId);t?.isPlaying&&t.stop(),e._audioHoverPlaying=!1}}),Or&&t._audioHoverMode&&t.hotspotAudioSlotId&&!t._audioHoverPlaying){const e=t.sound?.slot(t.hotspotAudioSlotId);e&&!e.isPlaying&&t.hotspotAudioReady&&(e.play(),t._audioHoverPlaying=!0)}}else if(Wr||(Mr.style.cursor="default",Kn=!1),Or&&Cs.forEach(t=>{if(t._audioHoverPlaying&&t.hotspotAudioSlotId){const e=t.sound?.slot(t.hotspotAudioSlotId);e?.isPlaying&&e.stop(),t._audioHoverPlaying=!1}}),Ur&&"hover"===Ur.activationMode&&!Nr){const t=e.querySelector(".storysplat-hotspot-popup"),n=e.querySelector(".storysplat-hotspot-overlay");t&&t.classList.remove("visible"),n&&n.classList.remove("visible"),Ur=null}if(xn?.metadata?.segments?.length&&!a){const a=Mr.clientWidth,r=Mr.clientHeight,l=i/a*2-1,c=1-s/r*2,d=he.getPosition(),p=he.forward,h=he.right,u=he.up,m=he.camera.fov*Math.PI/180,g=a/r,f=Math.tan(m/2),y=new t.Vec3(p.x+l*g*f*h.x+c*f*u.x,p.y+l*g*f*h.y+c*f*u.y,p.z+l*g*f*h.z+c*f*u.z).normalize();let v=0,b=1/0;const x=2;for(const t of xn.metadata.segments){const e=t.centroid_3d[0],n=t.centroid_3d[1],o=t.centroid_3d[2],i=e-d.x,s=n-d.y,a=o-d.z,r=i*y.x+s*y.y+a*y.z;if(r<0)continue;const l=d.x+r*y.x,c=d.y+r*y.y,p=d.z+r*y.z,h=Math.sqrt((l-e)**2+(c-n)**2+(p-o)**2);h<x*Math.max(1,.1*r)&&h<b&&(b=h,v=t.id)}if(xn.setHighlight(v),v>0){const t=xn.metadata.segments.find(t=>t.id===v);if(t){wn||(wn=document.createElement("div"),wn.style.cssText="position:absolute;pointer-events:none;padding:4px 10px;background:rgba(0,0,0,0.75);color:#fff;border-radius:6px;font-size:13px;font-family:sans-serif;white-space:nowrap;z-index:9999;transform:translate(-50%,-120%);transition:opacity 0.15s;",e.appendChild(wn));const[i,s,a]=t.avg_color;wn.textContent="";const r=document.createElement("span");r.style.cssText=`display:inline-block;width:10px;height:10px;border-radius:50%;background:rgb(${i},${s},${a});margin-right:6px;vertical-align:middle;`,wn.appendChild(r),wn.appendChild(document.createTextNode(`${t.name} (${t.gaussian_count.toLocaleString()} splats)`)),wn.style.left=n.clientX-o.left+"px",wn.style.top=n.clientY-o.top+"px",wn.style.opacity="1"}}else wn&&(wn.style.opacity="0")}}),Mr.addEventListener("click",n=>{if(Pr)return;if(Ki)return;if(Dr)return void(Dr=!1);const o=Mr.getBoundingClientRect(),s=n.clientX-o.left,c=n.clientY-o.top,d=Sr(s,c);if(null!==d&&d.portal){const t=d.portal;if(console.log("[StorySplat Viewer] Portal clicked:",t.title||t.targetSceneName),a)return void i.emit("portalClick",{portal:t});if("click"===(t.activationMode||"click")){!1!==t.confirmNavigation&&(t.title||t.targetSceneName||t.targetSceneId)&&Fs?(t=>{if(!Fs)return;const e=Fs.querySelector(".storysplat-portal-popup-title");e&&(t.title?e.textContent=t.title:t.targetSceneName?e.textContent=`${x(u,"switchScenes").replace("?","")} ${t.targetSceneName}?`:e.textContent=x(u,"switchScenes")),Bs=t,Fs.classList.add("visible")})(t):_r(t)}return}const p=$r(s,c);if(null!==p){const n=p.entity,o=p.hotspot;if(console.log("[StorySplat Viewer] Hotspot clicked:",o.title),i.emit("hotspotClick",{hotspot:o}),n.hotspotAudioSlotId&&n.hotspotAudioReady){if("click"===(o.audioTriggerMode||("proximity"===(n.mediaTriggerMode||"click")?"proximity":"click"))){const t=n.sound?.slot(n.hotspotAudioSlotId);if(t){const e=o.audioClickBehavior||"pause-resume",i=t.isPlaying,s=t.isPaused;i||s?s&&"pause-resume"===e?(t.resume(),n.hotspotAudioPlaying=!0,console.log("[Audio] Hotspot audio resumed (click):",o.title)):s&&"stop-restart"===e?(t.play(),n.hotspotAudioPlaying=!0,console.log("[Audio] Hotspot audio restarted (click):",o.title)):i&&("stop-restart"===e?(t.stop(),n.hotspotAudioPlaying=!1,console.log("[Audio] Hotspot audio stopped (click):",o.title)):(t.pause(),n.hotspotAudioPlaying=!1,console.log("[Audio] Hotspot audio paused (click):",o.title))):(t.play(),n.hotspotAudioPlaying=!0,console.log(`[Audio] Hotspot audio started (click/${e}):`,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?(vr(n,o),console.log("[Hotspot] Video started")):(br(n),console.log("[Hotspot] Video paused"))}const s=o.activationMode||"click",c="none"!==o.contentType&&(o.title||o.information||o.photoUrl||o.popupVideoUrl||o.iframeUrl||o.externalLinkUrl||o.modelUrl);"click"===s&&c&&(eo?Po?ko():function(e){if(!eo)return;Mo||(Mo=new t.Entity("arContentPlane"),Mo.addComponent("render",{type:"plane"}),Mo.setLocalScale(.8,1,.45),U.root.addChild(Mo));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",Eo||(Eo=new t.Texture(U.graphicsDevice,{width:n,height:o,format:t.PIXELFORMAT_RGBA8,mipmaps:!1,minFilter:t.FILTER_LINEAR,magFilter:t.FILTER_LINEAR})),Eo.setSource(i);const c=new t.StandardMaterial;c.diffuse=new t.Color(0,0,0),c.diffuseMap=Eo,c.emissive=new t.Color(1,1,1),c.emissiveMap=Eo,c.useLighting=!1,c.blendType=t.BLEND_NORMAL,c.cull=t.CULLFACE_NONE,c.depthWrite=!0,c.update(),Mo.render&&Mo.render.meshInstances[0]&&(Mo.render.meshInstances[0].material=c),Mo.enabled=!0,Po=!0,console.log("[StorySplat Viewer] AR content displayed for hotspot:",e.title)}(o):a&&l===o.id||F(e,o,u));const d=o.teleportWaypoint??o.teleportToWaypoint,h=o.teleportPercent??o.teleportToPercent,m=o.teleportMode||"animate";let g=null;if(void 0!==d&&-1!==d){const t=r.waypoints?.length||1,e=Math.max(0,Math.min(d,t-1));g=Vi(e),console.log("[Hotspot] Teleporting to waypoint:",e,"(progress:",g,", mode:",m,")")}else void 0!==h&&-1!==h&&(g=Math.max(0,Math.min(h/100,1)),console.log("[Hotspot] Teleporting to percent:",h,"(progress:",g,", mode:",m,")"));null!==g&&("instant"===m?(ki=g,Ti=g,as(ki)):(Ti=g,cs(g,800)))}null===p&&null===d&&"single"===Ft&&("explore"!==Sn&&"walk"!==Sn&&"orbit"!==Sn||jr(s,c))}),Mr.addEventListener("dblclick",t=>{if(eo)return;if("double"!==Ft)return;const e=Mr.getBoundingClientRect();jr(t.clientX-e.left,t.clientY-e.top)});let Xr=0,qr=!1;Mr.addEventListener("touchstart",t=>{eo||t.touches.length>1&&(qr=!0)}),Mr.addEventListener("touchend",t=>{if(eo)return;if(1!==t.changedTouches.length)return;if(qr)return 0===t.touches.length&&(qr=!1),void(Xr=0);const e=Date.now();if(e-Xr<300){if("double"===Ft){const e=t.changedTouches[0],n=Mr.getBoundingClientRect();jr(e.clientX-n.left,e.clientY-n.top)}Xr=0}else Xr=e}),Mr.addEventListener("touchcancel",t=>{eo||0===t.touches.length&&(qr=!1,Xr=0)}),document.addEventListener("keydown",t=>{const e=document.activeElement?.tagName;if("INPUT"===e||"TEXTAREA"===e||document.activeElement?.isContentEditable)return;if(["KeyW","KeyA","KeyS","KeyD","ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Space"].includes(t.code)&&vn&&vn.cancelWalkTo(),t.shiftKey&&t.ctrlKey&&"KeyD"===t.code&&vn){t.preventDefault();const e=!vn.collisionDebugVisible;vn.setCollisionDebug(e),console.log(`[StorySplat Viewer] Collision debug: ${e?"ON":"OFF"} (${vn.collisionMeshEntities.length} meshes)`)}}),Yo(.2,"Initializing...");const Yr=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"),Yo(.3,"Loading 4DGS frames..."),At=new se(U,{frameUrls:n.frameSequence.frameUrls,fps:n.frameSequence.fps||24,loop:!1!==n.frameSequence.loop,preloadCount:n.frameSequence.preloadCount||10,autoplay:n.frameSequence.autoplay||!1,rotation:n.frameSequence.rotation},{onFrameChange:(t,e)=>{i.emit("frameChange",t,e)},onLoadProgress:(t,e)=>{Yo(.3+t/e*.6,`Loading frames... ${t}/${e}`)},onError:t=>{console.error("[StorySplat Viewer] Frame sequence error:",t),i.emit("error",new Error(t))}}),At.on("complete",()=>{i.emit("frameComplete")}),U.on("update",t=>{At&&!vt&&At.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=[],l=ln()||!0===r.forceMobilePreview,c=Boolean(r.mobileLodMetaUrl||r.mobileSogUrl||r.mobileSplatUrl||r.mobileCompressedPlyUrl),d=l&&c,p=d?r.mobileLodMetaUrl:r.lodMetaUrl,h=d?r.mobileSogUrl:r.sogUrl,m=d?r.mobileCompressedPlyUrl:void 0,g=d?r.mobileSplatUrl:r.splatUrl;console.log("[SPLAT] Building URL priority list..."),console.log("[SPLAT] Mobile asset selection:",{onMobile:l,forceMobilePreview:!0===r.forceMobilePreview,usingMobileAsset:d}),console.log("[SPLAT] Available URLs:",{lodMetaUrl:p||"(none)",sogUrl:h||"(none)",compressedPlyUrl:m||"(none)",splatUrl:g||"(none)",fallbackUrls:r.fallbackUrls?.length||0}),p&&(a.push(p),console.log("[SPLAT] ✓ LOD streaming URL added (highest priority):",p)),h&&(a.push(h),console.log("[SPLAT] ✓ SOG URL added (second priority):",h)),m&&m!==g&&(a.push(m),console.log("[SPLAT] ✓ Compressed PLY URL added (third priority):",m)),g&&(a.push(g),console.log("[SPLAT] ✓ Original splat URL added (fourth priority):",g)),r.fallbackUrls&&(a.push(...r.fallbackUrls),console.log("[SPLAT] ✓ Fallback URLs added:",r.fallbackUrls)),console.log("[SPLAT] Final URL priority order:",a),console.log("[SPLAT] Will try URLs in order: LOD streaming > SOG > compressed PLY > PLY/Other"),Yo(.3,x(u,"loading"));for(const l of a)if(l)try{const a=decodeURIComponent(l.toLowerCase().split(/[?#]/)[0]).split("/").pop()||"";if(a.endsWith(".glb")||a.endsWith(".gltf"))return console.log("[SPLAT] Detected mesh format (GLB/GLTF), loading as container:",l),la({id:"__primary-mesh__",name:"Primary Model",modelUrl:l,position:{x:0,y:0,z:0}},0),void Yo(1,"");const c=l.split(".").pop()?.toLowerCase()||"splat",d="gsplat",p=l.includes("lod-meta.json"),h=l.includes(".sog")||p;console.log("[SPLAT] Attempting to load URL:",l),console.log("[SPLAT] Format detection:",{extension:c,isLodStreaming:p,isSogFormat:h,assetType:d});const m=p?await gi(l):null,g=new t.Asset("splat-"+Date.now(),d,{url:l});g.on("progress",(t,n)=>{if(n>0){Zo(l)&&(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)`),Yo(o,`${x(u,"loading")} ${i}%`)}}),s=l,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:l}),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 c=()=>{h&&a&&window.removeEventListener("unhandledrejection",a)};g.ready(()=>{if(vt)return console.log("[SPLAT] Ignoring load - viewer was destroyed during loading"),c(),void e(new Error("Viewer destroyed"));console.log("[SPLAT] ✓ Asset loaded and resources ready");try{if(it=m,ot=Ko(g,l,m),vn){vn.setSplatEntity(ot);const t=vn.voxelCollisionInstance;t&&un.setVoxelCollision(t,ot)}pn(l)?(console.log("[SPLAT] ✓ LOD streaming enabled for this splat"),console.log("[SPLAT] LOD configured:",{lodBaseDistance:Q,lodMultiplier:J,preset:Z})):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||r.revealEffect||"none",i=o.revealStyle||n.revealStyle||r.revealStyle||"bloom",a=Lt(e,i);if(a){ot.addComponent("script");const t="radial"===i?xt():Ct();st=ot.script?.create?.(t)??null,st&&(st.enabled=!1,st.center.set(0,0,0),st.speed=a.speed,st.acceleration=a.acceleration,st.delay=a.delay,st.oscillationIntensity=a.oscillationIntensity,st.dotTint.set(a.dotTint.r,a.dotTint.g,a.dotTint.b),st.waveTint.set(a.waveTint.r,a.waveTint.g,a.waveTint.b),st.endRadius=a.endRadius,console.log("[StorySplat Viewer] Reveal effect configured (waiting to start):",i,e))}else console.log("[StorySplat Viewer] Reveal effect disabled");p?(c(),t()):setTimeout(()=>{s||(c(),t())},100)}catch(t){console.error("[StorySplat Viewer] Error during gsplat setup:",t),c(),e(t)}}),g.on("error",t=>{const n=t;console.error("[SPLAT] ✗ Asset load error:",{url:l,assetType:d,isSogFormat:h,isLodFormat:p,error:t,message:n?.message||"Unknown error",status:n?.status||n?.statusCode||"N/A"}),c(),e(t)}),Qo(g,l)}),Vt=s;const f=Zo(s);return i.emit("loaded",{bandwidthUsed:f?e:0,isStorySplatHosted:f}),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?Z:"N/A",bandwidthUsed:`${(e/1024/1024).toFixed(2)}MB`,isStorySplatHosted:f,bandwidthCounted:f?"Yes":"No (self-hosted)"})}catch(t){console.warn("[SPLAT] ✗ Failed to load URL, trying next fallback:",l),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"))};Yr().then(()=>{if(Yo(1,"Ready!"),a||(r.hotspots&&0!==r.hotspots.length?(console.log(`[StorySplat Viewer] Creating ${r.hotspots.length} hotspots...`),r.hotspots.forEach((t,e)=>{fr(t,e)})):console.log("[StorySplat Viewer] No hotspots to create"),function(){if(!r.portals||0===r.portals.length)return void console.log("[StorySplat Viewer] No portals to create");const t=r.portals.filter(t=>!t.menuOnly);console.log(`[StorySplat Viewer] Creating ${t.length} portals (${r.portals.length-t.length} menu-only)...`),t.forEach((t,e)=>{xr(t,e)});for(const t of r.portals)"panorama"===t.type&&t.panoramaUrl&&jo(t.panoramaUrl).catch(()=>{})}(),r.mirrorPlanes&&0!==r.mirrorPlanes.length&&(console.log(`[StorySplat Viewer] Creating ${r.mirrorPlanes.length} mirror plane(s)...`),r.mirrorPlanes.forEach((t,e)=>{const n=zs(t,e);a&&t.id&&ol.set(t.id,n)})),r.waypoints&&0!==r.waypoints.length&&(r.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:ar(i.distanceModel||"exponential"),maxDistance:i.maxDistance||1e3,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,Hs(t,t=>{const e=Zs.get(s);e&&(e.playing=t)}));const e=Zs.get(s);e&&(e.assetReady=!0,"autoplay"===i.triggerMode&&t&&(t.play(),e.playing=!0)),console.log(`[Audio] Waypoint audio loaded: ${s}, spatialSound=${i.spatialSound}, maxDistance=${i.maxDistance}`)}),U.assets.load(c),nn.addChild(a),Zs.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}`)}})}),Zs.size>0&&console.log(`[StorySplat Viewer] Setup ${Zs.size} waypoint audio sources`)),function(){const e=r.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=ar(e.distanceModel||"linear");i.addComponent("sound",{positional:!1!==e.spatialSound,refDistance:e.refDistance||1,maxDistance:e.maxDistance||10,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(vt)return;const t=i.sound?.slot(n);t&&(t.asset=a.id);const o=rr.get(n);o&&(t&&Hs(t,t=>{o.playing=t}),o.assetReady=!0,"autoplay"===(e.triggerMode||(!1!==e.autoplay?"autoplay":"proximity"))&&t&&(console.log(`[Audio] Autoplay emitter: ${e.name||n}`),t.play(),o.playing=!0)),console.log(`[Audio] Emitter loaded: ${e.name||n}, triggerMode=${e.triggerMode||"autoplay"}, spatial=${!1!==e.spatialSound}, maxDistance=${e.maxDistance||10}`)}),U.assets.add(a),U.assets.load(a),nn.addChild(i),rr.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})`)}),rr.size>0&&console.log(`[StorySplat Viewer] Setup ${rr.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:",r.particles),console.log("[Particle] Type:",typeof r.particles),console.log("[Particle] Is Array:",Array.isArray(r.particles)),!r.particles||0===r.particles.length)return console.log("[Particle] ⚠️ No particle systems to create (particles array is empty or undefined)"),void console.log("═══════════════════════════════════════");console.log(`[Particle] 📋 Found ${r.particles.length} particle system(s) to create`);for(let t=0;t<r.particles.length;t++){const e=r.particles[t];console.log(`[Particle] --- Particle System ${t+1}/${r.particles.length} ---`),console.log("[Particle] Raw config:",JSON.stringify(e,null,2));try{const t=e.particleTexture||"flare";let n;if("custom"===t&&e.customTextureUrl){const t=`custom_${e.customTextureUrl}`;console.log(`[Particle] Custom texture: ${e.customTextureUrl.substring(0,60)}...`),n=await oa(t,e.customTextureUrl)}else{const e=ea.has(t)?t:"flare";console.log(`[Particle] Texture: ${e} (procedural)`),n=na(e)}console.log("[Particle] ✅ Texture ready:",n.name),console.log("[Particle] Creating entity...");const o=ia(e);console.log("[Particle] ✅ Entity created:",o.name),o.particlesystem?(o.particlesystem.colorMap=n,console.log("[Particle] ✅ Texture applied to particle system"),console.log("[Particle] Particle system properties:",{numParticles:o.particlesystem.numParticles,lifetime:o.particlesystem.lifetime,rate:o.particlesystem.rate,loop:o.particlesystem.loop,autoPlay:o.particlesystem.autoPlay})):console.warn("[Particle] ⚠️ Entity has no particlesystem component!"),nn.addChild(o),console.log("[Particle] ✅ Entity added to scene");const i=o.getPosition();console.log(`[Particle] Position: (${i.x.toFixed(2)}, ${i.y.toFixed(2)}, ${i.z.toFixed(2)})`);const s=(e.id||e.name||`particle-${Js.size}`).replace(/[^a-zA-Z0-9]/g,"_");Js.set(s,o),console.log(`[Particle] ✅ SUCCESS: Created "${e.name||s}"`)}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: ${Js.size}/${r.particles.length} particle systems created`),console.log("[Particle] Active particle systems:",Array.from(Js.keys())),console.log("═══════════════════════════════════════")}(),async function(){r.customMeshes&&0!==r.customMeshes.length?(console.log(`[StorySplat Viewer] Creating ${r.customMeshes.length} custom meshes...`),console.log("[StorySplat Viewer] All custom mesh configs:",r.customMeshes),setTimeout(()=>{let t=0,e=0;r.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{la(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] ${r.customMeshes.length} custom meshes queued for loading`)):console.log("[StorySplat Viewer] No custom meshes to create")}(),xa(),r.lights&&0!==r.lights.length?(console.log(`[StorySplat Viewer] Creating ${r.lights.length} custom lights...`),r.lights.forEach((t,e)=>{let n=null;switch(t.type){case"point":n=Pa(t);break;case"directional":n=ka(t);break;case"hemispheric":n=Ta(t);break;case"ambient":Aa(t);break;case"spot":n=La(t);break;default:return void console.warn("[StorySplat Viewer] Unknown light type:",t.type)}if(n){nn.addChild(n),Ca.push(n);const o=t.id||t.name||`light-${e}`;Ma.set(o,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")),za(),Oa(),zt.length>0&&zt[0].url&&(Gt&&!a?Ci(zt[0].url,zt[0]).then(()=>{const t=()=>{const e=!Yt||Yt.material,n=!Zt||Zt.material;e&&n?requestAnimationFrame(()=>{D.preloader&&P(D.preloader),i.emit("loaded",{url:zt[0].url})}):requestAnimationFrame(t)};requestAnimationFrame(t)}):Gt||yi(zt[0].url,zt[0])),st&&!Gt&&(st.enabled=!0,console.log("[StorySplat Viewer] Reveal effect started immediately")),Gt&&!a);else if(st||!r.lodMetaUrl&&!r.mobileLodMetaUrl)setTimeout(()=>{D.preloader&&P(D.preloader)},200);else{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(()=>{D.preloader&&P(D.preloader)})})):requestAnimationFrame(o)};requestAnimationFrame(o)}if(function(){if(!r.includeXR)return;const n=r.xrMode||"both",o=!!U.xr;if(o&&("vr"===n||"both"===n)){const e=U.xr;e.isAvailable(t.XRTYPE_VR)&&(D.vrButton?.classList.add("available"),console.log("[StorySplat Viewer] VR is available")),e.on("available:"+t.XRTYPE_VR,t=>{t?D.vrButton?.classList.add("available"):D.vrButton?.classList.remove("available")})}if("ar"===n||"both"===n){const t=!!navigator.mediaDevices?.getUserMedia,n=ln();t&&n?(D.arButton?.classList.add("available"),Le("AR is available via 8th Wall",{hasCamera:t,mobile:n}),Ie(e,r.eighthWallBaseUrl).catch(t=>{const e=t instanceof Error?t.message:String(t);Re("8th Wall pre-load failed; will retry on AR tap",{message:e}),So(`AR engine failed to load: ${e}`)})):Le("AR not available",{hasCamera:t,mobile:n})}let s=1,a=4,l=!1;const c=new t.Vec3,d=new t.Quat,p=new t.Vec3,h=new t.Quat;if(o&&("vr"===n||"both"===n)){Ze.addComponent("script"),Ze.script.create(Je),Ze.script.create(tn);const t=U.xr;t.on("start",()=>{if(oo)return;eo=!0,console.log("[StorySplat Viewer] VR session started"),D.vrButton?.classList.add("active"),D.vrButton.textContent=x(u,"exitVr"),un.disable(),vn&&vn.disable(),c.copy(Ze.getPosition()),d.copy(Ze.getRotation()),p.copy(he.getPosition()),h.copy(he.getRotation());const t=h.getEulerAngles();Ze.setPosition(p.x,0,p.z),Ze.setEulerAngles(0,t.y,0),U.autoRender=!0,he.camera.nearClip=.01,U.scene.gsplat&&(s=U.scene.gsplat.colorUpdateDistance??1,a=U.scene.gsplat.colorUpdateAngle??4,U.scene.gsplat.colorUpdateDistance=5,U.scene.gsplat.colorUpdateAngle=20),We&&(l=We.enabled,We.enabled=!1),An.enabled=!1,$n=!1,Qn=0,i.emit("xrStart",{type:"vr"})}),t.on("end",()=>{oo||(eo=!1,console.log("[StorySplat Viewer] VR session ended"),D.vrButton?.classList.remove("active"),D.vrButton&&(D.vrButton.textContent=x(u,"vr")),no=null,Ze.setPosition(c),Ze.setRotation(d),he.setPosition(p),he.setRotation(h),U.autoRender=!1,U.renderNextFrame=!0,he.camera.nearClip=r.nearClip||.1,"explore"===Sn?un.enable():"walk"===Sn&&vn&&vn.enable(),U.scene.gsplat&&(U.scene.gsplat.colorUpdateDistance=s,U.scene.gsplat.colorUpdateAngle=a),We&&(We.enabled=l),requestAnimationFrame(()=>{e.prepend(U.graphicsDevice.canvas),U.renderNextFrame=!0}),i.emit("xrEnd",{}))})}D.vrButton&&o&&D.vrButton.addEventListener("click",()=>{const e=U.xr;eo&&"vr"===no?e.end():!eo&&e.isAvailable(t.XRTYPE_VR)&&(no="vr",he.camera.startXr(t.XRTYPE_VR,t.XRSPACE_LOCALFLOOR,{optionalFeatures:["hand-tracking"],callback:t=>{t&&(console.error("[StorySplat Viewer] Failed to start VR:",t),no=null)}}))}),D.arButton&&D.arButton.addEventListener("click",()=>{"ar"===no||oo||io?Co():eo||_o()})}(),r.htmlMeshes&&r.htmlMeshes.length>0){console.log("[StorySplat Viewer] Setting up HTML meshes:",r.htmlMeshes.length);const t=Qt(U,r.htmlMeshes);U.__htmlMeshManager=t,i.on("progressUpdate",()=>{const e=100*ki,n=$i(ki);t.updateVisibility(e,n,"loop"===Ii)})}try{const t=new URLSearchParams(window.location.search),e=t.get("waypoint"),n=t.get("autoplay");if(null!==e&&r.waypoints&&r.waypoints.length>0){const t=parseInt(e,10);!isNaN(t)&&t>=0&&t<r.waypoints.length&&(console.log("[StorySplat Viewer] URL param: navigating to waypoint",t),ds(t))}"true"!==n||o.autoPlay||r.autoPlay||(console.log("[StorySplat Viewer] URL param: starting autoplay"),ws())}catch(t){console.warn("[StorySplat Viewer] Could not parse URL parameters:",t)}i.emit("ready"),console.log("[StorySplat Viewer] Ready");if((r.audioEmitters&&r.audioEmitters.length>0||r.narrationTrack&&r.narrationTrack.audioUrl||r.waypoints?.some(t=>t.interactions?.some(t=>"audio"===t.type))||r.hotspots?.some(t=>t.audioUrl)||r.customMeshes?.some(t=>t.interaction?.playAudio&&t.interaction?.audioUrl))&&D.tapForAudio){const t=U.systems.sound?.manager?.context;if(t&&"suspended"===t.state){D.tapForAudio.style.display="",D.tapForAudio.classList.add("visible");const n=()=>{t.resume(),Us.forEach(t=>{"suspended"===t.state&&t.resume()}),Cs.forEach(t=>{const e=t.hotspotAudioSlotId,n=t.hotspotData;if(e&&"autoplay"===n?.audioTriggerMode&&t.hotspotAudioReady){const n=t.sound?.slot(e);n&&!n.isPlaying&&(n.play(),t.hotspotAudioPlaying=!0)}}),rr.forEach(t=>{const{config:e,slotId:n,entity:o,assetReady:i}=t;if(!i)return;if("autoplay"===(e.triggerMode||(!1!==e.autoplay?"autoplay":"proximity"))){const e=o.sound?.slot(n);e&&!e.isPlaying&&(e.play(),t.playing=!0)}}),Zs.forEach(t=>{const{config:e,slotId:n,entity:o,assetReady:i}=t;if(!i)return;if("autoplay"===(e.triggerMode||"proximity")){const e=o.sound?.slot(n);e&&!e.isPlaying&&(e.play(),t.playing=!0)}}),sa.forEach(t=>{const{config:e,audioSlotId:n,entity:o}=t;if(!n)return;if("autoplay"===(e.interaction?.audioTriggerMode||e.interaction?.activationMode||"click")){const e=o.sound?.slot(n);e&&!e.isPlaying&&(e.play(),t.audioPlaying=!0)}}),D.tapForAudio&&(D.tapForAudio.classList.remove("visible"),setTimeout(()=>{D.tapForAudio&&(D.tapForAudio.style.display="none")},500)),e.removeEventListener("click",n),e.removeEventListener("touchstart",n)};e.addEventListener("click",n),e.addEventListener("touchstart",n)}}if(a||Vo(S),At&&un&&un.syncFromCamera(new t.Vec3(0,1,0)),c){if(A(D,{nextWaypoint:ps,prevWaypoint:hs,play:ws,pause:Ss,isPlaying:()=>nt,getCurrentWaypointIndex:()=>et,getWaypointCount:()=>r.waypoints?.length||0,getWaypoints:()=>r.waypoints||[],setCameraMode:Vo,shareCurrentView:Tt,on:(t,e)=>i.on(t,e)},S,u,{container:e,hideProgressText:p.hideProgressText}),function(t,e){if(!t.sceneMenuContainer)return;const n=t.sceneMenuContainer.querySelectorAll(".storysplat-scene-menu-item[data-portal-id]"),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")})})}(D,t=>{const e=r.portals?.find(e=>e.id===t);e&&_r(e)}),D.muteButton&&D.muteButton.addEventListener("click",()=>{const t=(Ns?dr():cr(),Ns),e=D.muteButton.querySelector(".storysplat-unmuted-icon"),n=D.muteButton.querySelector(".storysplat-muted-icon");e&&(e.style.display=t?"none":""),n&&(n.style.display=t?"":"none"),D.muteButton.setAttribute("aria-label",x(u,t?"unmute":"mute"))}),D.relightingButton&&ut){const t=r.splatRelighting?.viewerDefaultOn??!1;console.log("[StorySplat Viewer] Relighting toggle button wired up, startOn:",t);let e=t,n=t?1:0;D.relightingButton.classList.toggle("active",t);let o=null;D.relightingButton.addEventListener("click",()=>{if(st&&st.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:",mt.size),D.relightingButton.classList.toggle("active",e),null!==o&&cancelAnimationFrame(o);const t=()=>{if(0===mt.size)return;const e=ut?ut.relightFade:n,i=n-e;if(Math.abs(i)<.01){for(const t of mt)t.setRelightFade(n);return void(o=null)}const s=e+.15*i;for(const t of mt)t.setRelightFade(s);o=requestAnimationFrame(t)};o=requestAnimationFrame(t)})}D.fisheyeButton&&D.fisheyeButton.addEventListener("click",()=>{const t=Pe>0;ke(t?0:.95),D.fisheyeButton.classList.toggle("active",!t)});const t=e.querySelector(".storysplat-hotspot-popup-close");t&&t.addEventListener("click",()=>{Ks(),Qs(e)}),D.waypointListContainer&&r.waypoints&&r.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")})})}(D,t=>{console.log("[StorySplat Viewer] Waypoint list: jumping to waypoint",t),"tour"!==Sn&&Vo("tour"),ds(t)}),i.on("waypointChange",({index:t})=>{G(D,t)}),G(D,0)),i.emit("progressUpdate",{progress:ki,index:et}),r.waypoints&&r.waypoints.length>0&&i.emit("waypointChange",{index:0,waypoint:r.waypoints[0],prevIndex:-1})}(o.autoPlay||r.autoPlay)&&ws()}).catch(t=>{console.error("[StorySplat Viewer] Failed to initialize:",t),D.preloader&&P(D.preloader),i.emit("error",t)});const Zr=()=>{U.resizeCanvas()};window.addEventListener("resize",Zr);const Kr=new Map,Qr=new Map,Jr=new Map,tl=new Map,el=new Map,nl=new Map,ol=new Map;a&&(Cs.forEach(t=>{const e=t.hotspotData?.id;e&&Kr.set(e,t)}),Ca.forEach((t,e)=>{const n=r.lights?.[e],o=n?.id||n?.name||`light-${e}`;Qr.set(o,t)}),Js.forEach((t,e)=>{Jr.set(e,t)}),Is.forEach(t=>{const e=t.portalData?.id;e&&el.set(e,t)}));let il=null;const sl=(t,e)=>{switch(e){case"hotspot":return Kr.get(t)||Cs.find(e=>e.hotspotData?.id===t)||null;case"portal":return el.get(t)||Is.find(e=>e.portalData?.id===t)||null;case"light":return Qr.get(t)||Ma.get(t)||null;case"particle":{const e=t.replace(/[^a-zA-Z0-9]/g,"_");return Js.get(e)||Js.get(t)||null}case"audioEmitter":return rr.get(t)?.entity||null;case"customMesh":{const e=sa.get(t);return e?.entity||null}case"mirrorPlane":return ol.get(t)||Ms.find(e=>e.name===t)||null;default:return null}},al={fog:{apply:t=>{const e=U.scene.fog;if(!e)return;t.has("fog.density")&&(e.density=t.get("fog.density")),t.has("fog.start")&&(e.start=t.get("fog.start")),t.has("fog.end")&&(e.end=t.get("fog.end"));const n=t.get("fog.color.r"),o=t.get("fog.color.g"),i=t.get("fog.color.b");void 0===n&&void 0===o&&void 0===i||e.color.set(n??e.color.r,o??e.color.g,i??e.color.b)},readValues:()=>{const t=U.scene.fog;return t?{"fog.density":t.density??.05,"fog.start":t.start??10,"fog.end":t.end??100,"fog.color.r":t.color?.r??.72,"fog.color.g":t.color?.g??.77,"fog.color.b":t.color?.b??.82}:null}},weather:{apply:t=>{if(!Te)return;let e=!1;t.has("weather.speed")&&(Te.speed=t.get("weather.speed"),e=!0),t.has("weather.drift")&&(Te.drift=t.get("weather.drift"),e=!0),t.has("weather.opacity")&&(Te.opacity=t.get("weather.opacity"),e=!0),t.has("weather.elongate")&&(Te.elongate=t.get("weather.elongate"),e=!0),t.has("weather.particleMinSize")&&(Te.particleMinSize=t.get("weather.particleMinSize"),e=!0),t.has("weather.particleMaxSize")&&(Te.particleMaxSize=t.get("weather.particleMaxSize"),e=!0);const n=t.get("weather.color.r"),o=t.get("weather.color.g"),i=t.get("weather.color.b");void 0===n&&void 0===o&&void 0===i||(void 0!==n&&(Te.color[0]=n),void 0!==o&&(Te.color[1]=o),void 0!==i&&(Te.color[2]=i),e=!0),e&&Te.markScalarsDirty()},readValues:()=>Te?{"weather.speed":Te.speed,"weather.drift":Te.drift,"weather.opacity":Te.opacity,"weather.elongate":Te.elongate,"weather.particleMinSize":Te.particleMinSize,"weather.particleMaxSize":Te.particleMaxSize,"weather.color.r":Te.color[0],"weather.color.g":Te.color[1],"weather.color.b":Te.color[2]}:null}};if(r.entityAnimations&&r.entityAnimations.length>0&&(il=new ue(sl,r.entityAnimations,al),console.log(`[StorySplat Viewer] Entity animation system initialized with ${r.entityAnimations.length} animation(s)`)),p.measurementsEnabled&&D.measureButton&&D.measureCanvas){const oc=D.measureButton,ic=D.measureCanvas,sc=p.sceneScale??r.uiOptions?.sceneScale??1,ac=p.sceneScaleUnit??r.uiOptions?.sceneScaleUnit??"meters",rc=p.measurementColor??r.uiOptions?.measurementColor??"#FF9800",lc={meters:"m",centimeters:"cm",feet:"ft",inches:"in"};let cc=!1,dc="single";const pc=[],hc=p.includeDefaultMeasurementsInViewer??r.uiOptions?.includeDefaultMeasurementsInViewer,uc=p.defaultMeasurementsVisibleOnLoad??r.uiOptions?.defaultMeasurementsVisibleOnLoad??!1,mc=hc??uc,gc=p.alwaysShowDefaultMeasurements??r.uiOptions?.alwaysShowDefaultMeasurements??!1;function fc(){for(let t=0;t<pc.length;t++)if(pc[t].locked)return!0;return!1}function yc(){return cc||gc&&fc()}if(mc&&Array.isArray(r.measurements)&&r.measurements.length>0)for(const Oc of r.measurements)Oc&&Oc.pointA&&Oc.pointB&&pc.push({from:new t.Vec3(Oc.pointA.x,Oc.pointA.y,Oc.pointA.z),to:new t.Vec3(Oc.pointB.x,Oc.pointB.y,Oc.pointB.z),locked:!0});let vc=null,bc=null;const xc=[],wc=[];let Sc=null;const _c=D.measureList||null;function Cc(){if(!_c)return;_c.innerHTML="";const t=document.createElement("div");t.className="storysplat-measure-list-header";const e=document.createElement("span");e.className="storysplat-measure-list-title",e.textContent="Measurements",t.appendChild(e);if(pc.some(t=>!t.locked)){const e=document.createElement("button");e.className="storysplat-measure-list-clear",e.textContent="Clear all",e.addEventListener("click",()=>{Ac(),Fc()}),t.appendChild(e)}if(_c.appendChild(t),0===pc.length){const t=document.createElement("div");t.className="storysplat-measure-list-empty",t.textContent="Click two points to measure",_c.appendChild(t)}else for(let t=0;t<pc.length;t++){const e=pc[t],n=document.createElement("div");n.className="storysplat-measure-list-item";const o=document.createElement("span");o.className="storysplat-measure-list-dot",n.appendChild(o);const i=document.createElement("span");i.style.flex="1";const s=e.from.distance(e.to);if(i.textContent=Mc(s),n.appendChild(i),e.locked){const t=document.createElement("span");t.className="storysplat-measure-list-lock",t.textContent="🔒",t.title="Default measurement",t.style.fontSize="12px",t.style.padding="0 4px",t.style.opacity="0.6",n.appendChild(t)}else{const e=document.createElement("button");e.className="storysplat-measure-list-clear",e.textContent="×",e.style.fontSize="14px",e.style.padding="0 4px",e.style.lineHeight="1";const o=t;e.addEventListener("click",()=>{pc.splice(o,1),Ic(),Fc(),Rc()}),n.appendChild(e)}_c.appendChild(n)}}function Mc(t){const e=t*sc*({meters:1,centimeters:100,feet:3.28084,inches:39.3701}[ac]||1),n=lc[ac]||"m";return`${e.toFixed(2)} ${n}`}const Ec=new t.Vec3;function Pc(t){he.camera.worldToScreen(t,Ec);const e=Ec;return e.z<0?null:{x:e.x,y:e.y}}function kc(t,n){const o=document.createElement("div");return o.className="storysplat-measure-point",o.style.left=`${t}px`,o.style.top=`${n}px`,e.appendChild(o),xc.push(o),o}function Tc(t,n,o){const i=document.createElement("div");return i.className="storysplat-measure-label",i.textContent=t,i.style.left=`${n}px`,i.style.top=`${o}px`,e.appendChild(i),wc.push(i),i}function Ac(){for(let t=pc.length-1;t>=0;t--)pc[t].locked||pc.splice(t,1);vc=null,bc=null,xc.forEach(t=>t.remove()),xc.length=0,wc.forEach(t=>t.remove()),wc.length=0,Sc&&(Sc.remove(),Sc=null),Cc(),Rc()}function Lc(){const t=e.getBoundingClientRect();ic.width=t.width,ic.height=t.height}function Rc(){Lc();const t=ic.getContext("2d");if(t&&(t.clearRect(0,0,ic.width,ic.height),yc())){t.strokeStyle=rc,t.lineWidth=2,t.setLineDash([]);for(const e of pc){if(!cc&&!e.locked)continue;const n=Pc(e.from),o=Pc(e.to);n&&o&&(t.beginPath(),t.moveTo(n.x,n.y),t.lineTo(o.x,o.y),t.stroke())}if(vc&&bc){const e=Pc(vc);e&&(t.strokeStyle=rc,t.globalAlpha=.6,t.setLineDash([6,4]),t.beginPath(),t.moveTo(e.x,e.y),t.lineTo(bc.x,bc.y),t.stroke(),t.setLineDash([]),t.globalAlpha=1)}}}function zc(){let t=0,e=0;for(let n=0;n<pc.length;n++){const o=pc[n];if(!cc&&!o.locked)continue;const i=Pc(o.from);i&&xc[t]?(xc[t].style.left=`${i.x}px`,xc[t].style.top=`${i.y}px`,xc[t].style.display=""):xc[t]&&(xc[t].style.display="none"),t++;const s=Pc(o.to);s&&xc[t]?(xc[t].style.left=`${s.x}px`,xc[t].style.top=`${s.y}px`,xc[t].style.display=""):xc[t]&&(xc[t].style.display="none"),t++,wc[e]&&(i&&s?(wc[e].style.left=(i.x+s.x)/2+"px",wc[e].style.top=(i.y+s.y)/2+"px",wc[e].style.display=""):wc[e].style.display="none"),e++}if(vc&&xc[t]){const e=Pc(vc);e?(xc[t].style.left=`${e.x}px`,xc[t].style.top=`${e.y}px`,xc[t].style.display=""):xc[t].style.display="none"}}function Dc(){cc=!0,Pr=!0,oc.classList.add("active"),Mr.style.cursor="none",Jn=new t.Color(1,.596,0),_c&&(_c.style.display="block"),Fc(),dc=Ft,Ft="none"}function Ic(){vc=null,bc=null,Sc&&(Sc.remove(),Sc=null),Fc()}function Fc(){xc.forEach(t=>t.remove()),xc.length=0,wc.forEach(t=>t.remove()),wc.length=0,Sc&&(Sc.remove(),Sc=null);for(let t=0;t<pc.length;t++){const e=pc[t];if(!cc&&!e.locked)continue;const n=Pc(e.from),o=kc(n?n.x:0,n?n.y:0);!n&&o&&(o.style.display="none");const i=Pc(e.to),s=kc(i?i.x:0,i?i.y:0);!i&&s&&(s.style.display="none");const a=Tc(Mc(e.from.distance(e.to)),n&&i?(n.x+i.x)/2:0,n&&i?(n.y+i.y)/2:0);n&&i||!a||(a.style.display="none")}if(vc){const t=Pc(vc);kc(t?t.x:0,t?t.y:0)}Cc()}Ar=()=>{xc.forEach(t=>t.remove()),xc.length=0,wc.forEach(t=>t.remove()),wc.length=0,Sc&&(Sc.remove(),Sc=null),_c&&_c.remove(),ic.remove(),oc.remove()},oc.addEventListener("click",()=>{cc?(cc=!1,Pr=!1,oc.classList.remove("active"),Mr.style.cursor="",Jn=null,vc=null,bc=null,Sc&&(Sc.remove(),Sc=null),gc&&fc()?Fc():(xc.forEach(t=>t.remove()),xc.length=0,wc.forEach(t=>t.remove()),wc.length=0),Rc(),_c&&(_c.style.display="none"),Ft=dc):Dc()});const Bc=t=>{"Escape"===t.key&&cc&&vc&&(Ic(),Rc())};document.addEventListener("keydown",Bc),Tr=Bc;const Vc=new t.Picker(U,1,1,!0);async function $c(t,e){try{const n=.25;Vc.resize(Math.floor(Mr.clientWidth*n),Math.floor(Mr.clientHeight*n));const o=U.scene.layers.getLayerByName("World");if(!o)return null;Vc.prepare(he.camera,U.scene,[o]);const i=Math.floor(t*n),s=Math.floor(e*n),a=await Vc.getWorldPointAsync(i,s);if(a&&isFinite(a.x)&&isFinite(a.y)&&isFinite(a.z)){const t=he.getPosition().distance(a);if(t>.1&&t<1e3)return a}}catch(t){}return null}kr=Vc,Mr.addEventListener("click",async t=>{if(!cc)return;if(Dr)return;const e=Mr.getBoundingClientRect(),n=t.clientX-e.left,o=t.clientY-e.top,i=await $c(n,o);i&&(t.stopPropagation(),vc?(pc.push({from:vc.clone(),to:i.clone(),locked:!1}),vc=i.clone()):vc=i.clone(),Fc(),Rc())},!0);let Uc=0;Mr.addEventListener("pointermove",t=>{if(!cc||!vc)return;const n=Mr.getBoundingClientRect();bc={x:t.clientX-n.left,y:t.clientY-n.top};const o=performance.now();if(o-Uc>80){Uc=o;const t=bc.x,n=bc.y;$c(t,n).then(o=>{if(cc&&vc&&o){const i=Mc(vc.distance(o));Sc||(Sc=document.createElement("div"),Sc.className="storysplat-measure-label",Sc.style.opacity="0.8",e.appendChild(Sc)),Sc.textContent=i;const s=Pc(vc);s&&(Sc.style.left=(s.x+t)/2+"px",Sc.style.top=(s.y+n)/2+"px")}})}Rc()}),U.on("update",()=>{yc()&&(pc.length>0||vc)&&(zc(),Rc())});(p.defaultMeasurementsVisibleOnLoad??r.uiOptions?.defaultMeasurementsVisibleOnLoad??!1)&&pc.some(t=>t.locked)?Dc():gc&&pc.some(t=>t.locked)&&(Fc(),Rc())}if(r.skins&&r.skins.length>0&&D.skinsButton&&D.skinsDropdown&&D.skinOverlay&&D.skinOverlayWrapper&&D.skinOpacitySlider&&D.skinExitButton){const Nc=D.skinsButton,Wc=D.skinsDropdown,Hc=D.skinOverlayWrapper,Gc=D.skinOverlay,jc=D.skinOpacitySlider,Xc=D.skinExitButton,qc=r.skins;let Yc=null,Zc=null,Kc=0,Qc=null;const Jc=()=>{Wc.querySelectorAll(".storysplat-skin-item").forEach(t=>{const e=t.getAttribute("data-skin-id");t.classList.toggle("active",!!Yc&&e===Yc)})},td=t=>{if(Yc){if(Yc=null,Qc&&(Gc.removeEventListener("load",Qc),Qc=null),Wc.querySelectorAll(".storysplat-skin-item.loading").forEach(t=>{t.classList.remove("loading")}),Hc.classList.remove("active"),Wc.classList.contains("open")||Nc.classList.remove("active"),Xc.style.display="none",Jc(),"tour"===Zc)try{ss()}catch(t){}Zc=null}},ed=e=>{const n=qc.find(t=>t.id===e);if(!n)return;if("tour"===Sn){null===Zc&&(Zc=Sn);try{Vo("explore")}catch(t){}}const o=new t.Vec3(n.cameraPosition.x,n.cameraPosition.y,-n.cameraPosition.z),i=new t.Vec3(n.cameraPivot.x,n.cameraPivot.y,-n.cameraPivot.z);if(n.cameraRotation){const e=n.cameraRotation,s=new t.Quat(-e.x,-e.y,e.z,e.w);"function"==typeof un.syncFromPose&&un.syncFromPose(o,s,i),he.setPosition(o),he.setRotation(s)}else he.setPosition(o),he.lookAt(i),un.syncFromCamera(i);Yc=e;const s=n.canvasWidth&&n.canvasHeight?n.canvasWidth/n.canvasHeight:void 0;Hc.style.aspectRatio=s?String(s):"",Gc.style.opacity=String(parseInt(jc.value,10)/100),Nc.classList.add("active"),Xc.style.display="block",Jc(),Kc=performance.now()+400,Hc.classList.remove("active"),Qc&&(Gc.removeEventListener("load",Qc),Qc=null);const a=Wc.querySelector(`.storysplat-skin-item[data-skin-id="${CSS.escape(e)}"]`);Wc.querySelectorAll(".storysplat-skin-item.loading").forEach(t=>{t!==a&&t.classList.remove("loading")}),a&&a.classList.add("loading");const r=()=>{a&&a.classList.remove("loading"),Yc===e&&requestAnimationFrame(()=>{Yc===e&&Hc.classList.add("active")})},l=Gc.src===n.imageUrl;l&&Gc.complete&&Gc.naturalWidth>0?r():(Qc=()=>{Gc.removeEventListener("load",Qc),Qc=null,r()},Gc.addEventListener("load",Qc),l||(Gc.src=n.imageUrl))},nd=()=>{const t=Wc.classList.contains("open")||!!Yc;Nc.classList.toggle("active",t)};Nc.addEventListener("click",t=>{t.stopPropagation();const e=!Wc.classList.contains("open");Wc.classList.toggle("open",e),nd()});const od=t=>{Wc.contains(t.target)||Nc.contains(t.target)||(Wc.classList.remove("open"),nd())};document.addEventListener("click",od),Lr=od,Wc.querySelectorAll(".storysplat-skin-item").forEach(t=>{t.addEventListener("click",e=>{e.stopPropagation();const n=t.getAttribute("data-skin-id");n&&ed(n)})}),jc.addEventListener("input",()=>{Gc.style.opacity=String(parseInt(jc.value,10)/100)}),Xc.addEventListener("click",t=>{t.stopPropagation(),td()});const id=new Set(["w","a","s","d","q","e","W","A","S","D","Q","E","ArrowUp","ArrowDown","ArrowLeft","ArrowRight"]),sd=t=>{if(Yc&&!(performance.now()<Kc)){if("keydown"===t.type){const e=t.key;if(!id.has(e))return}td()}};I.addEventListener("pointerdown",sd),I.addEventListener("wheel",sd,{passive:!0}),window.addEventListener("keydown",sd),Rr=sd,zr=I}const rl={app:U,canvas:I,capturePhoto:_t,shareCurrentView:Tt,goToWaypoint:t=>{if("explore"===Sn)throw new Error('[StorySplat Viewer] goToWaypoint() requires tour mode. Call setCameraMode("tour") first.');ds(t)},nextWaypoint:()=>{if("explore"===Sn)throw new Error('[StorySplat Viewer] nextWaypoint() requires tour mode. Call setCameraMode("tour") first.');ps()},prevWaypoint:()=>{if("explore"===Sn)throw new Error('[StorySplat Viewer] prevWaypoint() requires tour mode. Call setCameraMode("tour") first.');hs()},getCurrentWaypointIndex:()=>et,getWaypointCount:()=>r.waypoints?.length||0,setPosition:(t,e,n)=>{if("explore"!==Sn)throw new Error('[StorySplat Viewer] setPosition() requires explore mode. Call setCameraMode("explore") first.');if(nt)throw new Error("[StorySplat Viewer] setPosition() cannot be used during autoplay. Call pause() or stop() first.");un.disable(),he.setPosition(t,e,n),un.syncFromCamera(),un.enable(),requestAnimationFrame(()=>{he.setPosition(t,e,n),un.syncFromCamera()})},setRotation:(t,e,n)=>{if("explore"!==Sn)throw new Error('[StorySplat Viewer] setRotation() requires explore mode. Call setCameraMode("explore") first.');if(nt)throw new Error("[StorySplat Viewer] setRotation() cannot be used during autoplay. Call pause() or stop() first.");un.disable(),he.setEulerAngles(t,e,n),un.syncFromCamera(),un.enable(),requestAnimationFrame(()=>{he.setEulerAngles(t,e,n),un.syncFromCamera()})},getPosition:()=>{const t=he.getPosition();return{x:t.x,y:t.y,z:t.z}},getRotation:()=>{const t=he.getEulerAngles();return{x:t.x,y:t.y,z:t.z}},play:()=>{if(!At&&"explore"===Sn)throw new Error('[StorySplat Viewer] play() requires tour mode. Call setCameraMode("tour") first.');ws()},pause:()=>{if(!At&&"explore"===Sn)throw new Error('[StorySplat Viewer] pause() requires tour mode. Call setCameraMode("tour") first.');Ss()},stop:()=>{if(!At&&"explore"===Sn)throw new Error('[StorySplat Viewer] stop() requires tour mode. Call setCameraMode("tour") first.');!function(){if(At)return At.stop(),void i.emit("playbackStop");Ss(),rs(0)}()},isPlaying:()=>nt,isFrameSequencePlaying:()=>At?.getIsPlaying()??!1,setFrame:t=>At?.setFrame(t),getCurrentFrame:()=>At?.getCurrentFrame()??0,getTotalFrames:()=>At?.getTotalFrames()??0,setFps:t=>At?.setFps(t),getFps:()=>At?.getFps()??24,getFrameProgress:()=>At?.getProgress()??0,setFrameProgress:t=>At?.setProgress(t),goToOriginalSplat:Ei,goToSplat:async t=>{const e=xi();if(t===e)return void Ei();const n=zt.find(e=>e.url===t);n||Ut.has(t)?n&&!Ut.has(t)&&await yi(t,n):await yi(t);const o=Bt||e;if(o===e&&ot)pi(ot);else if(o){const t=Ut.get(o);t&&(t.enabled=!1)}if(!ui(t))throw new Error(`[StorySplat Viewer] Failed to switch to splat: ${t}`);n&&vi(n.defaultExploreMode,!1),i.emit("splatChange",{url:t,isOriginal:!1})},getCurrentSplatUrl:function(){return Bt||xi()},isShowingOriginalSplat:function(){return Bt===xi()||null===Bt},getAdditionalSplats:()=>zt.map(t=>({url:t.url,name:t.name,waypointIndex:t.waypointIndex,percentage:t.percentage})),isCompareMode:()=>Gt,getComparePosition:()=>Xt,setComparePosition:t=>{_i(t),jt&&jt.setPosition(t)},destroy:()=>{vt=!0,Ss(),Co(),Mi(),il&&(il.destroy(),il=null),At&&(At.destroy(),At=null),Gs&&(Gs.pause(),Gs.src="",Gs=null),window.removeEventListener("resize",Zr),e.removeAttribute("data-storysplat-editor"),e.querySelector(".storysplat-error-popup")?.remove(),e.querySelector(".storysplat-photo-share-backdrop")?.remove(),e.querySelector(".storysplat-share-toast")?.remove(),D.preloader&&D.preloader.remove(),D.scrollControls&&D.scrollControls.remove(),D.fullscreenButton&&D.fullscreenButton.remove(),D.helpButton&&D.helpButton.remove(),D.helpPanel&&D.helpPanel.remove(),D.waypointInfo&&D.waypointInfo.remove(),D.watermark&&D.watermark.remove(),D.wasdHint&&D.wasdHint.remove(),D.orbitHint&&D.orbitHint.remove(),D.doubleTapHint&&D.doubleTapHint.remove(),D.lookZone&&D.lookZone.remove(),D.joystick&&D.joystick.remove(),D.muteButton&&D.muteButton.remove(),D.relightingButton&&D.relightingButton.remove(),D.waypointListContainer&&D.waypointListContainer.remove(),D.sceneMenuContainer&&D.sceneMenuContainer.remove(),D.shareButton&&D.shareButton.remove(),D.fpsCounter&&D.fpsCounter.remove(),D.hotspotPopup&&D.hotspotPopup.remove(),D.portalPopup&&D.portalPopup.remove(),D.vrButton&&D.vrButton.remove(),D.arButton&&D.arButton.remove(),D.modeContainer&&D.modeContainer.remove(),D.exploreControls&&D.exploreControls.remove();const t=document.getElementById("storysplat-viewer-styles");t&&t.remove(),e.classList.remove("storysplat-viewer-container"),pr.forEach(t=>t.destroy()),pr.length=0,ga(),un.setCollisionEntities([]),vn&&vn.destroy();const n=U.__customScriptSystem;n&&n.dispose();const o=U.__htmlMeshManager;if(o&&o.destroy(),Ln.destroy(),zn.destroy(),An.destroy(),Mr.removeEventListener("mousemove",Er),Mo&&(Mo.destroy(),Mo=null),Eo&&(Eo.destroy(),Eo=null),Mn.destroy(),kr&&(kr.destroy(),kr=null),Tr&&(document.removeEventListener("keydown",Tr),Tr=null),Lr&&(document.removeEventListener("click",Lr),Lr=null),Rr&&(zr&&(zr.removeEventListener("pointerdown",Rr),zr.removeEventListener("wheel",Rr)),window.removeEventListener("keydown",Rr),Rr=null,zr=null),Ho&&(clearTimeout(Ho),Ho=null),Oo){const t=Oo._escHandler;t&&document.removeEventListener("keydown",t),Oo.remove(),Oo=null}if(D._cleanup&&(D._cleanup(),D._cleanup=void 0),Ar&&(Ar(),Ar=null),ye&&he.camera){try{he.camera.postEffects.removeEffect(ye)}catch(t){}ye=null}We&&(We.destroy(),We=null),U.destroy(),I.remove()},resize:Zr,navigateToScene:async t=>{const e={targetSceneId:t,activationMode:"click"};await _r(e)},setCameraMode:t=>Vo(t),getCameraMode:()=>Sn,resumeTourAtCurrentCamera:()=>ss(),setExploreMode:t=>{if("explore"!==Sn&&"walk"!==Sn)throw new Error('[StorySplat Viewer] setExploreMode() requires explore mode. Call setCameraMode("explore") first.');"walk"===t?("walk"!==Sn&&Vo("walk"),Fo("walk")):Bo(t)},setExploreModeSettings:t=>(t=>{void 0!==t.enabledExploreModes&&(C=_(t.enabledExploreModes),r.enabledExploreModes=C,un.enableOrbit=E("orbit"),un.enableFly=E("fly")),void 0===t.initialSplatExploreMode&&E(It)||(It=L(t.initialSplatExploreMode??It),r.initialSplatExploreMode=It,zo=It),"walk"===Ro||E(Ro)||(zo=L(It));const e=zo??L(It);!t.applyCurrent||"explore"!==Sn&&"walk"!==Sn?"explore"!==Sn&&"walk"!==Sn&&(Fo(e),zo=null):(Bo(e),zo=null)})(t),setProgress:(t,e)=>{if("explore"===Sn)throw new Error('[StorySplat Viewer] setProgress() requires tour mode. Call setCameraMode("tour") first.');rs(t,e)},getProgress:()=>ki,muteAll:()=>cr(),unmuteAll:()=>dr(),isMuted:()=>Ns,getHotspots:()=>Cs.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=Cs.find(e=>e.hotspotData?.id===t);if(!n)throw new Error(`[StorySplat Viewer] Hotspot not found: ${t}`);const o=n.hotspotData;o&&F(e,o,u)},closeHotspot:()=>{const t=e.querySelector(".storysplat-hotspot-popup"),n=e.querySelector(".storysplat-hotspot-overlay");t&&t.classList.remove("visible"),n&&n.classList.remove("visible"),Ks(),Qs(e)},setPanoramaMode:t=>$o(t),isPanoramaMode:()=>ne,setButtonLabels:t=>{u={...u,...t};const n=t=>x(u,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 l=e.querySelector(".storysplat-btn-next");l&&(l.textContent=n("next"));const c=e.querySelector(".storysplat-waypoint-list-toggle");if(c){const t=c.querySelector("svg");c.textContent="",c.append(n("waypoints")),t&&c.appendChild(t),c.setAttribute("aria-label",n("waypoints"))}const d=e.querySelector(".storysplat-vr-btn");d&&!d.classList.contains("active")&&(d.textContent=n("vr"),d.setAttribute("aria-label",n("vr")));const p=e.querySelector(".storysplat-ar-btn");p&&!p.classList.contains("active")&&(p.textContent=n("ar"),p.setAttribute("aria-label",n("ar")));const h=e.querySelector(".storysplat-fullscreen-btn");h&&h.setAttribute("aria-label",n("fullscreen"));const m=e.querySelector(".storysplat-share-btn");m&&(m.setAttribute("aria-label",n("share")),m.setAttribute("title",n("share")));const g=e.querySelector(".storysplat-hotspot-popup-close");g&&(g.textContent=n("close"));const f=e.querySelector(".storysplat-portal-popup-confirm");f&&(f.textContent=n("yes"));const y=e.querySelector(".storysplat-portal-popup-cancel");y&&(y.textContent=n("cancel"));const v=e.querySelector(".storysplat-help-panel");if(v){v.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 v.appendChild(o),o},e=()=>v.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 b=e.querySelector(".storysplat-help-btn");b&&b.setAttribute("title",n("helpTitle"))},on:(t,e)=>i.on(t,e),off:(t,e)=>i.off(t,e),onInternal:(t,e)=>i.onInternal(t,e)};if(a){const ad=rl;ad.setCameraMode=t=>Vo(t),ad.getCameraMode=()=>Sn,ad.getCameraControls=()=>un,ad.setOrbitCameraSettings=t=>fn(t),ad.suppressInput=()=>{Ki=!0,Zi=!1,un.disable()},ad.resumeInput=()=>{Ki=!1,"explore"===Sn&&un.enable()},ad.setReticleEnabled=t=>{Zn=t,t||(An.enabled=!1,$n=!1,Qn=0)},ad.setReticleColor=e=>{Jn=e?new t.Color(e.r,e.g,e.b):null},ad.setEditingHotspotId=t=>{l=t},ad.setRefocusTapMode=t=>{Ft=t,ro?.setTapMode(t)},ad.getApp=()=>U,ad.getSplatEntity=()=>ot,ad.getSwapSplatEntity=t=>Ut.get(t)??null,ad.ensureSwapSplatLoaded=async(t,e)=>Ut.has(t)?Ut.get(t):(await yi(t,e),Ut.get(t)??null),ad.getAllHotspotEntities=()=>Kr,ad.getAllLightEntities=()=>Qr,ad.getAllPortalEntities=()=>el,ad.getAllMirrorPlaneEntities=()=>ol,ad.getAllCustomMeshEntities=()=>tl,ad.getAllParticleEntities=()=>Jr,ad.getAllCollisionMeshEntities=()=>nl,ad.getAllHtmlMeshEntities=()=>{const t=U.__htmlMeshManager,e=new Map;return t&&t.getAllMeshes().forEach((t,n)=>{e.set(n,t.entity)}),e},ad.getCamera=()=>he,ad.setProgress=(t,e)=>rs(t,e),ad.getProgress=()=>ki,ad.setTransitionSpeed=t=>{ls=500*(t||1)},ad.updateAdditionalSplats=t=>{zt=t,Ot=null},ad.setSwapTransitionEnabled=t=>{li=t},ad.setPrimarySplatUrl=t=>{Vt=t},ad.enableCompareMode=async(t,e,n)=>{Mi(),e&&Object.assign(te,e),Gt=!0,await Ci(t,n)},ad.disableCompareMode=()=>{Mi(),Gt=!1},ad.updateSwapSplatTransform=(t,e)=>{const n=Ut.get(t);if(n){if(e.scale){const t=r.invertXScale||!1,o=r.invertYScale||!1,i=e.scale;n.setLocalScale(t?-i.x:i.x,o?i.y:-i.y,t!==o?i.z:-i.z)}if(e.position){const t=e.position;n.setPosition(t[0],t[1],-t[2])}if(e.rotation){const t=e.rotation;n.setEulerAngles(t[0]*(180/Math.PI),t[1]*(180/Math.PI),-t[2]*(180/Math.PI))}}},ad.setCompareSettings=t=>{if(Object.assign(te,t),jt){if(t.orientation){jt.setOrientation(t.orientation);const e="horizontal"===t.orientation?1:0;Yt&&Yt.updateOrientation(e),Zt&&Zt.updateOrientation(e)}if(void 0===t.beforeLabel&&void 0===t.afterLabel||jt.setLabels(t.beforeLabel??te.beforeLabel??"Before",t.afterLabel??te.afterLabel??"After"),void 0===t.lineColor&&void 0===t.lineOpacity||jt.updateLineStyle(t.lineColor,t.lineOpacity),void 0===t.handleColor&&void 0===t.handleOpacity||jt.updateHandleColor(t.handleColor??te.handleColor??"#ffffff",t.handleOpacity??te.handleOpacity),void 0!==t.handleStyle&&jt.updateHandleStyle(t.handleStyle),void 0!==t.initialPosition){const e=t.initialPosition/100;jt.setPosition(e),_i(e)}if(void 0!==t.showLabels){const e=jt.overlay?.querySelectorAll(".ss-compare-label");e&&e.forEach(e=>{e.style.display=t.showLabels?"":"none"})}void 0!==t.enableKeyboard&&jt.setEnableKeyboard(t.enableKeyboard),void 0!==t.enableSnap&&jt.setEnableSnap(t.enableSnap)}},ad.addHotspot=t=>{const e=t.id||`hotspot-${Date.now()}`,n=fr(t,Cs.length);return Kr.set(e,n),e},ad.removeHotspot=t=>{const e=Kr.get(t);if(e){const n=e.leaderLineEntity;n&&(n._leaderMat?.destroy(),n.destroy()),e.destroy(),Kr.delete(t);const o=Cs.indexOf(e);o>=0&&Cs.splice(o,1)}},ad.updateHotspot=(t,e)=>{const n=Kr.get(t);if(!n)return;if(e.position&&n.setPosition(e.position.x,e.position.y,-e.position.z),void 0!==e.opacity){const t=n.hotspotMaterial||n.render?.meshInstances?.[0]?.material;t&&"opacity"in t&&(t.opacity=e.opacity,t.update())}const o={...n.hotspotData||{},...e};if(n.hotspotData=o,void 0!==e.position||void 0!==e.leaderLineEnabled||void 0!==e.leaderLineTarget||void 0!==e.leaderLineColor||void 0!==e.leaderLineWidth||void 0!==e.leaderLineOpacity){const t=n.leaderLineEntity;t&&(t._leaderMat?.destroy(),t.destroy());const e=gr(o,n.getPosition().clone());e?(nn.addChild(e),n.leaderLineEntity=e):n.leaderLineEntity=void 0}},ad.addLight=e=>{const n=e.id||e.name||`light-${Date.now()}`;let o=null;switch(e.type){case"point":default:o=Pa(e);break;case"directional":o=ka(e);break;case"hemispheric":o=Ta(e);break;case"ambient":Aa(e);break;case"spot":o=La(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,nn.addChild(o),Ca.push(o),Qr.set(n,o),Ma.set(n,o)}return n},ad.removeLight=t=>{const e=Qr.get(t);if(e){e.destroy(),Qr.delete(t),Ma.delete(t);const n=Ca.indexOf(e);n>=0&&Ca.splice(n,1)}},ad.updateLight=(t,e)=>{const n=Qr.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=Ea(e.color))))},ad.addCustomMesh=t=>{const e=t.id||t.name||`mesh-${Date.now()}`,n=la(t,sa.size);return n&&tl.set(e,n),e},ad.updateCustomMesh=(t,e)=>{const n=tl.get(t);n&&(e.position&&n.setPosition(e.position.x??0,e.position.y??0,-(e.position.z??0)),e.rotation&&n.setRotation(ur(e.rotation.x??0,e.rotation.y??0,e.rotation.z??0)),e.scale&&n.setLocalScale(e.scale.x??1,e.scale.y??1,e.scale.z??1))},ad.removeCustomMesh=t=>{const e=tl.get(t);e&&(sa.forEach((t,n)=>{t.entity===e&&(t.modelAsset&&(U.assets.remove(t.modelAsset),t.modelAsset.unload()),sa.delete(n))}),e.destroy(),tl.delete(t))},ad.addParticleSystem=e=>{const n=e.id||e.name||`particle-${Date.now()}`,o=ia(e),i=e.particleTexture||"flare";if("custom"===i&&e.customTextureUrl){oa(`custom_${e.customTextureUrl}`,e.customTextureUrl).then(t=>{o.particlesystem&&(o.particlesystem.colorMap=t)}).catch(t=>{console.warn("[Editor] Failed to load custom particle texture:",t)})}else{const t=ea.has(i)?i:"flare";o.particlesystem&&(o.particlesystem.colorMap=na(t))}const s=new t.Entity("particle-picker");s.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1}),s.setLocalScale(.15,.15,.15);const a=new t.StandardMaterial;a.diffuse=new t.Color(1,.5,0,1),a.opacity=.3,a.blendType=t.BLEND_NORMAL,a.depthWrite=!1,a.update(),s.render.meshInstances[0].material=a,o.addChild(s),nn.addChild(o);const r=n.replace(/[^a-zA-Z0-9]/g,"_");return Js.set(r,o),Jr.set(n,o),n},ad.removeParticleSystem=t=>{const e=t.replace(/[^a-zA-Z0-9]/g,"_"),n=Jr.get(t)||Js.get(e);n&&(n.destroy(),Jr.delete(t),Js.delete(e))},ad.addPortal=t=>{const e=t.id||`portal-${Date.now()}`,n=xr(t,Is.length);return el.set(e,n),e},ad.removePortal=t=>{const e=el.get(t);if(e){const n=e.videoElement;n&&(n.pause(),n.src="");const o=e.leaderLineEntity;o&&(o._leaderMat?.destroy(),o.destroy()),e.destroy(),el.delete(t);const i=Is.indexOf(e);i>=0&&Is.splice(i,1)}},ad.updatePortal=(t,e)=>{const n=el.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)}if(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)),void 0!==e.opacity){const t=n.portalMaterial||n.render?.meshInstances?.[0]?.material;t&&"opacity"in t&&(t.opacity=e.opacity,t.update())}const o={...n.portalData||{},...e};if(n.portalData=o,void 0!==e.position||void 0!==e.leaderLineEnabled||void 0!==e.leaderLineTarget||void 0!==e.leaderLineColor||void 0!==e.leaderLineWidth||void 0!==e.leaderLineOpacity){const t=n.leaderLineEntity;t&&(t._leaderMat?.destroy(),t.destroy());const e=gr(o,n.getPosition().clone());e?(nn.addChild(e),n.leaderLineEntity=e):n.leaderLineEntity=void 0}},ad.addMirrorPlane=t=>{const e=t.id||`mirror-${Date.now()}`,n=zs(t,Ms.length);return ol.set(e,n),e},ad.removeMirrorPlane=t=>{Ds(t)},ad.updateMirrorPlane=(t,e)=>{const n=ol.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&&n.setLocalScale(e.scale.x??1,e.scale.y??1,e.scale.z??1);const o=n._mirrorMaterial;if(o&&(void 0!==e.intensity&&o.setParameter("uIntensity",e.intensity),e.tint)){const t=parseInt(e.tint.slice(1,3),16)/255,n=parseInt(e.tint.slice(3,5),16)/255,i=parseInt(e.tint.slice(5,7),16)/255;o.setParameter("uTint",[t,n,i])}n._mirrorData={...n._mirrorData,...e}};let rd=null;return ad.showProximityRadius=(e,n,o)=>{if(rd){const t=rd._proximityUpdateFn;t&&U.off("update",t),rd.destroy(),rd=null}const i=new t.Entity("proximity-radius-preview"),s=e.getPosition();i.setPosition(s.x,s.y,s.z);const a=(e,n,o,i)=>{const s=new t.Entity(e);s.addComponent("render",{type:"cylinder",castShadows:!1,receiveShadows:!1}),s.setLocalScale(2*n,.05,2*n),s.setLocalPosition(0,0,0);const a=new t.StandardMaterial;return a.diffuse=o,a.emissive=new t.Color(.8*o.r,.8*o.g,.8*o.b),a.opacity=i,a.blendType=i<1?t.BLEND_NORMAL:t.BLEND_NONE,a.depthWrite=i>=1,a.cull=t.CULLFACE_NONE,a.useLighting=!1,a.update(),s.render.meshInstances[0].material=a,s},r=(e,n,o,i)=>{const s=new t.Entity(e);s.addComponent("render",{type:"cylinder",castShadows:!1,receiveShadows:!1}),s.setLocalScale(2*n,i,2*n),s.setLocalPosition(0,0,0);const a=new t.StandardMaterial;return a.diffuse=o,a.emissive=new t.Color(.9*o.r,.9*o.g,.9*o.b),a.opacity=1,a.blendType=t.BLEND_NONE,a.depthWrite=!0,a.cull=t.CULLFACE_NONE,a.useLighting=!1,a.update(),s.render.meshInstances[0].material=a,s};if(n>0){const e=a("trigger-disc",n,new t.Color(.15,.5,1),.5),o=r("trigger-ring",n,new t.Color(.3,.7,1),.15);i.addChild(e),i.addChild(o)}if(o){const e=o.maxDistance,n=o.refDistance;if(e&&e>0){const n=a("audio-max-disc",e,new t.Color(1,.6,.15),.12),o=r("audio-max-ring",e,new t.Color(1,.7,.2),.1);i.addChild(n),i.addChild(o)}if(n&&n>0){const e=a("audio-ref-disc",n,new t.Color(.2,.8,.3),.3),o=r("audio-ref-ring",n,new t.Color(.3,.9,.4),.12);i.addChild(e),i.addChild(o)}if(n&&e&&e>n&&o.rolloffFactor){const s=o.rolloffFactor,a=o.distanceModel||"linear",l=[.75,.5,.25];for(const o of l){let l=null;if("linear"===a?l=n+(1-o)*(e-n)/s:"inverse"===a?l=n+(n/o-n)/s:"exponential"===a&&(l=n*Math.pow(o,-1/s)),null!==l&&l>n&&l<e){const e=1-o,n=new t.Color(.6,.6,.6),s=r(`rolloff-${Math.round(100*o)}`,l,n,.04),a=s.render.meshInstances[0].material;a.opacity=.3+.4*e,a.blendType=t.BLEND_NORMAL,a.depthWrite=!1,a.update(),i.addChild(s)}}}}const l=[`trigger=${n}`];o?.maxDistance&&l.push(`max=${o.maxDistance}`),o?.refDistance&&l.push(`ref=${o.refDistance}`),o?.rolloffFactor&&l.push(`rolloff=${o.rolloffFactor}`),console.log(`[Editor] Proximity radius: entity=${e.name}, ${l.join(", ")}`);const c=e.name,d=()=>{let t=e.parent?e:null;if(!t&&c&&(t=U.root.findByName(c)),t){const e=t.getPosition();i.setPosition(e.x,e.y,e.z)}};U.on("update",d),i._proximityUpdateFn=d,U.root.addChild(i),rd=i},ad.hideProximityRadius=()=>{if(rd){const t=rd._proximityUpdateFn;t&&U.off("update",t),rd.destroy(),rd=null}},ad.addHtmlMesh=t=>{const e=t.id||`html-mesh-${Date.now()}`;let n=U.__htmlMeshManager;return n||(n=Qt(U,[]),U.__htmlMeshManager=n),n.createMesh({...t,id:e,html:t.html||t.htmlContent||""}),e},ad.removeHtmlMesh=t=>{const e=U.__htmlMeshManager;e&&e.destroyMesh(t)},ad.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,l=Array.isArray(s)?s[1]:s.y,c=Array.isArray(s)?s[2]:s.z;if(o.setPosition(a??0,l??0,-(c??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,b=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(b,e))}else o.setRotation(b)}const d=e.scaling,p=(d?Array.isArray(d)?d[0]:d.x:1)??1,h=(d?Array.isArray(d)?d[1]:d.y:1)??1,u=(d?Array.isArray(d)?d[2]:d.z:1)??1;if("plane"===i){const t=.01;o.setLocalScale(3*p,u*t,3*h)}else"cube"===i||"sphere"===i?o.setLocalScale(3*p,3*h,3*u):"floor"===i?o.setLocalScale(100*p,1*h,100*u):o.setLocalScale(p,h,u);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})}if(o.addComponent("collision",{type:"sphere"===i?"sphere":"box"}),o.enabled=!1!==e.visible,o._collisionMeshId=n,o._collisionMeshType=i,nn.addChild(o),nl.set(n,o),!vn){const t=null!=r.walkSpeed?r.walkSpeed:sn;vn=new at(he,U,{moveSpeed:t,sprintMultiplier:2,lookSensitivity:1/(r.cameraRotationSensitivity||4e3)*10,playerHeight:r.playerHeight||1.6,gravity:20,jumpVelocity:8,collisionRadius:.3,stepHeight:.3}),w.includes("walk")||w.push("walk")}return vn.addCollisionEntity(o),un.setCollisionEntities(vn.collisionMeshEntities),n},ad.removeCollisionMesh=t=>{const e=nl.get(t);e&&(vn&&(vn.removeCollisionEntity(e),un.setCollisionEntities(vn.collisionMeshEntities)),e.destroy()),nl.delete(t)},ad.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||10,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&&!1!==e.enabled){const o=new t.Asset(`audio-emitter-${n}`,"audio",{url:e.url});o.on("load",()=>{if(vt)return;const t=i.sound?.slot(n);t&&(t.asset=o.id);const s=rr.get(n);if(s){s.assetReady=!0,t&&Hs(t,t=>{s.playing=t});"autoplay"===(e.triggerMode||(!1!==e.autoplay?"autoplay":"proximity"))&&t&&(t.play(),s.playing=!0)}}),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,nn.addChild(i),rr.set(n,{entity:i,config:e,slotId:n,playing:!1,assetReady:!1}),n},ad.removeAudioEmitter=t=>{const e=rr.get(t);if(e){const n=e.entity.sound?.slot(e.slotId);n&&n.stop(),e.entity.destroy(),rr.delete(t)}},ad.updateAudioEmitter=(t,e)=>{const n=rr.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})},ad.setSplatRelighting=t=>{if(r.splatRelighting={...r.splatRelighting,...t},!1!==t.enabled){if(!ut&&Ca.length>0&&ot&&za(),ut){if(void 0!==t.ambientColor||void 0!==t.ambientIntensity){const e=t.ambientColor??r.splatRelighting?.ambientColor??"#ffffff",n=t.ambientIntensity??r.splatRelighting?.ambientIntensity??0;for(const t of mt)t.setAmbientColor(e),t.ambientR*=n,t.ambientG*=n,t.ambientB*=n}if(!0===t.enabled&&!gt){for(const t of mt)t.enabled=!0,t.setRelightFade(1);gt=!0}}void 0===t.shadowsEnabled&&void 0===t.shadowIntensity&&void 0===t.shadowGroundY&&void 0===t.shadowPlaneScale||Oa()}else{for(const t of mt)t.enabled=!1;gt=!1}},ad.setRelightFade=t=>{for(const e of mt)e.setRelightFade(t)},ad.setNarrationTrack=t=>{if(t)qs(t);else{if(Gs){Gs.pause(),Gs.src="";const t=Os.indexOf(Gs);t>=0&&Os.splice(t,1),Ws.delete(Gs),Gs=null}js=[],Xs=!1}},ad.setSkybox=(t,e)=>{_a(t,e)},ad.setSkyboxRotation=e=>{const n=new t.Quat;n.setFromEulerAngles(0,-e*(180/Math.PI),0),U.scene.skyboxRotation=n,fa&&fa.setEulerAngles(0,-e*(180/Math.PI),0),r.skybox&&(r.skybox.rotation=e),r.skyboxRotation=e},ad.setBackgroundColor=e=>{const n=new t.Color(e.r/255,e.g/255,e.b/255);he.camera&&(he.camera.clearColor=n)},ad.setFOV=t=>{he.camera&&(he.camera.fov=t)},ad.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),ot&&(console.log("[SPLAT] loadSplatByUrl: Destroying existing splat entity"),ot.destroy(),ot=null);for(const e of o)try{const n=decodeURIComponent(e.toLowerCase().split(/[?#]/)[0]).split("/").pop()||"";if(n.endsWith(".glb")||n.endsWith(".gltf")){console.log("[SPLAT] loadSplatByUrl: Detected mesh format, loading as container:",e);const t=sa.get("__primary-mesh__");return t&&(t.entity.destroy(),t.modelAsset&&U.assets.remove(t.modelAsset),sa.delete("__primary-mesh__")),void la({id:"__primary-mesh__",name:"Primary Model",modelUrl:e,position:{x:0,y:0,z:0}},0)}console.log("[SPLAT] loadSplatByUrl: Trying URL:",e);const o=e.includes("lod-meta.json")?await gi(e):null,s=new t.Asset("splat-"+Date.now(),"gsplat",{url:e});return await new Promise((t,n)=>{s.ready(()=>{if(vt)t();else try{if(it=o,ot=Ko(s,e,o),vn){vn.setSplatEntity(ot);const t=vn.voxelCollisionInstance;t&&un.setVoxelCollision(t,ot)}console.log("[SPLAT] loadSplatByUrl: New splat entity added to scene"),t()}catch(t){console.error("[SPLAT] loadSplatByUrl: Error during gsplat setup:",t),n(t)}}),s.on("error",t=>{console.error("[SPLAT] loadSplatByUrl: Asset load error:",{url:e,error:t}),n(t)}),Qo(s,e)}),Vt=e,void i.emit("loaded",{bandwidthUsed:0,isStorySplatHosted:Zo(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),ad.addWaypoint=t=>{r.waypoints||(r.waypoints=[]),r.waypoints.push(t)},ad.removeWaypoint=t=>{r.waypoints&&t>=0&&t<r.waypoints.length&&r.waypoints.splice(t,1)},ad.updateWaypoint=(t,e)=>{r.waypoints&&t>=0&&t<r.waypoints.length&&Object.assign(r.waypoints[t],e)},ad.rebuildTourPath=e=>{if(e&&(r.waypoints=e),ts.length=0,es.length=0,ns.length=0,r.waypoints&&r.waypoints.length>0&&r.waypoints.forEach(e=>{const n=e.position?new t.Vec3(e.position.x,e.position.y,-e.position.z):new t.Vec3(0,r.playerHeight||1.6,0);ts.push(n);const o=e.rotation?Jo(e.rotation):new t.Quat;es.push(o);let i=e.fov||os;i<3.5&&(i*=180/Math.PI),ns.push(i)}),ts.length>=2){const t=_n;_n=!0,as(ki),_n=t}else ts.length>0&&(Gi=ts[0].clone(),ji=es[0].clone(),is=ns[0]);const n=r.waypoints?.length||1,o=Math.max(1,20*Fi(Ii,n));void 0!==r.autoplaySpeed&&(Wi=60*r.autoplaySpeed/o),console.log("[Editor] Tour path rebuilt with",ts.length,"waypoints")},ad.setSplatScale=(t,e,n)=>{const o=e??r.invertXScale??!1,i=n??r.invertYScale??!1;if(r.invertXScale=o,r.invertYScale=i,r.scale={x:t,y:t,z:t},!ot)return;const s=o?-t:t,a=i?t:-t,l=o!==i?t:-t;ot.setLocalScale(s,a,l)},ad.setSplatPosition=(t,e,n)=>{r.position=[t,e,n],ot&&ot.setPosition(t,e,-n)},ad.setSplatRotation=(t,e,n)=>{r.rotation=[t,e,n],ot&&fi(ot,[t,e,n],it)},ad.setAutoplaySpeed=t=>{r.autoplaySpeed=t;const e=r.waypoints?.length||1,n=Math.max(1,20*Fi(Ii,e));Wi=60*t/n},ad.setLoopMode=t=>{const e=Ii;Ii=t,r.loopMode=t,ki=Ui(ki,e,t),Ti=Ui(Ti,e,t);const n=r.waypoints?.length||1,o=Math.max(1,20*Fi(Ii,n));void 0!==r.autoplaySpeed&&(Wi=60*r.autoplaySpeed/o),_n&&as(ki)},ad.setScrollSpeed=t=>{r.scrollSpeed=t},ad.startPlayground=()=>{or()},ad.stopPlayground=()=>{ir()},ad.isPlaygroundActive=()=>Ga,ad.initVoxelCollision=async t=>{if(!vn){const t=null!=r.walkSpeed?r.walkSpeed:sn;vn=new at(he,U,{moveSpeed:t,sprintMultiplier:2,lookSensitivity:1/(r.cameraRotationSensitivity||4e3)*10,playerHeight:r.playerHeight||1.6,gravity:20,jumpVelocity:8,collisionRadius:.3,stepHeight:.3}),w.includes("walk")||w.push("walk"),console.log("[StorySplat Viewer] CharacterController created lazily for voxel collision")}ot&&vn.setSplatEntity(ot),await vn.initVoxelCollision(t);const e=vn.voxelCollisionInstance;e&&un.setVoxelCollision(e,ot),console.log("[StorySplat Viewer] Voxel collision loaded via editor API")},ad.setVoxelDebug=t=>{vn&&vn.setVoxelDebug(t)},ad.getVoxelDebugVisible=()=>vn?.voxelDebugVisible??!1,ad.clearVoxelDebug=()=>{vn?.clearVoxelDebug()},ad.getVoxelDebugLayerId=()=>Pn.id,ad.getEntityAnimationSystem=()=>il,ad.setEntityAnimations=t=>{il?il.setAnimations(t):il=new ue(sl,t,al)},ad.setPostProcessing=e=>{if(Ne(e)&&he.camera)try{We||(We=new t.CameraFrame(U,he.camera)),We.enabled=!0,_e(We,e)}catch(t){console.warn("[StorySplat Viewer] CameraFrame setup failed:",t)}else We&&(_e(We,{}),We.enabled=!1);const n=!1!==e?.antialiasing?.enabled;n&&!We?.enabled&&he.camera?(ye||(ye=new fe(U.graphicsDevice)),he.camera.postEffects.effects?.some(t=>t.effect===ye)||he.camera.postEffects.addEffect(ye)):ye&&he.camera&&(he.camera.postEffects.removeEffect(ye),ye=null),n&&We?.enabled&&(We.rendering.sharpness=Math.max(We.rendering.sharpness,.5)),Ce(e?.fog),Me(e?.exposure)},ad.setFisheye=t=>{ke(t)},ad.setWeather=t=>{r.weather=t,Ue(t)},ad.setLodSettings=t=>{if(!U.scene.gsplat)return;const e={"desktop-max":{range:[0,5],lodBaseDistance:15,lodMultiplier:2},desktop:{range:[0,2],lodBaseDistance:15,lodMultiplier:2},"mobile-max":{range:[1,2],lodBaseDistance:15,lodMultiplier:2},mobile:{range:[2,5],lodBaseDistance:15,lodMultiplier:2}};let n=0,o=5,i=15,s=2;if("custom"===t.preset)n=t.lodRangeMin??0,o=t.lodRangeMax??5,i=t.lodBaseDistance??15,s=t.lodMultiplier??2;else if("auto"!==t.preset){const a=e[t.preset];a&&(n=a.range[0],o=a.range[1],i=a.lodBaseDistance,s=a.lodMultiplier)}else{let a;a=q&&t.mobilePreset&&e[t.mobilePreset]?t.mobilePreset:!q&&t.desktopPreset&&e[t.desktopPreset]?t.desktopPreset:dn(q);const r=e[a];n=r.range[0],o=r.range[1],i=r.lodBaseDistance,s=r.lodMultiplier}U.scene.gsplat.lodRangeMin=n,U.scene.gsplat.lodRangeMax=o;const a=t.splatBudget??0;U.scene.gsplat.splatBudget=a,Q=i,J=s,K=[n,o];const r=t=>{if(!t)return;const e=t.gsplat;e&&(e.lodBaseDistance=i,e.lodMultiplier=s)};r(ot),Ut.forEach(t=>r(t));const l=t.renderer;l&&l in en&&(U.scene.gsplat.renderer=en[l],on(U,"setLodSettings",l)),console.log("[StorySplat Viewer] LOD settings updated:",{preset:t.preset,rangeMin:n,rangeMax:o,baseDistance:i,multiplier:s,budget:a,renderer:t.renderer})},ad.loadSegments=async(t,e)=>{const n=async(o=20)=>{if(!ot?.gsplat)return o<=0?void console.warn("[StorySplat Viewer] Cannot load segments: splat entity never became ready"):(await new Promise(t=>setTimeout(t,500)),n(o-1));const i=await ge(ot,t,e);xn=i,r.enableSegmentHover=!0,r.segmentDataUrl=t,r.segmentMetaUrl=e};try{await n()}catch(t){console.warn("[StorySplat Viewer] Segment loading failed:",t)}},ad.getSegmentMetadata=()=>xn?.metadata??null,ad.getSegmentIds=()=>xn?.segmentIds??null,ad.selectBySegment=(t,e,n)=>{if(!xn)return;const o=xn.segmentIds;if(e){for(let n=0;n<Math.min(o.length,e.length);n++)o[n]===t?e[n]|=1:e[n]&=-2;n?.()}else console.warn("[StorySplat Viewer] selectBySegment: no editAttachmentState provided")},ad.clearSegments=()=>{xn&&(xn.destroy(),xn=null),wn&&(wn.remove(),wn=null),r.enableSegmentHover=!1},ad}if(r.customScript&&""!==r.customScript.trim()){console.log("[StorySplat Viewer] Initializing custom script system...");const ld=ie(rl,U,I,r.customScript,()=>ki,()=>et);ld&&(U.__customScriptSystem=ld,console.log("[StorySplat Viewer] Custom script system initialized"))}const ll=!0===o.disableAnalytics,cl=[];if(o.analytics&&!ll&&!s){const cd=o.analytics.baseUrl??"https://discover.storysplat.com";s=re(rl,cd,o.analytics.sceneId,o.analytics.ownerId),cl.push(s)}if(o.googleTagId&&!ll&&cl.push(pe(rl,o.googleTagId,{sceneId:o.analytics?.sceneId||"",sceneName:n.name||""})),cl.length>0){const dd=rl.destroy;rl.destroy=()=>{for(const t of cl)try{t.destroy()}catch{}dd()}}return rl}async function un(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),hn(t,i,n)}function mn(t,e,n){return t+(e-t)*n}async function gn(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 fn extends Error{constructor(t){super(`Scene not found: ${t}`),this.name="SceneNotFoundError"}}class yn extends Error{constructor(t,e){super(t),this.name="SceneApiError",this.statusCode=e}}const vn=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 bn(t,e,n={}){const o=n.baseUrl||vn;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 fn(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 yn(n,a.status)}const r=await a.json();if(!r.success||!r.data)throw new yn("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=!0===h.disableAnalytics,m=!("analytics"in n)&&!u,g=r.meta.htmlUrl||r.meta.playcanvasHtmlUrl||null,f=hn(t,c,{...h,...m&&{analytics:{sceneId:e,ownerId:l,baseUrl:o}}});if(g&&f.on("error",e=>{if(e?.message?.includes("Failed to load splat from any URL")){console.log("[StorySplat] Falling back to legacy viewer:",g);try{f.destroy()}catch(t){}const e=document.createElement("iframe");e.src=g,e.style.cssText="width:100%;height:100%;border:none;position:absolute;top:0;left:0;",e.allow="fullscreen; autoplay",e.title=r.meta.name||"StorySplat Scene";const n=t.querySelector(".storysplat-outdated-banner");t.replaceChildren(),t.style.position="relative",n&&t.appendChild(n),t.appendChild(e)}}),m){gn(o,e,l,"view");let t=!1;f.on("loaded",n=>{t||(t=!0,n&&n.bandwidthUsed>0&&n.isStorySplatHosted?gn(o,e,l,"bandwidth",n.bandwidthUsed):console.log("[StorySplat] Bandwidth not tracked (self-hosted or no data)"))})}return f}async function xn(t,e={}){const n=`${e.baseUrl||vn}/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 fn(t);throw new yn(`API error: ${i.status}`,i.status)}const s=await i.json();return{...s,userName:s.userName||"Unknown",userSlug:s.userSlug||"unknown"}}class wn{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 Sn{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")||t.tags?.has("custom-mesh")){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 _n{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 Cn;!function(t){t[t.selected=1]="selected",t[t.hidden=2]="hidden",t[t.deleted=4]="deleted"}(Cn||(Cn={}));const Mn=512,En=1536,Pn=[0,4,8,12,1,5,9,13,2,6,10,14];class kn{constructor(e){this.device=e,this.count=1,this.version=0,this.texture=new t.Texture(e,{name:"splatTransformPalette",width:En,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/Mn);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%Mn*3,o=Math.floor(t/Mn)*En*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[Pn[t]]}}getTransform(e,n){const o=n??new t.Mat4,i=e%Mn*3,s=Math.floor(e/Mn)*En*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[Pn[t]]=this.data[s+4*(i+e)+n]}return o}upload(){this._upload()}destroy(){this.texture.destroy()}_writeIdentity(t){const e=t%Mn*3,n=Math.floor(t/Mn)*En*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:En,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(En*i*4),this.data.set(n),this.version++}}const Tn=[{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"}],An=[];for(let t=0;t<45;t++)An.push(`f_rest_${t}`);const Ln=[0,9,24,45];class Rn{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]&Cn.deleted||s++;const a=Tn.filter(t=>{try{return null!==i.getProp(t.storage)}catch{return!1}});let r=[];for(const t of An)try{null!==i.getProp(t)&&r.push(t)}catch{}if(void 0!==n&&n>=0&&n<=3){const t=Ln[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),b=a.findIndex(t=>"y"===t.name),x=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),C=a.findIndex(t=>"rot_3"===t.name),M=a.findIndex(t=>"scale_0"===t.name),E=a.findIndex(t=>"scale_1"===t.name),P=a.findIndex(t=>"scale_2"===t.name),k=new Map,T=n=>{if(0===n)return null;let o=k.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},k.set(n,o)}return o},A=new t.Vec3,L=new t.Quat;let R=0;for(let t=0;t<e.numSplats;t++){if(e.state[t]&Cn.deleted)continue;const n=T(e.transform[t]);if(n){const e=f[v]?.[t]??0,o=f[b]?.[t]??0,i=f[x]?.[t]??0;A.set(e,o,i),n.mat.transformPoint(A,A),w>=0&&(L.set(f[S]?.[t]??0,f[_]?.[t]??0,f[C]?.[t]??0,f[w]?.[t]??1),L.mul2(n.rot,L));for(let e=0;e<f.length;e++){let o=f[e]?.[t]??0;e===v?o=A.x:e===b?o=A.y:e===x?o=A.z:e===w?o=L.w:e===S?o=L.x:e===_?o=L.y:e===C?o=L.z:e===M?o+=Math.log(Math.abs(n.scale.x)):e===E?o+=Math.log(Math.abs(n.scale.y)):e===P&&(o+=Math.log(Math.abs(n.scale.z))),g[R++]=o}}else for(let e=0;e<f.length;e++)g[R++]=f[e]?.[t]??0;for(let e=0;e<y.length;e++)g[R++]=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.resource??o?.resource;return i?.gsplatData?i.gsplatData:null}}class zn{constructor(e,n){this._stats={total:0,selected:0,hidden:0,deleted:0,shBands:0},this._material=null,this._originalVS=null,this._originalPS=null,this._originalCenterVS=null,this._wasUnified=!1,this._savedAssetId=null,this._savedResource=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.entity=e,this._app=n,this.device=n.graphicsDevice,this.transformPalette=new kn(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.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),console.log("[SplatEdit] Attachment init:","instance:",!!i,"placement:",!!s,"resource:",!!a,"texDims:",r.x,"x",r.y,"numSplats:",this.numSplats,"stateArrayLen:",this.texWidth*this.texHeight,"resource.textureDimensions:",a?.textureDimensions,"instance.material:",!!i?.material),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_R8,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._updateStats(),console.log("[SplatEdit] updateState:",this._stats,"stateTexture format:",this.stateTexture.format,"stateTexture size:",this.stateTexture.width,"x",this.stateTexture.height)}updateTransform(){this._uploadTransform()}destroy(){this._removeShaders(),this.stateTexture.destroy(),this.transformTexture.destroy(),this.transformPalette.destroy()}_uploadState(){this.stateTexture.lock().set(this.state),this.stateTexture.unlock()}_uploadTransform(){this.transformTexture.lock().set(this.transform),this.transformTexture.unlock()}_updateStats(){let t=0,e=0,n=0;for(let o=0;o<this.numSplats;o++){const i=this.state[o];i&Cn.selected&&t++,i&Cn.hidden&&e++,i&Cn.deleted&&n++}this._stats.selected=t,this._stats.hidden=e,this._stats.deleted=n}_applyShaders(){let t=this.entity.gsplat;if(!t)return;(t.unified||t._unified)&&(console.log("[SplatEdit] Switching from unified to non-unified for edit mode"),this._wasUnified=!0,this._savedAssetId=t.asset??null,this._savedResource=t.resource??null,this.entity.removeComponent("gsplat"),this._savedResource?this.entity.addComponent("gsplat",{resource:this._savedResource}):this._savedAssetId&&this.entity.addComponent("gsplat",{asset:this._savedAssetId}),t=this.entity.gsplat);const e=t.instance;if(this._material=e?.material?e.material:t.material??null,console.log("[SplatEdit] _applyShaders:","instance:",!!e,"instance.material:",!!e?.material,"material:",!!this._material,"wasUnified:",this._wasUnified),!this._material)return void console.warn("SplatEditAttachment: could not find gsplat material");const n=this.device?.isWebGPU?"wgsl":"glsl",o=this._material.getShaderChunks(n);this._originalVS=o.get("gsplatVS")??null,this._originalPS=o.get("gsplatPS")??null,this._originalCenterVS=o.get("gsplatCenterVS")??null,o.set("gsplatVS",'\n#include "gsplatCommonVS"\n\nuniform sampler2D splatState;\n\nuniform vec4 selectedClr;\nuniform vec4 lockedClr;\n\nuniform vec3 clrOffset;\nuniform vec4 clrScale;\n\nvarying mediump vec4 texCoord_flags; // xy: texCoord, z: selected, w: locked\nvarying mediump vec4 color;\n\n#if PICK_PASS\n uniform uint pickOp; // 0: add, 1: remove, 2: set\n uniform int pickMode; // 0: pick id, 1: depth estimation\n#endif\n\nmediump vec4 discardVec = vec4(0.0, 0.0, 2.0, 1.0);\n\nuniform float saturation;\n\nvec3 applySaturation(vec3 color) {\n vec3 grey = vec3(dot(color, vec3(0.299, 0.587, 0.114)));\n return grey + (color - grey) * saturation;\n}\n\nvoid main(void) {\n // read gaussian details\n SplatSource source;\n if (!initSource(source)) {\n gl_Position = discardVec;\n return;\n }\n\n // get per-gaussian edit state, discard if deleted\n uint vertexState = uint(texelFetch(splatState, splat.uv, 0).r * 255.0 + 0.5) & 7u;\n\n #if PICK_PASS\n if (pickOp == 0u) {\n // add: skip deleted, locked and selected splats\n if (vertexState != 0u) {\n gl_Position = discardVec;\n return;\n }\n } else if (pickOp == 1u) {\n // remove: skip deleted, locked and unselected splats\n if (vertexState != 1u) {\n gl_Position = discardVec;\n return;\n }\n } else {\n // set: skip deleted and locked splats\n if ((vertexState & 6u) != 0u) {\n gl_Position = discardVec;\n return;\n }\n }\n #else\n // skip deleted splats\n if ((vertexState & 4u) != 0u) {\n gl_Position = discardVec;\n return;\n }\n #endif\n\n // get center\n vec3 modelCenter = getCenter();\n\n SplatCenter center;\n center.modelCenterOriginal = modelCenter;\n center.modelCenterModified = modelCenter;\n if (!initCenter(modelCenter, center)) {\n gl_Position = discardVec;\n return;\n }\n\n SplatCorner corner;\n if (!initCorner(source, center, corner)) {\n gl_Position = discardVec;\n return;\n }\n\n gl_Position = center.proj + vec4(corner.offset, 0.0);\n\n // store texture coord and locked state\n texCoord_flags = vec4(\n corner.uv,\n (vertexState & 1u) != 0u ? 1.0 : 0.0, // selected\n (vertexState & 2u) != 0u ? 1.0 : 0.0 // locked\n );\n\n #if PICK_PASS\n if (pickMode == 1) {\n // depth estimation mode\n float linearDepth = -center.view.z;\n float normalizedDepth = (linearDepth - camera_params.z) / (camera_params.y - camera_params.z);\n vec4 clr = getColor();\n color = vec4(normalizedDepth, 0.0, 0.0, 1.0) * clr.a;\n } else {\n // pick id — encode splat.index as RGBA\n uvec4 bits = (uvec4(splat.index) >> uvec4(0u, 8u, 16u, 24u)) & uvec4(255u);\n color = vec4(bits) / 255.0;\n }\n #elif FORWARD_PASS\n // read color\n color = getColor();\n\n // evaluate spherical harmonics\n #if SH_BANDS > 0\n vec3 dir = normalize(center.view * mat3(center.modelView));\n vec3 sh[SH_COEFFS];\n float scale;\n readSHData(sh, scale);\n color.xyz += evalSH(sh, dir) * scale;\n #endif\n\n // apply tint/brightness\n color = color * clrScale + vec4(clrOffset, 0.0);\n\n // apply saturation\n color.xyz = applySaturation(color.xyz);\n\n // don\'t allow out-of-range alpha\n color.a = clamp(color.a, 0.0, 1.0);\n\n // apply tonemapping\n color = vec4(prepareOutputFromGamma(max(color.xyz, 0.0), -center.view.z), color.w);\n\n // apply locked/selected colors\n if ((vertexState & 2u) != 0u) {\n color *= lockedClr;\n } else if ((vertexState & 1u) != 0u) {\n color.xyz = mix(color.xyz, selectedClr.xyz, selectedClr.a);\n }\n #endif\n}\n'),o.set("gsplatPS","\nvarying mediump vec4 texCoord_flags;\nvarying mediump vec4 color;\n\nuniform bool outlineMode;\nuniform float ringSize;\n\n#if PICK_PASS\n uniform int pickMode; // 0: id, 1: depth estimation\n#endif\n\nconst float EXP4 = exp(-4.0);\nconst float INV_EXP4 = 1.0 / (1.0 - EXP4);\n\nfloat normExp(float x) {\n return (exp(x * -4.0) - EXP4) * INV_EXP4;\n}\n\nvoid main(void) {\n mediump float A = dot(texCoord_flags.xy, texCoord_flags.xy);\n\n if (A > 1.0) {\n discard;\n }\n\n #if PICK_PASS\n if (pickMode == 1) {\n // depth estimation\n mediump float alpha = normExp(A);\n if (alpha < 1.0 / 255.0) {\n discard;\n }\n gl_FragColor = color * alpha;\n } else {\n // pick id\n gl_FragColor = color;\n }\n #else\n mediump float norm = normExp(A);\n mediump float alpha = norm * color.a;\n\n if (texCoord_flags.w == 0.0 && ringSize > 0.0) {\n // rings mode — show splat edges\n if (A < 1.0 - ringSize) {\n alpha = max(0.05, alpha);\n } else {\n alpha = 0.6;\n }\n }\n\n bool selected = texCoord_flags.z != 0.0 && texCoord_flags.w == 0.0;\n\n if (outlineMode) {\n pcFragColor0 = vec4(color.xyz * alpha, alpha);\n pcFragColor1 = vec4(0.0, 0.0, 0.0, selected ? norm : 0.0);\n } else {\n if (selected) {\n pcFragColor0 = vec4(color.xyz * alpha * 0.8, alpha);\n pcFragColor1 = vec4(color.xyz * alpha * 0.2, alpha);\n } else {\n pcFragColor0 = vec4(color.xyz * alpha, alpha);\n pcFragColor1 = vec4(0.0, 0.0, 0.0, 0.0);\n }\n }\n #endif\n}\n"),o.set("gsplatCenterVS","\nuniform highp usampler2D splatTransform; // per-splat index into transform palette\nuniform sampler2D transformPalette; // palette of transform matrices\n\nmat4 applyPaletteTransform(mat4 model) {\n uint transformIndex = texelFetch(splatTransform, splat.uv, 0).r;\n if (transformIndex == 0u) {\n return model;\n }\n\n // read transform matrix\n int u = int(transformIndex % 512u) * 3;\n int v = int(transformIndex / 512u);\n\n mat4 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 t[3] = vec4(0.0, 0.0, 0.0, 1.0);\n\n return model * transpose(t);\n}\n\nuniform mat4 matrix_model;\nuniform mat4 matrix_view;\n#ifndef GSPLAT_CENTER_NOPROJ\n uniform vec4 camera_params; // 1 / far, far, near, isOrtho\n uniform mat4 matrix_projection;\n#endif\n\n// project the model space gaussian center to view and clip space\nbool initCenter(vec3 modelCenter, inout SplatCenter center) {\n mat4 modelView = matrix_view * applyPaletteTransform(matrix_model);\n vec4 centerView = modelView * vec4(modelCenter, 1.0);\n\n #ifndef GSPLAT_CENTER_NOPROJ\n if (camera_params.w != 1.0 && centerView.z > 0.0) {\n return false;\n }\n\n vec4 centerProj = matrix_projection * centerView;\n\n #if WEBGPU\n centerProj.z = clamp(centerProj.z, 0, abs(centerProj.w));\n #else\n centerProj.z = clamp(centerProj.z, -abs(centerProj.w), abs(centerProj.w));\n #endif\n\n center.proj = centerProj;\n center.projMat00 = matrix_projection[0][0];\n #endif\n\n center.view = centerView.xyz / centerView.w;\n center.modelView = modelView;\n return true;\n}\n");const i=this._detectSHBands(),s=t.resource??e?.resource,a=s?.shBands??i;this._material.setDefine?.("SH_BANDS",`${Math.min(i,a)}`),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.setParameter("outlineMode",0),this._material.setParameter("ringSize",0),this._material.update()}updateMaterialParams(){if(!this._material)return;const t=this.tintClr.r,e=this.tintClr.g,n=this.tintClr.b,o=this.temperature>0?1+.2*this.temperature:1,i=this.temperature<0?1-.2*this.temperature:1,s=(this.whitePoint-this.blackPoint)*t*o,a=(this.whitePoint-this.blackPoint)*e,r=(this.whitePoint-this.blackPoint)*n*i;this._material.setParameter("clrOffset",[this.brightness+this.blackPoint,this.brightness+this.blackPoint,this.brightness+this.blackPoint]),this._material.setParameter("clrScale",[s,a,r,this.transparency]),this._material.setParameter("saturation",this.saturation),this._material.setParameter("transformPalette",this.transformPalette.texture)}_removeShaders(){if(!this._material)return;const t=this.device?.isWebGPU?"wgsl":"glsl",e=this._material.getShaderChunks(t);null!==this._originalVS?e.set("gsplatVS",this._originalVS):e.delete("gsplatVS"),null!==this._originalPS?e.set("gsplatPS",this._originalPS):e.delete("gsplatPS"),null!==this._originalCenterVS?e.set("gsplatCenterVS",this._originalCenterVS):e.delete("gsplatCenterVS"),this._material.update(),this._material=null,this._wasUnified&&(console.log("[SplatEdit] Restoring unified mode"),this.entity.removeComponent("gsplat"),this._savedResource?this.entity.addComponent("gsplat",{resource:this._savedResource,unified:!0}):this._savedAssetId&&this.entity.addComponent("gsplat",{asset:this._savedAssetId,unified:!0}),this._wasUnified=!1,this._savedAssetId=null,this._savedResource=null)}_detectSHBands(){const t=this.entity.gsplat;if(!t)return 0;const e=t.instance??t._instance,n=t._placement,o=e??n,i=t.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 Dn=(t,e)=>{for(const n in e)t.resolve(n).setValue(e[n])};class In{constructor(e){this.shader=null,this.texture=null,this.renderTarget=null,this.data=null,this.device=e,this.dummyTexture=new t.Texture(e,{width:1,height:1,format:s})}getResources(e,n){const{device:o}=this;this.shader||(this.shader=t.ShaderUtils.createShader(o,{uniqueName:"splatEditIntersectShader",attributes:{vertex_position:a},vertexGLSL:"\n attribute vec2 vertex_position;\n void main(void) {\n gl_Position = vec4(vertex_position, 0.0, 1.0);\n }\n",fragmentGLSL:"\n uniform highp usampler2D transformA; // splat center x, y, z\n uniform highp usampler2D splatTransform; // transform palette index\n uniform sampler2D transformPalette; // palette of transforms\n uniform uvec2 splat_params; // splat texture width, num splats\n\n uniform mat4 matrix_model;\n uniform mat4 matrix_viewProjection;\n\n uniform uvec2 output_params; // output width, height\n\n // 0: mask, 1: rect, 2: sphere, 3: box\n uniform int mode;\n\n // mask params\n uniform sampler2D mask; // mask in alpha channel\n uniform vec2 mask_params; // mask width, height\n\n // rect params\n uniform vec4 rect_params; // rect x1, y1, x2, y2 in NDC\n\n // sphere params\n uniform vec4 sphere_params; // sphere x, y, z, radius\n\n // box params\n uniform vec4 box_params; // box center x, y, z\n uniform vec4 aabb_params; // half-extents x, y, z\n\n void main(void) {\n // calculate output id\n uvec2 outputUV = uvec2(gl_FragCoord);\n uint outputId = (outputUV.x + outputUV.y * output_params.x) * 4u;\n\n vec4 clr = vec4(0.0);\n\n for (uint i = 0u; i < 4u; i++) {\n uint id = outputId + i;\n\n if (id >= splat_params.y) {\n continue;\n }\n\n // calculate splatUV\n ivec2 splatUV = ivec2(\n int(id % splat_params.x),\n int(id / splat_params.x)\n );\n\n // read splat center\n vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);\n\n // apply optional per-splat transform\n uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;\n if (transformIndex > 0u) {\n // read transform matrix\n int u = int(transformIndex % 512u) * 3;\n int v = int(transformIndex / 512u);\n\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 center = vec4(center, 1.0) * t;\n }\n\n // transform to clip space and discard if outside\n vec3 world = (matrix_model * vec4(center, 1.0)).xyz;\n vec4 clip = matrix_viewProjection * vec4(world, 1.0);\n vec3 ndc = clip.xyz / clip.w;\n\n // skip offscreen fragments\n if (!any(greaterThan(abs(ndc), vec3(1.0)))) {\n if (mode == 0) {\n // select by mask\n ivec2 maskUV = ivec2((ndc.xy * vec2(0.5, -0.5) + 0.5) * mask_params);\n clr[i] = texelFetch(mask, maskUV, 0).a < 1.0 ? 0.0 : 1.0;\n } else if (mode == 1) {\n // select by rect\n clr[i] = all(greaterThan(ndc.xy * vec2(1.0, -1.0), rect_params.xy)) && all(lessThan(ndc.xy * vec2(1.0, -1.0), rect_params.zw)) ? 1.0 : 0.0;\n } else if (mode == 2) {\n // select by sphere\n clr[i] = length(world - sphere_params.xyz) < sphere_params.w ? 1.0 : 0.0;\n } else if (mode == 3) {\n // select by box\n vec3 relativePosition = world - box_params.xyz;\n bool isInsideCube = true;\n if (relativePosition.x < -aabb_params.x || relativePosition.x > aabb_params.x) {\n isInsideCube = false;\n }\n if (relativePosition.y < -aabb_params.y || relativePosition.y > aabb_params.y) {\n isInsideCube = false;\n }\n if (relativePosition.z < -aabb_params.z || relativePosition.z > aabb_params.z) {\n isInsideCube = false;\n }\n clr[i] = isInsideCube ? 1.0 : 0.0;\n }\n }\n }\n\n gl_FragColor = clr;\n }\n"}));const i=Math.max(1,Math.floor(e/2)),l=Math.ceil(n/(4*i));return this.texture&&this.texture.width===i&&this.texture.height===l||(this.texture&&(this.texture.destroy(),this.renderTarget.destroy()),this.texture=new t.Texture(o,{name:"splatEditIntersectTexture",width:i,height:l,format:s,mipmaps:!1,addressU:r,addressV:r}),this.renderTarget=new t.RenderTarget({colorBuffer:this.texture,depth:!1}),this.data=new Uint8Array(i*l*4)),{shader:this.shader,texture:this.texture,renderTarget:this.renderTarget,data:this.data}}async run(e,n,o,i,s,a,r,d){const{device:p}=this,{scope:h}=p,u=this.getResources(n.width,e);if(Dn(h,{transformA:n,splatTransform:i,transformPalette:s,splat_params:[n.width,e],matrix_model:a.data,matrix_viewProjection:"mask"===d.mode||"rect"===d.mode?d.viewProjection.data:(new t.Mat4).data,output_params:[u.texture.width,u.texture.height]}),"mask"===d.mode?Dn(h,{mode:0,mask:d.maskTexture,mask_params:[d.maskTexture.width,d.maskTexture.height]}):Dn(h,{mask:this.dummyTexture,mask_params:[0,0]}),"rect"===d.mode?Dn(h,{mode:1,rect_params:[d.rect.minX,d.rect.minY,d.rect.maxX,d.rect.maxY]}):Dn(h,{rect_params:[0,0,0,0]}),"sphere"===d.mode?Dn(h,{mode:2,sphere_params:[d.center.x,d.center.y,d.center.z,d.radius]}):Dn(h,{sphere_params:[0,0,0,0]}),"box"===d.mode){const e=(new t.Vec3).add2(d.min,d.max).mulScalar(.5),n=(new t.Vec3).sub2(d.max,d.min).mulScalar(.5);Dn(h,{mode:3,box_params:[e.x,e.y,e.z,0],aabb_params:[n.x,n.y,n.z,0]})}else Dn(h,{box_params:[0,0,0,0],aabb_params:[0,0,0,0]});p.setBlendState(l.NOBLEND),c(p,u.renderTarget,u.shader);return await u.texture.read(0,0,u.texture.width,u.texture.height,{renderTarget:u.renderTarget,data:u.data,immediate:!1})}destroy(){this.shader?.destroy(),this.texture?.destroy(),this.renderTarget?.destroy(),this.dummyTexture?.destroy(),this.shader=null,this.texture=null,this.renderTarget=null}}class Fn{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,b,x]=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(b,x)};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 Bn{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 Vn{constructor(t){this._queue=Promise.resolve(),this._centersTexture=null,this.device=t,this._intersect=new In(t),this._calcBound=new Fn(t),this._calcPositions=new Bn(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(3*e.numSplats))})}destroy(){this._intersect.destroy(),this._calcBound.destroy(),this._calcPositions.destroy(),this._centersTexture?.destroy(),this._centersTexture=null}_enqueue(t){const e=this._queue.then(t);return this._queue=e.then(()=>{},()=>{}),e}_getTransformATexture(e){if(this._centersTexture)return this._centersTexture;const n=e.entity.gsplat;if(!n)return null;const o=n.instance;if(o?.resource){const t=o.resource;if("function"==typeof t.getTexture){const e=t.getTexture("transformA");if(e)return e}if(t.streams?.textures instanceof Map){const e=t.streams.textures.get("transformA");if(e)return e}}const i=n._placement;if(i?.resource){const t=i.resource;if("function"==typeof t.getTexture){const e=t.getTexture("transformA");if(e)return e}if(t.streams?.textures instanceof Map){const e=t.streams.textures.get("transformA");if(e)return e}}const s=n.resource;if(s){if("function"==typeof s.getTexture){const t=s.getTexture("transformA");if(t)return t}if(s.streams?.textures instanceof Map){const t=s.streams.textures.get("transformA");if(t)return t}}const a=o?.resource??i?.resource??s,r=a?.centers??null;if(r&&r.length>0){const n=e.texWidth,o=e.texHeight,i=new t.Texture(this.device,{name:"splatEditCenters",width:n,height:o,format:t.PIXELFORMAT_RGBA32U,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=new Float32Array(s.buffer,s.byteOffset,s.length),l=Math.min(e.numSplats,Math.floor(r.length/3));for(let t=0;t<l;t++)a[4*t+0]=r[3*t+0],a[4*t+1]=r[3*t+1],a[4*t+2]=r[3*t+2],a[4*t+3]=1;return i.unlock(),this._centersTexture=i,console.log("[SplatEdit] Built centers texture from resource.centers:",l,"splats,",n,"x",o,"texture"),i}return console.warn("[SplatEdit] _getTransformATexture: no centers data found.","resource type:",a?.constructor?.name,"streams:",a?.streams?.textures instanceof Map?[...a.streams.textures.keys()]:"none","centers:",!!r),null}}class $n{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 Un{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]&Cn.deleted||("set"===this.op?this.selectionResult[n]?t[n]|=Cn.selected:t[n]&=~Cn.selected:"add"===this.op?this.selectionResult[n]&&(t[n]|=Cn.selected):"remove"===this.op&&this.selectionResult[n]&&(t[n]&=~Cn.selected));this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class On{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]&Cn.deleted||(t[n]|=Cn.selected);this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class Nn{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]&=~Cn.selected;this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class Wn{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]&Cn.deleted||(t[n]^=Cn.selected);this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class Hn{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]&Cn.selected&&(t[n]=(t[n]|Cn.hidden)&~Cn.selected);this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class Gn{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]&=~Cn.hidden;this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class jn{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]&Cn.selected&&(t[n]=(t[n]|Cn.deleted)&~Cn.selected);this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class Xn{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]&=~Cn.deleted;this.attachment.updateState()}undo(){this.attachment.state.set(this.prevState),this.attachment.updateState()}}class qn{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 Yn{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]&Cn.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]&Cn.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(){}}const Zn=4294967295;class Kn{constructor(t){this._picker=null,this._app=t}pickRect(t,e,n,o,i){const s=t.camera;if(!s)return new Set;const a=this._getOrCreatePicker(o,i);a.resize(o,i),a.prepare(s,this._app.scene);const r=this._readPickPixels(a,0,0,o,i);return r?this._decodePixels(r):new Set}pickPoint(t,e,n){const o=t.camera;if(!o)return null;const i=this._app.graphicsDevice.canvas,s=i.width,a=i.height,r=this._getOrCreatePicker(s,a);r.resize(s,a),r.prepare(o,this._app.scene);const l=this._readPickPixels(r,e,n,1,1);if(!l||l.length<4)return null;const c=l[0],d=l[1],p=l[2],h=(l[3]<<24|c<<16|d<<8|p)>>>0;return h!==Zn?h:null}pickByMask(t,e){const n=t.camera;if(!n)return new Set;const o=this._app.graphicsDevice.canvas,i=o.width,s=o.height,a=this._getOrCreatePicker(i,s);a.resize(i,s),a.prepare(n,this._app.scene);const r=this._readPickPixels(a,0,0,i,s);if(!r)return new Set;const l=e.width,c=e.height,d=e.lock(),p=new Uint8Array(d);e.unlock();const h=new Set;for(let t=0;t<s;t++)for(let e=0;e<i;e++){const n=4*(t*i+e),o=r[n],a=r[n+1],d=r[n+2],u=(r[n+3]<<24|o<<16|a<<8|d)>>>0;if(u===Zn)continue;const m=Math.floor(e/i*l);p[4*(Math.floor(t/s*c)*l+m)+3]>25&&h.add(u)}return h}indicesToMask(t,e){const n=new Uint8Array(e);for(const o of t)o<e&&(n[o]=1);return n}destroy(){this._picker?.destroy(),this._picker=null}_getOrCreatePicker(e,n){return this._picker||(this._picker=new t.Picker(this._app,e,n)),this._picker}_readPickPixels(t,e,n,o,i){const s=this._app.graphicsDevice,a=t.renderTarget;if(!a)return null;const r=a.height-(n+i),l=Math.max(0,Math.floor(e)),c=Math.max(0,Math.floor(r)),d=Math.min(Math.floor(o),a.width-l),p=Math.min(Math.floor(i),a.height-c);if(d<=0||p<=0)return null;const h=new Uint8Array(4*d*p),u=s;return u.setRenderTarget(a),u.updateBegin(),u.readPixels(l,c,d,p,h),u.updateEnd(),h}_decodePixels(t){const e=new Set,n=t.length;for(let o=0;o<n;o+=4){const n=t[o],i=t[o+1],s=t[o+2],a=(t[o+3]<<24|n<<16|i<<8|s)>>>0;a!==Zn&&e.add(a)}return e}}class Qn{constructor(t,e){this._attachment=null,this._active=!1,this._previewBaseState=null,this._app=t,this._camera=e,this._dataProcessor=new Vn(t.graphicsDevice),this._picker=new Kn(t),this._history=new $n,this._history.onChange=()=>{this.onUndoRedoChange?.(this._history.canUndo,this._history.canRedo),this._notifyStats()}}activate(t){this._active&&this.deactivate(),this._attachment=new zn(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 Un(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 Un(this._attachment,t,o))}selectSplatAtPoint(t,e,n){if(!this._attachment)return null;const o=this._picker.pickPoint(this._camera,e,n);if(null===o)return"set"===t&&this._history.execute(new Nn(this._attachment)),null;const i=new Uint8Array(this._attachment.numSplats);return i[o]=1,this._history.execute(new Un(this._attachment,t,i)),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 Un(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 Un(this._attachment,t,o))}selectAll(){this._attachment&&this._history.execute(new On(this._attachment))}selectNone(){this._attachment&&this._history.execute(new Nn(this._attachment))}selectInvert(){this._attachment&&this._history.execute(new Wn(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 qn(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 Hn(this._attachment))}unhideAll(){this._attachment&&this._history.execute(new Gn(this._attachment))}deleteSelection(){this._attachment&&this._history.execute(new jn(this._attachment))}resetDeleted(){this._attachment&&this._history.execute(new Xn(this._attachment))}transformSelection(t){this._attachment&&this._history.execute(new Yn(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 Rn.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(),this._picker.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]&Cn.deleted||("set"===t?e[i]?n[i]|=Cn.selected:n[i]&=~Cn.selected:"add"===t?e[i]&&(n[i]|=Cn.selected):"remove"===t&&e[i]&&(n[i]&=~Cn.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 Jn="Apr 28, 22:40:57";export{Ve as ARPlacement,Jn as BUILD_VERSION,tt as CameraControls,oe as CustomScriptSystem,b as DEFAULT_BUTTON_LABELS,_n as EditorCameraController,ue as EntityAnimationSystem,se as FrameSequencePlayer,wn as GizmoManager,vt as GsplatRelighting,Tt as REVEAL_PRESETS,yn as SceneApiError,fn as SceneNotFoundError,Sn as SelectionManager,zn as SplatEditAttachment,Qn as SplatEditSystem,Rn as SplatSerializer,Cn as SplatState,kn as TransformPalette,it as VoxelCollision,Ue as addVec3,$e as cloneVec3,hn as createViewer,bn as createViewerFromSceneId,un as createViewerFromUrl,v as exportPropsToViewerConfig,xn as fetchSceneMeta,g as generateHTML,f as generateHTMLFromUrl,w as generateViewerStyles,Qe as generateWaypointPathSamples,yt as getGsplatRelightingClass,He as getPathSegmentCount,Lt as getRevealPreset,qe as getSegmentHandles,Ge as getSegmentIndices,je as getVirtualBezierHandles,Xe as isBezierSegment,We as isLoopingMode,Be as isNativeWebXRAvailable,Fe as isXR8Available,Ie as loadXR8,Ye as sampleCatmullRom,Ze as sampleCubicBezier,Ke as sampleWaypointPathSegment,Ne as scaleVec3,re as setupAnalyticsTracking,ie as setupCustomScript,pe as setupGoogleAnalytics,Oe as subVec3,y as transformSceneToExportProps};
2
2
  //# sourceMappingURL=index.esm.js.map