storysplat-viewer 0.1.21 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/index.js CHANGED
@@ -75012,7 +75012,7 @@ class CameraManager {
75012
75012
  }
75013
75013
  }
75014
75014
  setCameraMode(mode, immediate = false) {
75015
- var _a, _b, _c, _d, _e, _f;
75015
+ var _a, _b, _c, _d, _e, _f, _g;
75016
75016
  if (!this._camera)
75017
75017
  return;
75018
75018
  const allowedModes = (_b = (_a = this._viewerOptions) === null || _a === void 0 ? void 0 : _a.allowedCameraModes) !== null && _b !== void 0 ? _b : ['explore', 'walk', 'tour', 'hybrid'];
@@ -75062,7 +75062,8 @@ class CameraManager {
75062
75062
  // Trigger callback and update UI
75063
75063
  if (oldMode !== mode) {
75064
75064
  (_e = (_d = this._viewerOptions) === null || _d === void 0 ? void 0 : _d.onCameraModeChanged) === null || _e === void 0 ? void 0 : _e.call(_d, mode);
75065
- (_f = this._analyticsManager) === null || _f === void 0 ? void 0 : _f.trackCameraModeChange(oldMode, mode); // Track event
75065
+ (_f = this.onCameraModeChange) === null || _f === void 0 ? void 0 : _f.call(this, mode); // Call the new callback
75066
+ (_g = this._analyticsManager) === null || _g === void 0 ? void 0 : _g.trackCameraModeChange(oldMode, mode); // Track event
75066
75067
  this._updateUIManager(); // Notify UI Manager of the change
75067
75068
  }
75068
75069
  }
@@ -108343,7 +108344,7 @@ class NavigationManager {
108343
108344
  this.onProgressUpdate(scrollPercentage / 100, currentWaypointIndex);
108344
108345
  }
108345
108346
  // --- Tour/Hybrid Mode Logic ---
108346
- if ((this.currentMode === 'hybrid' && !this.userControl) || (this.currentMode === 'tour' && !this.userControl)) {
108347
+ if ((this.currentMode === 'hybrid') || (this.currentMode === 'tour' && !this.userControl)) {
108347
108348
  this.updateCameraPositionFromPath();
108348
108349
  }
108349
108350
  // Update UI for tour/hybrid modes
@@ -108353,8 +108354,8 @@ class NavigationManager {
108353
108354
  }
108354
108355
  // Check waypoint triggers
108355
108356
  this.updateActiveWaypoint();
108356
- // --- Explore/Walk Mode Logic ---
108357
- if (this.currentMode === 'explore' || this.currentMode === 'walk') {
108357
+ // --- Explore/Walk/Hybrid Mode Logic ---
108358
+ if (this.currentMode === 'explore' || this.currentMode === 'walk' || (this.currentMode === 'hybrid' && this.userControl)) {
108358
108359
  const deltaTime = this.scene.getEngine().getDeltaTime() / 1000.0;
108359
108360
  this.updateExploreWalkMovement(deltaTime);
108360
108361
  }
@@ -161032,6 +161033,13 @@ class SplatSwapManager {
161032
161033
  }
161033
161034
  }
161034
161035
 
161036
+ var UIType;
161037
+ (function (UIType) {
161038
+ UIType["Standard"] = "standard";
161039
+ UIType["Minimal"] = "minimal";
161040
+ UIType["Pro"] = "pro";
161041
+ })(UIType || (UIType = {}));
161042
+
161035
161043
  /**
161036
161044
  * Manages the preloader UI element.
161037
161045
  */
@@ -161600,6 +161608,775 @@ class MuteButtonUI {
161600
161608
  }
161601
161609
  }
161602
161610
 
161611
+ /**
161612
+ * Manages UI layout templates for the StorySplat viewer
161613
+ * Renders different UI layouts based on the uiType from scene JSON
161614
+ */
161615
+ class TemplateManager {
161616
+ constructor(targetElement, options) {
161617
+ var _a;
161618
+ // UI Components
161619
+ this.preloaderUI = null;
161620
+ this.startButtonUI = null;
161621
+ this.watermarkUI = null;
161622
+ this.helpPanelUI = null;
161623
+ this.muteButtonUI = null;
161624
+ // UI Elements for templates
161625
+ this.controlsContainer = null;
161626
+ this.modeToggleContainer = null;
161627
+ this.progressContainer = null;
161628
+ this.navigationContainer = null;
161629
+ this.targetElement = targetElement;
161630
+ this.options = options;
161631
+ this.uiType = ((_a = options.uiOptions) === null || _a === void 0 ? void 0 : _a.uiType) || 'standard';
161632
+ console.log(`TemplateManager: Initializing with uiType: ${this.uiType}`);
161633
+ }
161634
+ /**
161635
+ * Initialize UI components based on template type
161636
+ */
161637
+ async initializeUI(audioManager, navigationManager, cameraManager, onStartExperience) {
161638
+ // Create base UI components that are common to all templates
161639
+ this.createBaseComponents(audioManager, onStartExperience);
161640
+ // Apply template-specific layout
161641
+ switch (this.uiType) {
161642
+ case UIType.Minimal:
161643
+ case 'minimal':
161644
+ await this.applyMinimalTemplate(navigationManager, cameraManager);
161645
+ break;
161646
+ case UIType.Pro:
161647
+ case 'pro':
161648
+ await this.applyProTemplate(navigationManager, cameraManager);
161649
+ break;
161650
+ case UIType.Standard:
161651
+ case 'standard':
161652
+ default:
161653
+ await this.applyStandardTemplate(navigationManager, cameraManager);
161654
+ break;
161655
+ }
161656
+ }
161657
+ /**
161658
+ * Create base UI components common to all templates
161659
+ */
161660
+ createBaseComponents(audioManager, onStartExperience) {
161661
+ var _a, _b, _c, _d, _e, _f;
161662
+ // Preloader (always created)
161663
+ const defaultPreloaderConfig = {
161664
+ enabled: true,
161665
+ imageUrl: 'https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fimages%2FStorySplat.webp?alt=media&token=953e8ab3-1865-4ac1-a98d-b548b7066bda',
161666
+ lottieUrl: 'https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Flotties%2FstorySplatLottie.json?alt=media&token=d7edc19d-9cb8-4c20-96f3-d23b6d1272b4',
161667
+ uiColor: ((_a = this.options) === null || _a === void 0 ? void 0 : _a.uiColor) || '#4CAF50'
161668
+ };
161669
+ const preloaderConfig = Object.assign(Object.assign({}, defaultPreloaderConfig), (_b = this.options) === null || _b === void 0 ? void 0 : _b.preloaderConfig);
161670
+ this.preloaderUI = new PreloaderUI(this.targetElement, preloaderConfig);
161671
+ this.preloaderUI.create();
161672
+ // Start Button (conditional)
161673
+ if ((_c = this.options) === null || _c === void 0 ? void 0 : _c.showStartExperience) {
161674
+ this.startButtonUI = new StartButtonUI(this.targetElement, this.options, onStartExperience || (() => { }));
161675
+ this.startButtonUI.create();
161676
+ }
161677
+ // Watermark (conditional)
161678
+ this.watermarkUI = new WatermarkUI(this.targetElement, this.options);
161679
+ this.watermarkUI.create();
161680
+ // Help Panel (conditional)
161681
+ if ((_d = this.options) === null || _d === void 0 ? void 0 : _d.showHelpButton) {
161682
+ this.helpPanelUI = new HelpPanelUI(this.targetElement, this.options);
161683
+ this.helpPanelUI.create();
161684
+ }
161685
+ // Mute Button (conditional)
161686
+ if (((_f = (_e = this.options) === null || _e === void 0 ? void 0 : _e.audio) === null || _f === void 0 ? void 0 : _f.showMuteButton) && audioManager) {
161687
+ this.muteButtonUI = new MuteButtonUI(this.targetElement, this.options, audioManager);
161688
+ this.muteButtonUI.create();
161689
+ }
161690
+ }
161691
+ /**
161692
+ * Apply Minimal template layout (overlay controls)
161693
+ */
161694
+ async applyMinimalTemplate(navigationManager, cameraManager) {
161695
+ console.log('TemplateManager: Applying Minimal template');
161696
+ // Add minimal template class
161697
+ this.targetElement.classList.add('template-minimal');
161698
+ // Create minimal controls container (overlay style)
161699
+ if (this.options.showProgressBar && navigationManager) {
161700
+ this.createMinimalControls(navigationManager);
161701
+ }
161702
+ // Create mode toggle if multiple modes allowed
161703
+ if (this.shouldShowModeToggle() && cameraManager) {
161704
+ this.createMinimalModeToggle(cameraManager);
161705
+ }
161706
+ // Apply minimal template styles
161707
+ this.applyMinimalStyles();
161708
+ }
161709
+ /**
161710
+ * Apply Standard template layout (bottom navigation)
161711
+ */
161712
+ async applyStandardTemplate(navigationManager, cameraManager) {
161713
+ console.log('TemplateManager: Applying Standard template');
161714
+ // Add standard template class
161715
+ this.targetElement.classList.add('template-standard');
161716
+ // Create bottom navigation container
161717
+ if ((this.options.showProgressBar || this.options.showNavButtons) && navigationManager) {
161718
+ this.createStandardControls(navigationManager);
161719
+ }
161720
+ // Create mode toggle if multiple modes allowed
161721
+ if (this.shouldShowModeToggle() && cameraManager) {
161722
+ this.createStandardModeToggle(cameraManager);
161723
+ }
161724
+ // Apply standard template styles
161725
+ this.applyStandardStyles();
161726
+ }
161727
+ /**
161728
+ * Apply Pro template layout (enhanced bottom navigation)
161729
+ */
161730
+ async applyProTemplate(navigationManager, cameraManager) {
161731
+ console.log('TemplateManager: Applying Pro template');
161732
+ // Add pro template class
161733
+ this.targetElement.classList.add('template-pro');
161734
+ // Create waypoint info display at top
161735
+ if (navigationManager) {
161736
+ this.createProWaypointInfo();
161737
+ }
161738
+ // Create enhanced bottom navigation
161739
+ if ((this.options.showProgressBar || this.options.showNavButtons) && navigationManager) {
161740
+ this.createProControls(navigationManager);
161741
+ }
161742
+ // Create mode toggle on left side
161743
+ if (this.shouldShowModeToggle() && cameraManager) {
161744
+ this.createProModeToggle(cameraManager);
161745
+ }
161746
+ // Apply pro template styles
161747
+ this.applyProStyles();
161748
+ }
161749
+ /**
161750
+ * Helper to determine if mode toggle should be shown
161751
+ */
161752
+ shouldShowModeToggle() {
161753
+ const allowedModes = this.options.allowedCameraModes || [];
161754
+ return this.options.showCameraModeToggle !== false && allowedModes.length > 1;
161755
+ }
161756
+ /**
161757
+ * Create minimal overlay controls
161758
+ */
161759
+ createMinimalControls(navigationManager) {
161760
+ // Create overlay container
161761
+ this.controlsContainer = document.createElement('div');
161762
+ this.controlsContainer.id = 'scrollControls';
161763
+ this.controlsContainer.className = 'minimal-controls';
161764
+ // Progress bar
161765
+ if (this.options.showProgressBar) {
161766
+ const progressContainer = document.createElement('div');
161767
+ progressContainer.className = 'minimal-progress-container';
161768
+ const progressBar = document.createElement('div');
161769
+ progressBar.className = 'minimal-progress-bar';
161770
+ const progressFill = document.createElement('div');
161771
+ progressFill.className = 'minimal-progress-fill';
161772
+ progressFill.id = 'progressBar';
161773
+ progressBar.appendChild(progressFill);
161774
+ progressContainer.appendChild(progressBar);
161775
+ // Add percentage display
161776
+ const percentageDisplay = document.createElement('div');
161777
+ percentageDisplay.className = 'minimal-percentage';
161778
+ percentageDisplay.id = 'scrollPercentage';
161779
+ percentageDisplay.textContent = '0%';
161780
+ progressContainer.appendChild(percentageDisplay);
161781
+ this.controlsContainer.appendChild(progressContainer);
161782
+ }
161783
+ // Navigation buttons (inline with progress)
161784
+ if (this.options.showNavButtons) {
161785
+ const navContainer = document.createElement('div');
161786
+ navContainer.className = 'minimal-nav-buttons';
161787
+ const prevButton = this.createNavButton('prev', 'minimal');
161788
+ const nextButton = this.createNavButton('next', 'minimal');
161789
+ navContainer.appendChild(prevButton);
161790
+ navContainer.appendChild(nextButton);
161791
+ this.controlsContainer.appendChild(navContainer);
161792
+ }
161793
+ this.targetElement.appendChild(this.controlsContainer);
161794
+ // Connect to navigation manager
161795
+ this.connectNavigationControls(navigationManager);
161796
+ }
161797
+ /**
161798
+ * Create standard bottom controls
161799
+ */
161800
+ createStandardControls(navigationManager) {
161801
+ // Create bottom controls container
161802
+ this.controlsContainer = document.createElement('div');
161803
+ this.controlsContainer.id = 'scrollControls';
161804
+ this.controlsContainer.className = 'standard-controls';
161805
+ // Create controls content container
161806
+ const controlsContent = document.createElement('div');
161807
+ controlsContent.className = 'standard-controls-content';
161808
+ // Progress section
161809
+ const progressSection = document.createElement('div');
161810
+ progressSection.className = 'standard-progress-section';
161811
+ // Percentage display
161812
+ const percentageDisplay = document.createElement('div');
161813
+ percentageDisplay.id = 'scrollPercentage';
161814
+ percentageDisplay.className = 'standard-percentage';
161815
+ percentageDisplay.textContent = '0%';
161816
+ progressSection.appendChild(percentageDisplay);
161817
+ // Progress bar container
161818
+ const progressContainer = document.createElement('div');
161819
+ progressContainer.className = 'standard-progress-container';
161820
+ progressContainer.id = 'progressBarContainer';
161821
+ const progressBar = document.createElement('div');
161822
+ progressBar.className = 'standard-progress-bar';
161823
+ progressBar.id = 'progressBar';
161824
+ progressContainer.appendChild(progressBar);
161825
+ progressSection.appendChild(progressContainer);
161826
+ controlsContent.appendChild(progressSection);
161827
+ // Navigation buttons
161828
+ if (this.options.showNavButtons) {
161829
+ const buttonsContainer = document.createElement('div');
161830
+ buttonsContainer.className = 'standard-buttons-container';
161831
+ buttonsContainer.id = 'scrollButtons';
161832
+ const prevButton = this.createNavButton('prev', 'standard');
161833
+ const nextButton = this.createNavButton('next', 'standard');
161834
+ // Add autoplay button if enabled
161835
+ if (this.options.autoPlayEnabled) {
161836
+ const autoplayControls = document.createElement('div');
161837
+ autoplayControls.id = 'autoplayControls';
161838
+ autoplayControls.className = 'standard-autoplay';
161839
+ const playPauseButton = document.createElement('button');
161840
+ playPauseButton.id = 'playPauseButton';
161841
+ playPauseButton.className = 'standard-play-pause';
161842
+ playPauseButton.innerHTML = '<svg viewBox="0 0 24 24"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>';
161843
+ autoplayControls.appendChild(playPauseButton);
161844
+ buttonsContainer.appendChild(prevButton);
161845
+ buttonsContainer.appendChild(autoplayControls);
161846
+ buttonsContainer.appendChild(nextButton);
161847
+ }
161848
+ else {
161849
+ buttonsContainer.appendChild(prevButton);
161850
+ buttonsContainer.appendChild(nextButton);
161851
+ }
161852
+ controlsContent.appendChild(buttonsContainer);
161853
+ }
161854
+ this.controlsContainer.appendChild(controlsContent);
161855
+ this.targetElement.appendChild(this.controlsContainer);
161856
+ // Connect to navigation manager
161857
+ this.connectNavigationControls(navigationManager);
161858
+ }
161859
+ /**
161860
+ * Create pro enhanced controls
161861
+ */
161862
+ createProControls(navigationManager) {
161863
+ // Implementation based on pro template HTML
161864
+ // TODO: Implement based on pro template HTML
161865
+ }
161866
+ /**
161867
+ * Create minimal mode toggle
161868
+ */
161869
+ createMinimalModeToggle(cameraManager) {
161870
+ const allowedModes = this.options.allowedCameraModes || [];
161871
+ if (allowedModes.length <= 1)
161872
+ return;
161873
+ this.modeToggleContainer = document.createElement('div');
161874
+ this.modeToggleContainer.id = 'modeToggleContainer';
161875
+ this.modeToggleContainer.className = 'minimal-mode-toggle';
161876
+ // Create mode buttons
161877
+ if (allowedModes.includes('explore')) {
161878
+ const exploreButton = document.createElement('button');
161879
+ exploreButton.id = 'modeExplore';
161880
+ exploreButton.className = 'mode-button';
161881
+ exploreButton.textContent = 'Explore';
161882
+ exploreButton.addEventListener('click', () => cameraManager.setCameraMode('explore'));
161883
+ this.modeToggleContainer.appendChild(exploreButton);
161884
+ }
161885
+ if (allowedModes.includes('tour')) {
161886
+ const tourButton = document.createElement('button');
161887
+ tourButton.id = 'modeTour';
161888
+ tourButton.className = 'mode-button';
161889
+ tourButton.textContent = 'Tour';
161890
+ tourButton.addEventListener('click', () => cameraManager.setCameraMode('tour'));
161891
+ this.modeToggleContainer.appendChild(tourButton);
161892
+ }
161893
+ if (allowedModes.includes('hybrid')) {
161894
+ const hybridButton = document.createElement('button');
161895
+ hybridButton.id = 'modeHybrid';
161896
+ hybridButton.className = 'mode-button';
161897
+ hybridButton.textContent = 'Hybrid';
161898
+ hybridButton.addEventListener('click', () => cameraManager.setCameraMode('hybrid'));
161899
+ this.modeToggleContainer.appendChild(hybridButton);
161900
+ }
161901
+ if (allowedModes.includes('walk')) {
161902
+ const walkButton = document.createElement('button');
161903
+ walkButton.id = 'modeWalk';
161904
+ walkButton.className = 'mode-button';
161905
+ walkButton.textContent = 'Walk';
161906
+ walkButton.addEventListener('click', () => cameraManager.setCameraMode('walk'));
161907
+ this.modeToggleContainer.appendChild(walkButton);
161908
+ }
161909
+ this.targetElement.appendChild(this.modeToggleContainer);
161910
+ // Update button states based on current mode
161911
+ this.updateModeToggleState(cameraManager.currentMode);
161912
+ // Listen for mode changes
161913
+ cameraManager.onCameraModeChange = (mode) => {
161914
+ this.updateModeToggleState(mode);
161915
+ };
161916
+ }
161917
+ /**
161918
+ * Create standard mode toggle
161919
+ */
161920
+ createStandardModeToggle(cameraManager) {
161921
+ const allowedModes = this.options.allowedCameraModes || [];
161922
+ if (allowedModes.length <= 1)
161923
+ return;
161924
+ this.modeToggleContainer = document.createElement('div');
161925
+ this.modeToggleContainer.id = 'modeToggleContainer';
161926
+ this.modeToggleContainer.className = 'standard-mode-toggle';
161927
+ const toggleRow = document.createElement('div');
161928
+ toggleRow.className = 'standard-mode-toggle-row';
161929
+ // Create mode buttons
161930
+ if (allowedModes.includes('explore')) {
161931
+ const exploreButton = document.createElement('button');
161932
+ exploreButton.id = 'modeExplore';
161933
+ exploreButton.className = 'mode-button';
161934
+ exploreButton.textContent = 'Explore';
161935
+ exploreButton.addEventListener('click', () => cameraManager.setCameraMode('explore'));
161936
+ toggleRow.appendChild(exploreButton);
161937
+ }
161938
+ if (allowedModes.includes('tour')) {
161939
+ const tourButton = document.createElement('button');
161940
+ tourButton.id = 'modeTour';
161941
+ tourButton.className = 'mode-button';
161942
+ tourButton.textContent = 'Tour';
161943
+ tourButton.addEventListener('click', () => cameraManager.setCameraMode('tour'));
161944
+ toggleRow.appendChild(tourButton);
161945
+ }
161946
+ if (allowedModes.includes('hybrid')) {
161947
+ const hybridButton = document.createElement('button');
161948
+ hybridButton.id = 'modeHybrid';
161949
+ hybridButton.className = 'mode-button';
161950
+ hybridButton.textContent = 'Hybrid';
161951
+ hybridButton.addEventListener('click', () => cameraManager.setCameraMode('hybrid'));
161952
+ toggleRow.appendChild(hybridButton);
161953
+ }
161954
+ if (allowedModes.includes('walk')) {
161955
+ const walkButton = document.createElement('button');
161956
+ walkButton.id = 'modeWalk';
161957
+ walkButton.className = 'mode-button';
161958
+ walkButton.textContent = 'Walk';
161959
+ walkButton.addEventListener('click', () => cameraManager.setCameraMode('walk'));
161960
+ toggleRow.appendChild(walkButton);
161961
+ }
161962
+ this.modeToggleContainer.appendChild(toggleRow);
161963
+ this.targetElement.appendChild(this.modeToggleContainer);
161964
+ // Update button states based on current mode
161965
+ this.updateModeToggleState(cameraManager.currentMode);
161966
+ // Listen for mode changes
161967
+ cameraManager.onCameraModeChange = (mode) => {
161968
+ this.updateModeToggleState(mode);
161969
+ };
161970
+ }
161971
+ /**
161972
+ * Create pro mode toggle (left side)
161973
+ */
161974
+ createProModeToggle(cameraManager) {
161975
+ // Implementation for pro mode toggle
161976
+ // TODO: Implement mode toggle UI
161977
+ }
161978
+ /**
161979
+ * Create pro waypoint info display
161980
+ */
161981
+ createProWaypointInfo() {
161982
+ const waypointInfo = document.createElement('div');
161983
+ waypointInfo.id = 'waypointInfo';
161984
+ waypointInfo.className = 'pro-waypoint-info';
161985
+ this.targetElement.appendChild(waypointInfo);
161986
+ }
161987
+ /**
161988
+ * Helper to create navigation buttons
161989
+ */
161990
+ createNavButton(type, style) {
161991
+ const button = document.createElement('button');
161992
+ button.id = type === 'prev' ? 'prevButton' : 'nextButton';
161993
+ button.className = `${style}-nav-button ${type}`;
161994
+ button.innerHTML = type === 'prev' ? '←' : '→';
161995
+ button.title = type === 'prev' ? 'Previous' : 'Next';
161996
+ return button;
161997
+ }
161998
+ /**
161999
+ * Connect navigation controls to NavigationManager
162000
+ */
162001
+ connectNavigationControls(navigationManager) {
162002
+ // Connect progress updates
162003
+ if (this.options.showProgressBar) ;
162004
+ // Connect button clicks
162005
+ if (this.options.showNavButtons) {
162006
+ const prevButton = document.getElementById('prevButton');
162007
+ const nextButton = document.getElementById('nextButton');
162008
+ prevButton === null || prevButton === void 0 ? void 0 : prevButton.addEventListener('click', () => navigationManager.previousWaypoint());
162009
+ nextButton === null || nextButton === void 0 ? void 0 : nextButton.addEventListener('click', () => navigationManager.nextWaypoint());
162010
+ }
162011
+ }
162012
+ /**
162013
+ * Apply minimal template styles
162014
+ */
162015
+ applyMinimalStyles() {
162016
+ const style = document.createElement('style');
162017
+ style.textContent = `
162018
+ .template-minimal .minimal-controls {
162019
+ position: fixed;
162020
+ bottom: 20px;
162021
+ left: 50%;
162022
+ transform: translateX(-50%);
162023
+ display: flex;
162024
+ align-items: center;
162025
+ gap: 20px;
162026
+ z-index: 100;
162027
+ }
162028
+
162029
+ .template-minimal .minimal-progress-container {
162030
+ display: flex;
162031
+ align-items: center;
162032
+ gap: 10px;
162033
+ }
162034
+
162035
+ .template-minimal .minimal-progress-bar {
162036
+ width: 200px;
162037
+ height: 2px;
162038
+ background: rgba(255, 255, 255, 0.2);
162039
+ border-radius: 1px;
162040
+ overflow: hidden;
162041
+ }
162042
+
162043
+ .template-minimal .minimal-progress-fill {
162044
+ height: 100%;
162045
+ background: ${this.options.uiColor || '#4CAF50'};
162046
+ transition: width 0.3s ease;
162047
+ width: 0%;
162048
+ }
162049
+
162050
+ .template-minimal .minimal-percentage {
162051
+ color: white;
162052
+ font-size: 12px;
162053
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
162054
+ min-width: 35px;
162055
+ text-align: right;
162056
+ }
162057
+
162058
+ .template-minimal .minimal-nav-buttons {
162059
+ display: flex;
162060
+ gap: 10px;
162061
+ }
162062
+
162063
+ .template-minimal .minimal-nav-button {
162064
+ background: rgba(0, 0, 0, 0.5);
162065
+ border: none;
162066
+ color: white;
162067
+ width: 32px;
162068
+ height: 32px;
162069
+ border-radius: 50%;
162070
+ cursor: pointer;
162071
+ display: flex;
162072
+ align-items: center;
162073
+ justify-content: center;
162074
+ transition: background 0.2s ease;
162075
+ }
162076
+
162077
+ .template-minimal .minimal-nav-button:hover {
162078
+ background: rgba(0, 0, 0, 0.7);
162079
+ }
162080
+
162081
+ .template-minimal .minimal-mode-toggle {
162082
+ position: fixed;
162083
+ bottom: 70px;
162084
+ left: 50%;
162085
+ transform: translateX(-50%);
162086
+ display: flex;
162087
+ gap: 10px;
162088
+ z-index: 100;
162089
+ }
162090
+
162091
+ .template-minimal .mode-button {
162092
+ background: rgba(0, 0, 0, 0.5);
162093
+ border: none;
162094
+ color: white;
162095
+ padding: 6px 12px;
162096
+ border-radius: 4px;
162097
+ cursor: pointer;
162098
+ font-size: 12px;
162099
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
162100
+ transition: all 0.2s ease;
162101
+ min-width: 60px;
162102
+ }
162103
+
162104
+ .template-minimal .mode-button:hover {
162105
+ background: rgba(0, 0, 0, 0.7);
162106
+ }
162107
+
162108
+ .template-minimal .mode-button.selected {
162109
+ background: ${this.options.uiColor || '#4CAF50'};
162110
+ }
162111
+
162112
+ @media (max-width: 768px) {
162113
+ .template-minimal .minimal-controls {
162114
+ bottom: 10px;
162115
+ gap: 15px;
162116
+ }
162117
+
162118
+ .template-minimal .minimal-mode-toggle {
162119
+ bottom: 50px;
162120
+ }
162121
+
162122
+ .template-minimal .mode-button {
162123
+ padding: 5px 10px;
162124
+ font-size: 11px;
162125
+ min-width: 50px;
162126
+ }
162127
+ }
162128
+ `;
162129
+ document.head.appendChild(style);
162130
+ }
162131
+ /**
162132
+ * Apply standard template styles
162133
+ */
162134
+ applyStandardStyles() {
162135
+ const style = document.createElement('style');
162136
+ style.textContent = `
162137
+ .template-standard .standard-controls {
162138
+ position: fixed;
162139
+ bottom: 0;
162140
+ left: 0;
162141
+ right: 0;
162142
+ background: rgba(0, 0, 0, 0.8);
162143
+ backdrop-filter: blur(10px);
162144
+ padding: 15px 20px;
162145
+ z-index: 100;
162146
+ display: flex;
162147
+ justify-content: center;
162148
+ }
162149
+
162150
+ .template-standard .standard-controls-content {
162151
+ width: 100%;
162152
+ max-width: 800px;
162153
+ display: flex;
162154
+ flex-direction: column;
162155
+ gap: 10px;
162156
+ }
162157
+
162158
+ .template-standard .standard-progress-section {
162159
+ display: flex;
162160
+ flex-direction: column;
162161
+ align-items: center;
162162
+ gap: 5px;
162163
+ }
162164
+
162165
+ .template-standard .standard-percentage {
162166
+ color: white;
162167
+ font-size: 14px;
162168
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
162169
+ }
162170
+
162171
+ .template-standard .standard-progress-container {
162172
+ width: 100%;
162173
+ height: 4px;
162174
+ background: rgba(255, 255, 255, 0.2);
162175
+ border-radius: 2px;
162176
+ overflow: hidden;
162177
+ }
162178
+
162179
+ .template-standard .standard-progress-bar {
162180
+ height: 100%;
162181
+ background: ${this.options.uiColor || '#4CAF50'};
162182
+ transition: width 0.3s ease;
162183
+ width: 0%;
162184
+ }
162185
+
162186
+ .template-standard .standard-buttons-container {
162187
+ display: flex;
162188
+ justify-content: center;
162189
+ align-items: center;
162190
+ gap: 15px;
162191
+ margin-top: 5px;
162192
+ }
162193
+
162194
+ .template-standard .standard-nav-button {
162195
+ background: rgba(255, 255, 255, 0.1);
162196
+ border: 1px solid rgba(255, 255, 255, 0.2);
162197
+ color: white;
162198
+ padding: 8px 20px;
162199
+ border-radius: 20px;
162200
+ cursor: pointer;
162201
+ display: flex;
162202
+ align-items: center;
162203
+ gap: 8px;
162204
+ font-size: 14px;
162205
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
162206
+ transition: all 0.2s ease;
162207
+ }
162208
+
162209
+ .template-standard .standard-nav-button:hover {
162210
+ background: rgba(255, 255, 255, 0.2);
162211
+ transform: translateY(-1px);
162212
+ }
162213
+
162214
+ .template-standard .standard-autoplay {
162215
+ display: flex;
162216
+ align-items: center;
162217
+ }
162218
+
162219
+ .template-standard .standard-play-pause {
162220
+ background: rgba(255, 255, 255, 0.1);
162221
+ border: 1px solid rgba(255, 255, 255, 0.2);
162222
+ color: white;
162223
+ width: 40px;
162224
+ height: 40px;
162225
+ border-radius: 50%;
162226
+ cursor: pointer;
162227
+ display: flex;
162228
+ align-items: center;
162229
+ justify-content: center;
162230
+ transition: all 0.2s ease;
162231
+ }
162232
+
162233
+ .template-standard .standard-play-pause:hover {
162234
+ background: rgba(255, 255, 255, 0.2);
162235
+ }
162236
+
162237
+ .template-standard .standard-play-pause svg {
162238
+ width: 16px;
162239
+ height: 16px;
162240
+ fill: currentColor;
162241
+ }
162242
+
162243
+ .template-standard .standard-mode-toggle {
162244
+ position: fixed;
162245
+ top: 20px;
162246
+ right: 20px;
162247
+ z-index: 100;
162248
+ }
162249
+
162250
+ .template-standard .standard-mode-toggle-row {
162251
+ display: flex;
162252
+ gap: 5px;
162253
+ background: rgba(0, 0, 0, 0.8);
162254
+ backdrop-filter: blur(10px);
162255
+ padding: 5px;
162256
+ border-radius: 8px;
162257
+ }
162258
+
162259
+ .template-standard .mode-button {
162260
+ background: rgba(255, 255, 255, 0.1);
162261
+ border: 1px solid rgba(255, 255, 255, 0.2);
162262
+ color: white;
162263
+ padding: 8px 16px;
162264
+ border-radius: 4px;
162265
+ cursor: pointer;
162266
+ font-size: 13px;
162267
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
162268
+ transition: all 0.2s ease;
162269
+ min-width: 70px;
162270
+ }
162271
+
162272
+ .template-standard .mode-button:hover {
162273
+ background: rgba(255, 255, 255, 0.2);
162274
+ }
162275
+
162276
+ .template-standard .mode-button.selected {
162277
+ background: ${this.options.uiColor || '#4CAF50'};
162278
+ border-color: ${this.options.uiColor || '#4CAF50'};
162279
+ }
162280
+
162281
+ @media (max-width: 768px) {
162282
+ .template-standard .standard-controls {
162283
+ padding: 10px 15px;
162284
+ }
162285
+
162286
+ .template-standard .standard-nav-button {
162287
+ padding: 6px 15px;
162288
+ font-size: 13px;
162289
+ }
162290
+
162291
+ .template-standard .standard-mode-toggle {
162292
+ top: 10px;
162293
+ right: 10px;
162294
+ }
162295
+
162296
+ .template-standard .mode-button {
162297
+ padding: 6px 12px;
162298
+ font-size: 12px;
162299
+ min-width: 60px;
162300
+ }
162301
+ }
162302
+ `;
162303
+ document.head.appendChild(style);
162304
+ }
162305
+ /**
162306
+ * Apply pro template styles
162307
+ */
162308
+ applyProStyles() {
162309
+ // TODO: Add pro template CSS
162310
+ }
162311
+ /**
162312
+ * Update progress display
162313
+ */
162314
+ updateProgress(percentage) {
162315
+ const progressBar = document.getElementById('progressBar');
162316
+ const percentageDisplay = document.getElementById('scrollPercentage');
162317
+ if (progressBar) {
162318
+ progressBar.style.width = `${percentage}%`;
162319
+ }
162320
+ if (percentageDisplay) {
162321
+ percentageDisplay.textContent = `${Math.round(percentage)}%`;
162322
+ }
162323
+ }
162324
+ /**
162325
+ * Get preloader UI instance (for direct access if needed)
162326
+ */
162327
+ getPreloaderUI() {
162328
+ return this.preloaderUI;
162329
+ }
162330
+ /**
162331
+ * Show/hide preloader
162332
+ */
162333
+ showPreloader() {
162334
+ var _a;
162335
+ (_a = this.preloaderUI) === null || _a === void 0 ? void 0 : _a.show();
162336
+ }
162337
+ hidePreloader() {
162338
+ var _a;
162339
+ (_a = this.preloaderUI) === null || _a === void 0 ? void 0 : _a.hide();
162340
+ }
162341
+ /**
162342
+ * Update preloader progress
162343
+ */
162344
+ updatePreloaderProgress(percentage, text) {
162345
+ var _a;
162346
+ (_a = this.preloaderUI) === null || _a === void 0 ? void 0 : _a.updateProgress(percentage, text);
162347
+ }
162348
+ /**
162349
+ * Update mode toggle button states
162350
+ */
162351
+ updateModeToggleState(currentMode) {
162352
+ var _a;
162353
+ const buttons = (_a = this.modeToggleContainer) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.mode-button');
162354
+ buttons === null || buttons === void 0 ? void 0 : buttons.forEach(button => {
162355
+ const mode = button.id.replace('mode', '').toLowerCase();
162356
+ button.classList.toggle('selected', mode === currentMode);
162357
+ });
162358
+ }
162359
+ /**
162360
+ * Clean up and dispose
162361
+ */
162362
+ dispose() {
162363
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
162364
+ // Dispose UI components
162365
+ (_a = this.preloaderUI) === null || _a === void 0 ? void 0 : _a.dispose();
162366
+ (_b = this.startButtonUI) === null || _b === void 0 ? void 0 : _b.dispose();
162367
+ (_c = this.watermarkUI) === null || _c === void 0 ? void 0 : _c.dispose();
162368
+ (_d = this.helpPanelUI) === null || _d === void 0 ? void 0 : _d.dispose();
162369
+ (_e = this.muteButtonUI) === null || _e === void 0 ? void 0 : _e.dispose();
162370
+ // Remove template classes
162371
+ this.targetElement.classList.remove('template-minimal', 'template-standard', 'template-pro');
162372
+ // Remove created elements
162373
+ (_f = this.controlsContainer) === null || _f === void 0 ? void 0 : _f.remove();
162374
+ (_g = this.modeToggleContainer) === null || _g === void 0 ? void 0 : _g.remove();
162375
+ (_h = this.progressContainer) === null || _h === void 0 ? void 0 : _h.remove();
162376
+ (_j = this.navigationContainer) === null || _j === void 0 ? void 0 : _j.remove();
162377
+ }
162378
+ }
162379
+
161603
162380
  /******************************************************************************
161604
162381
  Copyright (c) Microsoft Corporation.
161605
162382
 
@@ -161913,7 +162690,7 @@ function createViewerOptions(rawData, userOptions) {
161913
162690
  }
161914
162691
  async function initializeViewer(targetElement, rawData, // Accept any JSON data
161915
162692
  options) {
161916
- var _a, _b, _c, _d, _e;
162693
+ var _a, _b, _c, _d;
161917
162694
  // Register BabylonJS loaders at runtime
161918
162695
  try {
161919
162696
  registerBuiltInLoaders();
@@ -161980,11 +162757,7 @@ options) {
161980
162757
  let navigationManager = null;
161981
162758
  let collisionManager = null;
161982
162759
  // let uiManager: UIManager | null = null; // REMOVED: Old monolithic UI Manager
161983
- let preloaderUI = null;
161984
- let startButtonUI = null;
161985
- let watermarkUI = null;
161986
- let helpPanelUI = null;
161987
- let muteButtonUI = null; // Add reference for MuteButtonUI
162760
+ let templateManager = null; // NEW: Template-based UI system
161988
162761
  let audioManager = null;
161989
162762
  let renderLoopManager = null; // Add RenderLoopManager reference
161990
162763
  let xrExperience = null; // Add XR experience reference
@@ -162018,13 +162791,9 @@ options) {
162018
162791
  splatSwapManager = null;
162019
162792
  audioManager === null || audioManager === void 0 ? void 0 : audioManager.dispose();
162020
162793
  audioManager = null;
162021
- // Dispose new UI modules
162022
- preloaderUI === null || preloaderUI === void 0 ? void 0 : preloaderUI.dispose();
162023
- startButtonUI === null || startButtonUI === void 0 ? void 0 : startButtonUI.dispose();
162024
- watermarkUI === null || watermarkUI === void 0 ? void 0 : watermarkUI.dispose();
162025
- helpPanelUI === null || helpPanelUI === void 0 ? void 0 : helpPanelUI.dispose();
162026
- muteButtonUI === null || muteButtonUI === void 0 ? void 0 : muteButtonUI.dispose(); // Dispose mute button UI
162027
- preloaderUI = startButtonUI = watermarkUI = helpPanelUI = muteButtonUI = null;
162794
+ // Dispose template manager (handles all UI components)
162795
+ templateManager === null || templateManager === void 0 ? void 0 : templateManager.dispose();
162796
+ templateManager = null;
162028
162797
  // NavigationManager and CameraManager disposal is handled by scene dispose or specific logic within them
162029
162798
  // Dispose features managed outside scene graph
162030
162799
  if (scene && finalViewerOptions) {
@@ -162176,44 +162945,23 @@ options) {
162176
162945
  splatSwapManager === null || splatSwapManager === void 0 ? void 0 : splatSwapManager.updateProgress(progress, waypointIndex);
162177
162946
  };
162178
162947
  }
162179
- // --- 9. Initialize Modular UI Components ---
162180
- // Initialize AudioManager first (needed for start button callback)
162181
- // Note: AudioManager is initialized later now, before render loop manager.
162182
- // We'll pass the instance later if needed, or adjust initialization order.
162183
- // Preloader (always show by default, like HTML exports)
162184
- const defaultPreloaderConfig = {
162185
- enabled: true,
162186
- imageUrl: 'https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fimages%2FStorySplat.webp?alt=media&token=953e8ab3-1865-4ac1-a98d-b548b7066bda',
162187
- lottieUrl: 'https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Flotties%2FstorySplatLottie.json?alt=media&token=d7edc19d-9cb8-4c6e-a94c-cba1d2b65d5e',
162188
- uiColor: (finalViewerOptions === null || finalViewerOptions === void 0 ? void 0 : finalViewerOptions.uiColor) || '#4CAF50'
162189
- };
162190
- const preloaderConfig = Object.assign(Object.assign({}, defaultPreloaderConfig), finalViewerOptions === null || finalViewerOptions === void 0 ? void 0 : finalViewerOptions.preloaderConfig);
162191
- preloaderUI = new PreloaderUI(targetElement, preloaderConfig);
162192
- preloaderUI.create();
162193
- // Start Button
162194
- if (finalViewerOptions === null || finalViewerOptions === void 0 ? void 0 : finalViewerOptions.showStartExperience) {
162195
- startButtonUI = new StartButtonUI(targetElement, finalViewerOptions, () => {
162196
- // Callback to initialize audio context on first interaction
162197
- audioManager === null || audioManager === void 0 ? void 0 : audioManager.initialize(); // Use the correct initialize method
162198
- });
162199
- startButtonUI.create();
162200
- }
162201
- // Watermark
162202
- watermarkUI = new WatermarkUI(targetElement, finalViewerOptions);
162203
- watermarkUI.create(); // Creates conditionally based on options inside the class
162204
- // Help Panel
162205
- helpPanelUI = new HelpPanelUI(targetElement, finalViewerOptions);
162206
- helpPanelUI.create(); // Creates conditionally based on options inside the class
162207
- // Mute Button (Needs AudioManager instance)
162208
- // We'll initialize this after AudioManager is created
162948
+ // --- 9. Initialize Template Manager ---
162949
+ // The template manager handles all UI components based on the uiType
162950
+ templateManager = new TemplateManager(targetElement, finalViewerOptions);
162951
+ // Initialize the template UI (but don't fully set up navigation controls yet)
162952
+ // We'll do that after navigation manager is created
162953
+ await templateManager.initializeUI(null, null, null, () => {
162954
+ // Callback to initialize audio context on first interaction
162955
+ audioManager === null || audioManager === void 0 ? void 0 : audioManager.initialize();
162956
+ });
162209
162957
  // --- 10. Load Splat Data ---
162210
162958
  console.log("StorySplat Viewer: Starting splat asset load...");
162211
162959
  splatRootMeshes = await loadSplatAsset(scene, data, (percentage, text) => {
162212
- // Update preloader UI via PreloaderUI instance
162213
- preloaderUI === null || preloaderUI === void 0 ? void 0 : preloaderUI.updateProgress(percentage, text);
162960
+ // Update preloader UI via template manager
162961
+ templateManager === null || templateManager === void 0 ? void 0 : templateManager.updatePreloaderProgress(percentage, text);
162214
162962
  });
162215
162963
  console.log("StorySplat Viewer: Splat asset loaded.");
162216
- preloaderUI === null || preloaderUI === void 0 ? void 0 : preloaderUI.hide(); // Hide preloader after splat is loaded - Check if preloaderUI is null?
162964
+ templateManager === null || templateManager === void 0 ? void 0 : templateManager.hidePreloader(); // Hide preloader after splat is loaded
162217
162965
  // --- 11. Initialize/Setup Features ---
162218
162966
  // Setup Particle Systems (sync)
162219
162967
  // Ensure setupParticleSystems is correctly imported and called
@@ -162251,11 +162999,9 @@ options) {
162251
162999
  if (!analyticsManager)
162252
163000
  throw new Error("AnalyticsManager not initialized before AudioManager"); // Guard
162253
163001
  audioManager = new AudioManager(scene, finalViewerOptions, analyticsManager);
162254
- // Now initialize Mute Button UI, passing the audioManager instance
162255
- if ((_e = finalViewerOptions === null || finalViewerOptions === void 0 ? void 0 : finalViewerOptions.audio) === null || _e === void 0 ? void 0 : _e.showMuteButton) {
162256
- muteButtonUI = new MuteButtonUI(targetElement, finalViewerOptions, audioManager);
162257
- muteButtonUI.create();
162258
- }
163002
+ // Re-initialize template UI with audio manager now that it's available
163003
+ // This will create the mute button if needed
163004
+ await templateManager.initializeUI(audioManager, navigationManager, cameraManager);
162259
163005
  // --- 12. Initialize Render Loop Manager ---
162260
163006
  // Needs engine, scene, cameraManager, navigationManager
162261
163007
  if (engine && scene && cameraManager) { // Ensure core components exist
@@ -162331,8 +163077,8 @@ options) {
162331
163077
  console.log(`StorySplat Viewer: Starting splat swap to ${newSplatUrl}`);
162332
163078
  analyticsManager === null || analyticsManager === void 0 ? void 0 : analyticsManager.trackSplatSwapStarted(newSplatUrl); // Corrected method call
162333
163079
  // Show preloader
162334
- preloaderUI === null || preloaderUI === void 0 ? void 0 : preloaderUI.show(); // Corrected method call (added in preloader.ts)
162335
- preloaderUI === null || preloaderUI === void 0 ? void 0 : preloaderUI.updateProgress(0, 'Preparing to load new splat...'); // Initial message
163080
+ templateManager === null || templateManager === void 0 ? void 0 : templateManager.showPreloader();
163081
+ templateManager === null || templateManager === void 0 ? void 0 : templateManager.updatePreloaderProgress(0, 'Preparing to load new splat...'); // Initial message
162336
163082
  // Dispose existing splat meshes
162337
163083
  console.log("StorySplat Viewer: Disposing current splat meshes...");
162338
163084
  splatRootMeshes.forEach(mesh => {
@@ -162355,7 +163101,7 @@ options) {
162355
163101
  // Ensure other potentially relevant fields from data are present if needed
162356
163102
  waypoints: data.waypoints, hotspots: data.hotspots });
162357
163103
  splatRootMeshes = await loadSplatAsset(scene, tempData, (percentage, text) => {
162358
- preloaderUI === null || preloaderUI === void 0 ? void 0 : preloaderUI.updateProgress(percentage, text);
163104
+ templateManager === null || templateManager === void 0 ? void 0 : templateManager.updatePreloaderProgress(percentage, text);
162359
163105
  });
162360
163106
  currentSplatUrl = newSplatUrl; // Update the tracked URL
162361
163107
  console.log("StorySplat Viewer: New splat asset loaded successfully.");
@@ -162377,12 +163123,12 @@ options) {
162377
163123
  console.error(`StorySplat Viewer: Failed to load new splat asset from ${newSplatUrl}:`, error);
162378
163124
  analyticsManager === null || analyticsManager === void 0 ? void 0 : analyticsManager.trackSplatSwapFailed(error, newSplatUrl); // Corrected method call
162379
163125
  // Hide preloader even on error
162380
- preloaderUI === null || preloaderUI === void 0 ? void 0 : preloaderUI.hide();
163126
+ templateManager === null || templateManager === void 0 ? void 0 : templateManager.hidePreloader();
162381
163127
  // Re-throw error to signal failure to the caller
162382
163128
  throw error;
162383
163129
  }
162384
163130
  // Hide preloader on success
162385
- preloaderUI === null || preloaderUI === void 0 ? void 0 : preloaderUI.hide();
163131
+ templateManager === null || templateManager === void 0 ? void 0 : templateManager.hidePreloader();
162386
163132
  console.log("StorySplat Viewer: Splat swap complete.");
162387
163133
  };
162388
163134
  /**