storysplat-viewer 0.1.12 → 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
@@ -41199,67 +41199,6 @@ function registerBuiltInLoaders() {
41199
41199
  });
41200
41200
  }
41201
41201
 
41202
- var name$3D = "storysplat-viewer";
41203
- var version = "0.1.12";
41204
- var description = "A viewer component for StorySplat scenes.";
41205
- var main = "dist/esm/index.js";
41206
- var module = "dist/esm/index.js";
41207
- var types = "dist/types/index.d.ts";
41208
- var scripts = {
41209
- "build:types": "tsc --emitDeclarationOnly --declaration --declarationDir dist/types --project tsconfig.json",
41210
- build: "npm run build:types && rollup -c",
41211
- "build:umd": "rollup -c rollup.config.umd.js",
41212
- "build:simple": "rollup -c rollup.config.simple.js",
41213
- dev: "rollup -c -w",
41214
- "test:serve": "node test/server.js",
41215
- "test:build-watch": "npm run build && concurrently \"npm run dev\" \"npm run test:serve\"",
41216
- test: "npm run build && npm run test:serve",
41217
- "test:python": "npm run build && python3 test/simple-server.py",
41218
- "test:open": "npm run build && open http://localhost:8080/test/index.html && npm run test:serve"
41219
- };
41220
- var devDependencies = {
41221
- "@babylonjs/core": "^8.4.1",
41222
- "@babylonjs/loaders": "^8.4.1",
41223
- "@rollup/plugin-commonjs": "^25.0.0",
41224
- "@rollup/plugin-json": "^6.1.0",
41225
- "@rollup/plugin-node-resolve": "^15.0.0",
41226
- "@rollup/plugin-typescript": "^11.0.0",
41227
- concurrently: "^8.0.0",
41228
- rollup: "^4.0.0",
41229
- "rollup-plugin-postcss": "^4.0.2",
41230
- tslib: "^2.6.0",
41231
- typescript: "^5.0.0"
41232
- };
41233
- var files = [
41234
- "dist/esm",
41235
- "dist/types",
41236
- "dist/umd"
41237
- ];
41238
- var publishConfig = {
41239
- access: "public"
41240
- };
41241
- var dependencies = {
41242
- "@babylonjs/loaders": "^8.4.1",
41243
- "gifuct-js": "^2.1.2"
41244
- };
41245
- var peerDependencies = {
41246
- "@babylonjs/core": "^8.4.1"
41247
- };
41248
- var packageJson = {
41249
- name: name$3D,
41250
- version: version,
41251
- description: description,
41252
- main: main,
41253
- module: module,
41254
- types: types,
41255
- scripts: scripts,
41256
- devDependencies: devDependencies,
41257
- files: files,
41258
- publishConfig: publishConfig,
41259
- dependencies: dependencies,
41260
- peerDependencies: peerDependencies
41261
- };
41262
-
41263
41202
  /**
41264
41203
  * Detect if the effect is a DrawWrapper
41265
41204
  * @param effect defines the entity to test
@@ -75073,7 +75012,7 @@ class CameraManager {
75073
75012
  }
75074
75013
  }
75075
75014
  setCameraMode(mode, immediate = false) {
75076
- var _a, _b, _c, _d, _e, _f;
75015
+ var _a, _b, _c, _d, _e, _f, _g;
75077
75016
  if (!this._camera)
75078
75017
  return;
75079
75018
  const allowedModes = (_b = (_a = this._viewerOptions) === null || _a === void 0 ? void 0 : _a.allowedCameraModes) !== null && _b !== void 0 ? _b : ['explore', 'walk', 'tour', 'hybrid'];
@@ -75123,7 +75062,8 @@ class CameraManager {
75123
75062
  // Trigger callback and update UI
75124
75063
  if (oldMode !== mode) {
75125
75064
  (_e = (_d = this._viewerOptions) === null || _d === void 0 ? void 0 : _d.onCameraModeChanged) === null || _e === void 0 ? void 0 : _e.call(_d, mode);
75126
- (_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
75127
75067
  this._updateUIManager(); // Notify UI Manager of the change
75128
75068
  }
75129
75069
  }
@@ -108302,6 +108242,7 @@ class NavigationManager {
108302
108242
  this.waypointInfoElement = null; // Waypoint info popup (like HTML exports)
108303
108243
  this.prevButton = null;
108304
108244
  this.nextButton = null;
108245
+ this.playPauseButton = null;
108305
108246
  this.cameraModeToggleContainer = null; // Container for general mode toggles
108306
108247
  this.walkExploreToggleContainer = null; // Container for Walk/Explore toggle
108307
108248
  this.mobileJoystickContainer = null; // Container for joystick UI
@@ -108403,7 +108344,7 @@ class NavigationManager {
108403
108344
  this.onProgressUpdate(scrollPercentage / 100, currentWaypointIndex);
108404
108345
  }
108405
108346
  // --- Tour/Hybrid Mode Logic ---
108406
- if ((this.currentMode === 'hybrid' && !this.userControl) || (this.currentMode === 'tour' && !this.userControl)) {
108347
+ if ((this.currentMode === 'hybrid') || (this.currentMode === 'tour' && !this.userControl)) {
108407
108348
  this.updateCameraPositionFromPath();
108408
108349
  }
108409
108350
  // Update UI for tour/hybrid modes
@@ -108413,8 +108354,8 @@ class NavigationManager {
108413
108354
  }
108414
108355
  // Check waypoint triggers
108415
108356
  this.updateActiveWaypoint();
108416
- // --- Explore/Walk Mode Logic ---
108417
- 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)) {
108418
108359
  const deltaTime = this.scene.getEngine().getDeltaTime() / 1000.0;
108419
108360
  this.updateExploreWalkMovement(deltaTime);
108420
108361
  }
@@ -108487,6 +108428,15 @@ class NavigationManager {
108487
108428
  const logicalDefault = this.waypoints.length > 0 ? 'tour' : 'explore';
108488
108429
  const preferredDefault = dataInitialMode || optionsDefaultMode || logicalDefault;
108489
108430
  this.currentMode = this.allowedModes.includes(preferredDefault) ? preferredDefault : this.allowedModes[0];
108431
+ console.log('DEBUG NavigationManager initial mode setting:', {
108432
+ dataInitialMode,
108433
+ optionsDefaultMode,
108434
+ logicalDefault,
108435
+ preferredDefault,
108436
+ allowedModes: this.allowedModes,
108437
+ finalCurrentMode: this.currentMode,
108438
+ waypointCount: this.waypoints.length
108439
+ });
108490
108440
  // Set navigation parameters from options or use defaults
108491
108441
  this.transitionSpeed = (_a = this.options.transitionSpeed) !== null && _a !== void 0 ? _a : 1.5;
108492
108442
  this.cameraDampening = (_b = this.options.cameraDampening) !== null && _b !== void 0 ? _b : 0.1;
@@ -108525,7 +108475,14 @@ class NavigationManager {
108525
108475
  this.targetRotation.copyFrom(this.camera.rotationQuaternion);
108526
108476
  }
108527
108477
  }
108528
- this.setupUI(); // Setup UI based on options
108478
+ console.log('DEBUG NavigationManager: About to call setupUI...');
108479
+ try {
108480
+ this.setupUI(); // Setup UI based on options
108481
+ console.log('DEBUG NavigationManager: setupUI completed successfully');
108482
+ }
108483
+ catch (error) {
108484
+ console.error('DEBUG NavigationManager: Error in setupUI:', error);
108485
+ }
108529
108486
  // Event handlers (scroll, keyboard, pointer) are attached/detached in setMode
108530
108487
  // Start autoplay if enabled and applicable
108531
108488
  if (this.autoPlayEnabled && this.waypoints.length > 0 && (this.currentMode === 'tour' || this.currentMode === 'hybrid')) {
@@ -108891,7 +108848,7 @@ class NavigationManager {
108891
108848
  var _a, _b;
108892
108849
  // Clear existing UI first
108893
108850
  this.removeUI();
108894
- console.log('NavigationManager: Setting up UI with:', {
108851
+ console.log('DEBUG NavigationManager: Setting up UI with:', {
108895
108852
  waypoints: this.waypoints.length,
108896
108853
  allowedModes: this.allowedModes,
108897
108854
  currentMode: this.currentMode,
@@ -108900,10 +108857,11 @@ class NavigationManager {
108900
108857
  showCameraModeToggle: this.options.showCameraModeToggle,
108901
108858
  showWalkExploreToggle: this.options.showWalkExploreToggle,
108902
108859
  uiContainer: this.uiContainer ? 'found' : 'NOT FOUND',
108903
- uiType: ((_a = this.options.uiOptions) === null || _a === void 0 ? void 0 : _a.uiType) || 'minimal'
108860
+ uiType: ((_a = this.options.uiOptions) === null || _a === void 0 ? void 0 : _a.uiType) || 'standard',
108861
+ fullOptions: this.options
108904
108862
  });
108905
108863
  // Determine template type from options
108906
- const uiType = ((_b = this.options.uiOptions) === null || _b === void 0 ? void 0 : _b.uiType) || 'minimal';
108864
+ const uiType = ((_b = this.options.uiOptions) === null || _b === void 0 ? void 0 : _b.uiType) || 'standard';
108907
108865
  // Setup UI based on template type
108908
108866
  switch (uiType) {
108909
108867
  case 'pro':
@@ -108994,6 +108952,30 @@ class NavigationManager {
108994
108952
  this.prevButton.style.transition = 'all 0.2s ease';
108995
108953
  this.prevButton.addEventListener('click', this.previousWaypoint);
108996
108954
  buttonContainer.appendChild(this.prevButton);
108955
+ // Play/Pause button (only if autoplay is enabled)
108956
+ if (this.autoPlayEnabled) {
108957
+ this.playPauseButton = document.createElement('button');
108958
+ this.playPauseButton.className = 'storysplat-nav-button storysplat-play-pause-button';
108959
+ this.playPauseButton.innerHTML = this.isPlaying ? '⏸️' : '▶️';
108960
+ this.playPauseButton.style.background = 'rgba(0, 0, 0, 0.3)';
108961
+ this.playPauseButton.style.border = 'none';
108962
+ this.playPauseButton.style.color = 'white';
108963
+ this.playPauseButton.style.padding = '5px 10px';
108964
+ this.playPauseButton.style.fontSize = '14px';
108965
+ this.playPauseButton.style.borderRadius = '2px';
108966
+ this.playPauseButton.style.cursor = 'pointer';
108967
+ this.playPauseButton.style.transition = 'all 0.2s ease';
108968
+ this.playPauseButton.addEventListener('click', () => {
108969
+ if (this.isPlaying) {
108970
+ this.pause();
108971
+ }
108972
+ else {
108973
+ this.play();
108974
+ }
108975
+ this.updatePlayPauseButton();
108976
+ });
108977
+ buttonContainer.appendChild(this.playPauseButton);
108978
+ }
108997
108979
  this.nextButton = document.createElement('button');
108998
108980
  this.nextButton.className = 'storysplat-nav-button storysplat-next-button';
108999
108981
  this.nextButton.innerHTML = '→';
@@ -109017,24 +108999,8 @@ class NavigationManager {
109017
108999
  this.setupCommonUI(); // Add common UI elements (walk/explore toggles, etc.)
109018
109000
  }
109019
109001
  setupStandardTemplateUI() {
109020
- console.log('NavigationManager: Setting up STANDARD template UI');
109021
- // Standard template is similar to minimal but has waypoint info in scroll controls
109022
- // and supports inline/below button positioning
109023
- // --- Waypoint Info (Top of screen, like HTML exports) ---
109024
- this.waypointInfoElement = document.createElement('div');
109025
- this.waypointInfoElement.className = 'storysplat-waypoint-info';
109026
- this.waypointInfoElement.style.position = 'absolute';
109027
- this.waypointInfoElement.style.top = '0';
109028
- this.waypointInfoElement.style.left = '0';
109029
- this.waypointInfoElement.style.right = '0';
109030
- this.waypointInfoElement.style.background = 'rgba(0, 0, 0, 0.5)';
109031
- this.waypointInfoElement.style.color = 'white';
109032
- this.waypointInfoElement.style.textAlign = 'center';
109033
- this.waypointInfoElement.style.zIndex = '1000';
109034
- this.waypointInfoElement.style.pointerEvents = 'none';
109035
- this.waypointInfoElement.style.display = 'none';
109036
- this.waypointInfoElement.style.fontFamily = 'system-ui, -apple-system, sans-serif';
109037
- this.uiContainer.appendChild(this.waypointInfoElement);
109002
+ console.log('DEBUG NavigationManager: Setting up STANDARD template UI');
109003
+ // Standard template has waypoint info integrated into the navigator (not at top of screen)
109038
109004
  // --- Scroll Controls Container (Bottom center, like standard template) ---
109039
109005
  if (this.waypoints.length > 1) {
109040
109006
  this.scrollControlsContainer = document.createElement('div');
@@ -109053,6 +109019,19 @@ class NavigationManager {
109053
109019
  this.scrollControlsContainer.style.zIndex = '1000';
109054
109020
  this.scrollControlsContainer.style.background = 'rgba(0, 0, 0, 0.4)';
109055
109021
  this.scrollControlsContainer.style.borderRadius = '8px';
109022
+ // --- Waypoint Info (Inside navigator for standard template) ---
109023
+ this.waypointInfoElement = document.createElement('div');
109024
+ this.waypointInfoElement.className = 'storysplat-waypoint-info';
109025
+ this.waypointInfoElement.style.color = 'white';
109026
+ this.waypointInfoElement.style.textAlign = 'center';
109027
+ this.waypointInfoElement.style.pointerEvents = 'none';
109028
+ this.waypointInfoElement.style.display = 'none';
109029
+ this.waypointInfoElement.style.fontFamily = 'system-ui, -apple-system, sans-serif';
109030
+ this.waypointInfoElement.style.fontSize = '14px';
109031
+ this.waypointInfoElement.style.marginBottom = '5px';
109032
+ this.waypointInfoElement.style.maxWidth = '500px';
109033
+ this.waypointInfoElement.style.lineHeight = '1.4';
109034
+ this.scrollControlsContainer.appendChild(this.waypointInfoElement);
109056
109035
  // Scroll percentage display (larger for standard) - ensure no duplicates exist
109057
109036
  if (!this.uiContainer.querySelector('.storysplat-scroll-percentage')) {
109058
109037
  this.scrollPercentageElement = document.createElement('div');
@@ -109099,6 +109078,30 @@ class NavigationManager {
109099
109078
  this.prevButton.style.transition = 'all 0.2s ease';
109100
109079
  this.prevButton.addEventListener('click', this.previousWaypoint);
109101
109080
  buttonContainer.appendChild(this.prevButton);
109081
+ // Play/Pause button (only if autoplay is enabled)
109082
+ if (this.autoPlayEnabled) {
109083
+ this.playPauseButton = document.createElement('button');
109084
+ this.playPauseButton.className = 'storysplat-nav-button storysplat-play-pause-button';
109085
+ this.playPauseButton.innerHTML = this.isPlaying ? '⏸️' : '▶️';
109086
+ this.playPauseButton.style.background = 'rgba(0, 0, 0, 0.5)';
109087
+ this.playPauseButton.style.border = '1px solid rgba(255, 255, 255, 0.3)';
109088
+ this.playPauseButton.style.color = 'white';
109089
+ this.playPauseButton.style.padding = '8px 16px';
109090
+ this.playPauseButton.style.fontSize = '14px';
109091
+ this.playPauseButton.style.borderRadius = '4px';
109092
+ this.playPauseButton.style.cursor = 'pointer';
109093
+ this.playPauseButton.style.transition = 'all 0.2s ease';
109094
+ this.playPauseButton.addEventListener('click', () => {
109095
+ if (this.isPlaying) {
109096
+ this.pause();
109097
+ }
109098
+ else {
109099
+ this.play();
109100
+ }
109101
+ this.updatePlayPauseButton();
109102
+ });
109103
+ buttonContainer.appendChild(this.playPauseButton);
109104
+ }
109102
109105
  this.nextButton = document.createElement('button');
109103
109106
  this.nextButton.className = 'storysplat-nav-button storysplat-next-button';
109104
109107
  this.nextButton.innerHTML = 'Next <span style="margin-left: 5px;">→</span>';
@@ -109117,6 +109120,8 @@ class NavigationManager {
109117
109120
  this.uiContainer.appendChild(this.scrollControlsContainer);
109118
109121
  console.log('NavigationManager: Created standard template scroll controls');
109119
109122
  }
109123
+ // Add camera mode toggles to this template
109124
+ this.createCameraModeToggles();
109120
109125
  this.setupCommonUI(); // Add common UI elements
109121
109126
  }
109122
109127
  setupProTemplateUI() {
@@ -109208,6 +109213,31 @@ class NavigationManager {
109208
109213
  this.progressIndicator.style.width = '0%';
109209
109214
  progressBarContainer.appendChild(this.progressIndicator);
109210
109215
  centerContent.appendChild(progressBarContainer);
109216
+ // Play/Pause button (only if autoplay is enabled) - centered below progress bar
109217
+ if (this.autoPlayEnabled) {
109218
+ this.playPauseButton = document.createElement('button');
109219
+ this.playPauseButton.className = 'storysplat-nav-button storysplat-play-pause-button';
109220
+ this.playPauseButton.innerHTML = this.isPlaying ? '⏸️' : '▶️';
109221
+ this.playPauseButton.style.background = 'rgba(0, 0, 0, 0.5)';
109222
+ this.playPauseButton.style.border = 'none';
109223
+ this.playPauseButton.style.color = 'white';
109224
+ this.playPauseButton.style.padding = '6px 12px';
109225
+ this.playPauseButton.style.fontSize = '14px';
109226
+ this.playPauseButton.style.borderRadius = '4px';
109227
+ this.playPauseButton.style.cursor = 'pointer';
109228
+ this.playPauseButton.style.transition = 'background 0.2s ease';
109229
+ this.playPauseButton.style.marginTop = '5px';
109230
+ this.playPauseButton.addEventListener('click', () => {
109231
+ if (this.isPlaying) {
109232
+ this.pause();
109233
+ }
109234
+ else {
109235
+ this.play();
109236
+ }
109237
+ this.updatePlayPauseButton();
109238
+ });
109239
+ centerContent.appendChild(this.playPauseButton);
109240
+ }
109211
109241
  this.scrollControlsContainer.appendChild(centerContent);
109212
109242
  // Next button (right side)
109213
109243
  if (this.options.showNavButtons !== false) {
@@ -109229,33 +109259,99 @@ class NavigationManager {
109229
109259
  this.uiContainer.appendChild(this.scrollControlsContainer);
109230
109260
  console.log('NavigationManager: Created pro template scroll controls');
109231
109261
  }
109262
+ // For pro template, create camera mode buttons on the left side instead
109263
+ this.createProTemplateCameraModeToggles();
109232
109264
  this.setupCommonUI(); // Add common UI elements
109233
109265
  }
109266
+ createProTemplateCameraModeToggles() {
109267
+ // Show if explicitly enabled or if not explicitly disabled and we have multiple modes
109268
+ const shouldShowCameraModeToggle = this.options.showCameraModeToggle === true || (this.options.showCameraModeToggle !== false && this.allowedModes.length > 1);
109269
+ if (shouldShowCameraModeToggle) {
109270
+ console.log('NavigationManager: Creating PRO template camera mode toggle container (left side)');
109271
+ this.cameraModeToggleContainer = document.createElement('div');
109272
+ this.cameraModeToggleContainer.className = 'storysplat-camera-mode-toggle storysplat-camera-mode-toggle-pro';
109273
+ this.cameraModeToggleContainer.id = 'modeToggleContainer';
109274
+ // Position on left side, vertically centered and stacked
109275
+ this.cameraModeToggleContainer.style.position = 'absolute';
109276
+ this.cameraModeToggleContainer.style.left = '20px';
109277
+ this.cameraModeToggleContainer.style.top = '50%';
109278
+ this.cameraModeToggleContainer.style.transform = 'translateY(-50%)';
109279
+ this.cameraModeToggleContainer.style.display = 'flex';
109280
+ this.cameraModeToggleContainer.style.flexDirection = 'column';
109281
+ this.cameraModeToggleContainer.style.gap = '8px';
109282
+ this.cameraModeToggleContainer.style.zIndex = '1000';
109283
+ // Conditional button creation like HTML exports
109284
+ const buttonsToCreate = [];
109285
+ // Walk button only if walk allowed AND explore not allowed
109286
+ if (this.allowedModes.includes('walk') && !this.allowedModes.includes('explore')) {
109287
+ buttonsToCreate.push('walk');
109288
+ }
109289
+ // Explore button if explore is allowed
109290
+ if (this.allowedModes.includes('explore')) {
109291
+ buttonsToCreate.push('explore');
109292
+ }
109293
+ // Tour button if tour is allowed
109294
+ if (this.allowedModes.includes('tour')) {
109295
+ buttonsToCreate.push('tour');
109296
+ }
109297
+ // Hybrid button if hybrid is allowed (also check for 'auto' which maps to hybrid)
109298
+ if (this.allowedModes.includes('hybrid') || this.allowedModes.includes('auto')) {
109299
+ buttonsToCreate.push('hybrid');
109300
+ }
109301
+ buttonsToCreate.forEach((mode) => {
109302
+ var _a;
109303
+ const button = document.createElement('button');
109304
+ button.className = `mode-button mode-button-pro`;
109305
+ button.id = `mode${mode.charAt(0).toUpperCase() + mode.slice(1)}`;
109306
+ button.textContent = mode.charAt(0).toUpperCase() + mode.slice(1);
109307
+ button.dataset.mode = mode;
109308
+ // Style for pro template - vertical stack on left side
109309
+ button.style.padding = '8px 12px';
109310
+ button.style.background = 'rgba(0, 0, 0, 0.7)';
109311
+ button.style.border = `1px solid ${this.options.uiColor || '#4CAF50'}`;
109312
+ button.style.color = 'white';
109313
+ button.style.cursor = 'pointer';
109314
+ button.style.fontSize = '14px';
109315
+ button.style.borderRadius = '4px';
109316
+ button.style.transition = 'all 0.2s ease';
109317
+ button.style.minWidth = '80px';
109318
+ button.style.textAlign = 'center';
109319
+ button.addEventListener('click', () => this.setMode(mode));
109320
+ (_a = this.cameraModeToggleContainer) === null || _a === void 0 ? void 0 : _a.appendChild(button);
109321
+ });
109322
+ // Add directly to main UI container (not scroll controls)
109323
+ this.uiContainer.appendChild(this.cameraModeToggleContainer);
109324
+ console.log('DEBUG NavigationManager: PRO template camera mode toggle container created and added to left side');
109325
+ }
109326
+ }
109234
109327
  createCameraModeToggles() {
109235
109328
  // Show if explicitly enabled or if not explicitly disabled and we have multiple modes
109236
109329
  const shouldShowCameraModeToggle = this.options.showCameraModeToggle === true || (this.options.showCameraModeToggle !== false && this.allowedModes.length > 1);
109237
- console.log('NavigationManager: Camera mode toggle check:', {
109330
+ console.log('DEBUG NavigationManager: Camera mode toggle check:', {
109238
109331
  showCameraModeToggle: this.options.showCameraModeToggle,
109332
+ allowedModes: this.allowedModes,
109239
109333
  allowedModesLength: this.allowedModes.length,
109240
- shouldShow: shouldShowCameraModeToggle
109334
+ shouldShow: shouldShowCameraModeToggle,
109335
+ scrollControlsContainer: this.scrollControlsContainer ? 'exists' : 'null'
109241
109336
  });
109242
109337
  if (shouldShowCameraModeToggle) {
109243
109338
  console.log('NavigationManager: Creating camera mode toggle container');
109244
109339
  this.cameraModeToggleContainer = document.createElement('div');
109245
109340
  this.cameraModeToggleContainer.className = 'storysplat-camera-mode-toggle';
109246
109341
  this.cameraModeToggleContainer.id = 'modeToggleContainer';
109247
- // Style the container to match HTML exports
109248
- this.cameraModeToggleContainer.style.margin = '10px 0';
109342
+ // Style the container for minimal template - position at bottom
109343
+ this.cameraModeToggleContainer.style.margin = '5px 0';
109249
109344
  this.cameraModeToggleContainer.style.display = 'flex';
109250
109345
  this.cameraModeToggleContainer.style.justifyContent = 'center';
109251
109346
  // Create inner toggle bar (this is the #modeToggle div in HTML exports)
109252
109347
  const modeToggleBar = document.createElement('div');
109253
109348
  modeToggleBar.id = 'modeToggle';
109254
109349
  modeToggleBar.style.display = 'flex';
109255
- modeToggleBar.style.border = `1px solid ${this.options.uiColor || '#4CAF50'}`;
109256
- modeToggleBar.style.borderRadius = '5px';
109350
+ modeToggleBar.style.border = 'none';
109351
+ modeToggleBar.style.borderRadius = '3px';
109257
109352
  modeToggleBar.style.overflow = 'hidden';
109258
- modeToggleBar.style.width = '250px';
109353
+ modeToggleBar.style.width = 'auto';
109354
+ modeToggleBar.style.gap = '4px';
109259
109355
  // Conditional button creation like HTML exports
109260
109356
  const buttonsToCreate = [];
109261
109357
  // Walk button only if walk allowed AND explore not allowed
@@ -109270,8 +109366,8 @@ class NavigationManager {
109270
109366
  if (this.allowedModes.includes('tour')) {
109271
109367
  buttonsToCreate.push('tour');
109272
109368
  }
109273
- // Hybrid button if hybrid is allowed
109274
- if (this.allowedModes.includes('hybrid')) {
109369
+ // Hybrid button if hybrid is allowed (also check for 'auto' which maps to hybrid)
109370
+ if (this.allowedModes.includes('hybrid') || this.allowedModes.includes('auto')) {
109275
109371
  buttonsToCreate.push('hybrid');
109276
109372
  }
109277
109373
  buttonsToCreate.forEach((mode) => {
@@ -109280,27 +109376,44 @@ class NavigationManager {
109280
109376
  button.id = `mode${mode.charAt(0).toUpperCase() + mode.slice(1)}`;
109281
109377
  button.textContent = mode.charAt(0).toUpperCase() + mode.slice(1);
109282
109378
  button.dataset.mode = mode;
109283
- // Style exactly like HTML exports
109379
+ // Style for minimal template - transparent until hover
109284
109380
  button.style.flex = '1';
109285
- button.style.padding = '10px';
109286
- button.style.background = this.options.uiColor || '#4CAF50';
109381
+ button.style.padding = '6px 10px';
109382
+ button.style.background = 'transparent';
109287
109383
  button.style.border = 'none';
109288
109384
  button.style.color = 'white';
109289
109385
  button.style.cursor = 'pointer';
109290
- button.style.fontSize = '16px';
109291
- button.style.transition = 'background-color 0.3s';
109386
+ button.style.fontSize = '12px';
109387
+ button.style.transition = 'background-color 0.3s, opacity 0.3s';
109388
+ button.style.borderRadius = '3px';
109389
+ button.style.opacity = '0.6';
109390
+ button.style.textAlign = 'center';
109391
+ button.style.margin = '0 2px';
109392
+ button.style.minWidth = '60px';
109292
109393
  button.style.width = `${100 / buttonsToCreate.length}%`;
109394
+ // Add hover effects
109395
+ button.addEventListener('mouseenter', () => {
109396
+ button.style.background = 'rgba(0, 0, 0, 0.7)';
109397
+ button.style.opacity = '1';
109398
+ });
109399
+ button.addEventListener('mouseleave', () => {
109400
+ button.style.background = 'transparent';
109401
+ button.style.opacity = '0.6';
109402
+ });
109293
109403
  button.addEventListener('click', () => this.setMode(mode));
109294
109404
  modeToggleBar.appendChild(button);
109295
109405
  });
109296
109406
  this.cameraModeToggleContainer.appendChild(modeToggleBar);
109297
109407
  // Add to scroll controls if they exist (like HTML exports), otherwise to main UI
109298
109408
  if (this.scrollControlsContainer) {
109409
+ console.log('DEBUG NavigationManager: Adding camera mode toggle to scroll controls container');
109299
109410
  this.scrollControlsContainer.appendChild(this.cameraModeToggleContainer);
109300
109411
  }
109301
109412
  else {
109413
+ console.log('DEBUG NavigationManager: Adding camera mode toggle to main UI container');
109302
109414
  this.uiContainer.appendChild(this.cameraModeToggleContainer);
109303
109415
  }
109416
+ console.log('DEBUG NavigationManager: Camera mode toggle container created and added to DOM');
109304
109417
  }
109305
109418
  }
109306
109419
  setupCommonUI() {
@@ -109372,21 +109485,35 @@ class NavigationManager {
109372
109485
  updateUI() {
109373
109486
  // --- Navigation Progress/Buttons ---
109374
109487
  const progress = this.getProgress();
109375
- const showScrollControls = (this.currentMode === 'tour' || this.currentMode === 'hybrid');
109488
+ const showNavigationControls = (this.currentMode === 'tour' || this.currentMode === 'hybrid');
109489
+ const hasCameraModeToggles = this.cameraModeToggleContainer !== null;
109376
109490
  // Update progress indicator
109377
109491
  if (this.progressIndicator) {
109378
109492
  this.progressIndicator.style.width = `${progress * 100}%`;
109493
+ this.progressIndicator.style.display = showNavigationControls ? 'block' : 'none';
109379
109494
  }
109380
- // Update scroll percentage
109495
+ // Update scroll percentage - hide in explore/walk modes
109381
109496
  if (this.scrollPercentageElement) {
109382
109497
  this.scrollPercentageElement.textContent = `${Math.round(progress * 100)}%`;
109498
+ this.scrollPercentageElement.style.display = showNavigationControls ? 'block' : 'none';
109499
+ }
109500
+ // Show/hide prev/next buttons
109501
+ if (this.prevButton) {
109502
+ this.prevButton.style.display = showNavigationControls ? 'block' : 'none';
109383
109503
  }
109384
- // Show/hide entire scroll controls container
109504
+ if (this.nextButton) {
109505
+ this.nextButton.style.display = showNavigationControls ? 'block' : 'none';
109506
+ }
109507
+ // Show/hide play/pause button
109508
+ if (this.playPauseButton) {
109509
+ this.playPauseButton.style.display = showNavigationControls ? 'block' : 'none';
109510
+ }
109511
+ // Keep scroll controls container visible if we have camera mode toggles OR navigation controls
109385
109512
  if (this.scrollControlsContainer) {
109386
- this.scrollControlsContainer.style.display = showScrollControls ? 'flex' : 'none';
109513
+ this.scrollControlsContainer.style.display = (showNavigationControls || hasCameraModeToggles) ? 'flex' : 'none';
109387
109514
  }
109388
- // Update button states (only when controls are visible)
109389
- if (showScrollControls) {
109515
+ // Update button states (only when navigation controls are visible)
109516
+ if (showNavigationControls) {
109390
109517
  if (this.prevButton) {
109391
109518
  this.prevButton.disabled = progress <= 0 && !this.loopMode;
109392
109519
  this.prevButton.style.opacity = this.prevButton.disabled ? '0.4' : '1';
@@ -109458,6 +109585,12 @@ class NavigationManager {
109458
109585
  console.error("Failed to initialize virtual joysticks:", error);
109459
109586
  }
109460
109587
  }
109588
+ // Update play/pause button icon based on current state
109589
+ updatePlayPauseButton() {
109590
+ if (this.playPauseButton) {
109591
+ this.playPauseButton.innerHTML = this.isPlaying ? '⏸️' : '▶️';
109592
+ }
109593
+ }
109461
109594
  // Update virtual joystick visibility based on current camera mode
109462
109595
  updateVirtualJoystickVisibility() {
109463
109596
  if (!this.isMobileDevice || !VirtualJoystick.Canvas)
@@ -109712,6 +109845,7 @@ class NavigationManager {
109712
109845
  }
109713
109846
  this.isPlaying = true;
109714
109847
  this.userControl = false;
109848
+ this.updatePlayPauseButton(); // Update button icon
109715
109849
  console.log("Autoplay started/resumed");
109716
109850
  // If starting from the end (e.g., after looping manually), reset
109717
109851
  if (this.scrollPosition >= this.path.length - 1 && this.loopMode) {
@@ -109724,6 +109858,7 @@ class NavigationManager {
109724
109858
  pause() {
109725
109859
  if (this.isPlaying) {
109726
109860
  this.isPlaying = false;
109861
+ this.updatePlayPauseButton(); // Update button icon
109727
109862
  console.log("Autoplay paused");
109728
109863
  }
109729
109864
  }
@@ -160898,6 +161033,13 @@ class SplatSwapManager {
160898
161033
  }
160899
161034
  }
160900
161035
 
161036
+ var UIType;
161037
+ (function (UIType) {
161038
+ UIType["Standard"] = "standard";
161039
+ UIType["Minimal"] = "minimal";
161040
+ UIType["Pro"] = "pro";
161041
+ })(UIType || (UIType = {}));
161042
+
160901
161043
  /**
160902
161044
  * Manages the preloader UI element.
160903
161045
  */
@@ -161466,6 +161608,775 @@ class MuteButtonUI {
161466
161608
  }
161467
161609
  }
161468
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
+
161469
162380
  /******************************************************************************
161470
162381
  Copyright (c) Microsoft Corporation.
161471
162382
 
@@ -161718,6 +162629,8 @@ class Viewer {
161718
162629
  }
161719
162630
  }
161720
162631
 
162632
+ // Define version constant (updated manually with each release)
162633
+ const VIEWER_VERSION = '0.1.21';
161721
162634
  /**
161722
162635
  * Initializes the StorySplat Viewer within a given HTML element.
161723
162636
  *
@@ -161730,6 +162643,7 @@ class Viewer {
161730
162643
  * Transforms raw JSON data into properly typed StorySplatData
161731
162644
  */
161732
162645
  function transformSceneData(rawData) {
162646
+ var _a, _b;
161733
162647
  // Handle all the data casting and transformation internally
161734
162648
  return Object.assign({ loadedModelUrl: rawData.loadedModelUrl || rawData.modelUrl, hotspots: rawData.hotspots || [], waypoints: rawData.waypoints || [], lights: rawData.lights || [], particleSystems: rawData.particleSystems || [], customMeshes: rawData.customMeshes || [], additionalSplats: rawData.additionalSplats || [], activeSkyboxUrl: rawData.activeSkyboxUrl,
161735
162649
  // Splat scaling properties
@@ -161737,7 +162651,7 @@ function transformSceneData(rawData) {
161737
162651
  // Required top-level properties
161738
162652
  fov: rawData.fov || 0.8, minClipPlane: rawData.minClipPlane || 0.1, maxClipPlane: rawData.maxClipPlane || 1000, cameraMovementSpeed: rawData.cameraMovementSpeed || 0.2, cameraRotationSensitivity: rawData.cameraRotationSensitivity || 4000, autoPlayEnabled: rawData.autoPlayEnabled || false, autoPlaySpeed: rawData.autoPlaySpeed || 1.0,
161739
162653
  // Navigation properties for matching HTML export feel
161740
- scrollSpeed: rawData.scrollSpeed || 0.001, transitionSpeed: rawData.transitionSpeed || 1.5, cameraDampening: rawData.cameraDampening || 0.1, initialCameraMode: rawData.defaultCameraMode || rawData.initialCameraMode || 'explore', defaultCameraMode: rawData.defaultCameraMode || 'explore', allowedCameraModes: rawData.allowedCameraModes || ['explore', 'tour', 'hybrid', 'walk'], backgroundColor: rawData.backgroundColor, backgroundAudio: rawData.backgroundAudio, sceneTitle: rawData.sceneTitle,
162654
+ scrollSpeed: rawData.scrollSpeed || 0.001, transitionSpeed: rawData.transitionSpeed || 1.5, cameraDampening: rawData.cameraDampening || 0.1, initialCameraMode: rawData.defaultCameraMode || rawData.initialCameraMode || (((_a = rawData.waypoints) === null || _a === void 0 ? void 0 : _a.length) > 0 ? 'tour' : 'explore'), defaultCameraMode: rawData.defaultCameraMode || (((_b = rawData.waypoints) === null || _b === void 0 ? void 0 : _b.length) > 0 ? 'tour' : 'explore'), allowedCameraModes: rawData.allowedCameraModes || ['explore', 'tour', 'hybrid', 'walk'], backgroundColor: rawData.backgroundColor, backgroundAudio: rawData.backgroundAudio, sceneTitle: rawData.sceneTitle,
161741
162655
  // UI configuration
161742
162656
  uiColor: rawData.uiColor || '#4CAF50', scrollButtonMode: rawData.scrollButtonMode || 'waypoint', scrollAmount: rawData.scrollAmount || 10, loopMode: rawData.loopMode || false, invertCameraRotation: rawData.invertCameraRotation || false, playerHeight: rawData.playerHeight || 1.8 }, rawData);
161743
162657
  }
@@ -161763,7 +162677,7 @@ function createViewerOptions(rawData, userOptions) {
161763
162677
  buttonPosition: ((_e = rawData.uiOptions) === null || _e === void 0 ? void 0 : _e.buttonPosition) || 'below',
161764
162678
  showStartExperience: ((_f = rawData.uiOptions) === null || _f === void 0 ? void 0 : _f.showStartExperience) || false,
161765
162679
  debugMode: ((_g = rawData.uiOptions) === null || _g === void 0 ? void 0 : _g.debugMode) || false,
161766
- uiType: ((_h = rawData.uiOptions) === null || _h === void 0 ? void 0 : _h.uiType) || rawData.uiType || 'minimal',
162680
+ uiType: ((_h = rawData.uiOptions) === null || _h === void 0 ? void 0 : _h.uiType) || rawData.uiType || 'standard', // Default to standard instead of minimal to match HTML
161767
162681
  hideWatermark: ((_j = rawData.uiOptions) === null || _j === void 0 ? void 0 : _j.hideWatermark) || false,
161768
162682
  watermarkText: (_k = rawData.uiOptions) === null || _k === void 0 ? void 0 : _k.watermarkText,
161769
162683
  watermarkLink: (_l = rawData.uiOptions) === null || _l === void 0 ? void 0 : _l.watermarkLink
@@ -161776,7 +162690,7 @@ function createViewerOptions(rawData, userOptions) {
161776
162690
  }
161777
162691
  async function initializeViewer(targetElement, rawData, // Accept any JSON data
161778
162692
  options) {
161779
- var _a, _b;
162693
+ var _a, _b, _c, _d;
161780
162694
  // Register BabylonJS loaders at runtime
161781
162695
  try {
161782
162696
  registerBuiltInLoaders();
@@ -161801,7 +162715,27 @@ options) {
161801
162715
  }
161802
162716
  // Create comprehensive viewer options from the data
161803
162717
  const finalViewerOptions = createViewerOptions(rawData, options);
161804
- console.log(`StorySplat Viewer v${packageJson.version}: Initializing...`);
162718
+ console.log(`StorySplat Viewer v${VIEWER_VERSION}: Initializing...`);
162719
+ // Debug logging for camera mode configuration
162720
+ console.log('DEBUG: Raw data camera modes:', {
162721
+ defaultCameraMode: rawData.defaultCameraMode,
162722
+ initialCameraMode: rawData.initialCameraMode,
162723
+ allowedCameraModes: rawData.allowedCameraModes,
162724
+ waypoints: ((_a = rawData.waypoints) === null || _a === void 0 ? void 0 : _a.length) || 0
162725
+ });
162726
+ console.log('DEBUG: Transformed data camera modes:', {
162727
+ initialCameraMode: data.initialCameraMode,
162728
+ allowedCameraModes: data.allowedCameraModes,
162729
+ waypoints: ((_b = data.waypoints) === null || _b === void 0 ? void 0 : _b.length) || 0
162730
+ });
162731
+ console.log('DEBUG: Final viewer options camera modes:', {
162732
+ defaultCameraMode: finalViewerOptions.defaultCameraMode,
162733
+ allowedCameraModes: finalViewerOptions.allowedCameraModes,
162734
+ showCameraModeToggle: finalViewerOptions.showCameraModeToggle,
162735
+ showWalkExploreToggle: finalViewerOptions.showWalkExploreToggle,
162736
+ showProgressBar: finalViewerOptions.showProgressBar,
162737
+ showNavButtons: finalViewerOptions.showNavButtons
162738
+ });
161805
162739
  // --- 1. Create Canvas ---
161806
162740
  const canvas = document.createElement("canvas");
161807
162741
  canvas.style.width = "100%";
@@ -161823,11 +162757,7 @@ options) {
161823
162757
  let navigationManager = null;
161824
162758
  let collisionManager = null;
161825
162759
  // let uiManager: UIManager | null = null; // REMOVED: Old monolithic UI Manager
161826
- let preloaderUI = null;
161827
- let startButtonUI = null;
161828
- let watermarkUI = null;
161829
- let helpPanelUI = null;
161830
- let muteButtonUI = null; // Add reference for MuteButtonUI
162760
+ let templateManager = null; // NEW: Template-based UI system
161831
162761
  let audioManager = null;
161832
162762
  let renderLoopManager = null; // Add RenderLoopManager reference
161833
162763
  let xrExperience = null; // Add XR experience reference
@@ -161861,13 +162791,9 @@ options) {
161861
162791
  splatSwapManager = null;
161862
162792
  audioManager === null || audioManager === void 0 ? void 0 : audioManager.dispose();
161863
162793
  audioManager = null;
161864
- // Dispose new UI modules
161865
- preloaderUI === null || preloaderUI === void 0 ? void 0 : preloaderUI.dispose();
161866
- startButtonUI === null || startButtonUI === void 0 ? void 0 : startButtonUI.dispose();
161867
- watermarkUI === null || watermarkUI === void 0 ? void 0 : watermarkUI.dispose();
161868
- helpPanelUI === null || helpPanelUI === void 0 ? void 0 : helpPanelUI.dispose();
161869
- muteButtonUI === null || muteButtonUI === void 0 ? void 0 : muteButtonUI.dispose(); // Dispose mute button UI
161870
- 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;
161871
162797
  // NavigationManager and CameraManager disposal is handled by scene dispose or specific logic within them
161872
162798
  // Dispose features managed outside scene graph
161873
162799
  if (scene && finalViewerOptions) {
@@ -161970,9 +162896,17 @@ options) {
161970
162896
  throw new Error("Camera not initialized before NavigationManager"); // Guard
161971
162897
  if (!analyticsManager)
161972
162898
  throw new Error("AnalyticsManager not initialized before NavigationManager"); // Guard
162899
+ // Debug logging for NavigationManager initialization
162900
+ console.log('DEBUG: NavigationManager initialization with camera modes:', {
162901
+ allowedCameraModes: data.allowedCameraModes,
162902
+ initialCameraMode: data.initialCameraMode,
162903
+ finalViewerOptions_allowedCameraModes: finalViewerOptions.allowedCameraModes,
162904
+ finalViewerOptions_defaultCameraMode: finalViewerOptions.defaultCameraMode,
162905
+ waypointCount: ((_c = data.waypoints) === null || _c === void 0 ? void 0 : _c.length) || 0
162906
+ });
161973
162907
  // Pass the collisionManager and analyticsManager instances to NavigationManager
161974
162908
  // Pass viewerOptions directly, relying on its content for allowed modes etc.
161975
- navigationManager = new NavigationManager(scene, camera, Object.assign(Object.assign({}, finalViewerOptions), { allowedCameraModes: data.allowedCameraModes, initialCameraMode: data.initialCameraMode, waypoints: (_a = data.waypoints) === null || _a === void 0 ? void 0 : _a.map(wp => {
162909
+ navigationManager = new NavigationManager(scene, camera, Object.assign(Object.assign({}, finalViewerOptions), { allowedCameraModes: data.allowedCameraModes, initialCameraMode: data.initialCameraMode, waypoints: (_d = data.waypoints) === null || _d === void 0 ? void 0 : _d.map(wp => {
161976
162910
  var _a;
161977
162911
  return ({
161978
162912
  x: wp.x,
@@ -161997,6 +162931,7 @@ options) {
161997
162931
  triggerDistance: wp.triggerDistance
161998
162932
  });
161999
162933
  }) }), targetElement, collisionManager, analyticsManager);
162934
+ console.log('DEBUG: NavigationManager created. Current mode:', navigationManager.getCurrentMode());
162000
162935
  // --- No explicit sync needed - both managers use the same initial mode logic ---
162001
162936
  // NavigationManager determines its own initial mode in constructor based on the same data/options
162002
162937
  // The onCameraModeChanged callback will handle future mode changes
@@ -162010,44 +162945,23 @@ options) {
162010
162945
  splatSwapManager === null || splatSwapManager === void 0 ? void 0 : splatSwapManager.updateProgress(progress, waypointIndex);
162011
162946
  };
162012
162947
  }
162013
- // --- 9. Initialize Modular UI Components ---
162014
- // Initialize AudioManager first (needed for start button callback)
162015
- // Note: AudioManager is initialized later now, before render loop manager.
162016
- // We'll pass the instance later if needed, or adjust initialization order.
162017
- // Preloader (always show by default, like HTML exports)
162018
- const defaultPreloaderConfig = {
162019
- enabled: true,
162020
- imageUrl: 'https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fimages%2FStorySplat.webp?alt=media&token=953e8ab3-1865-4ac1-a98d-b548b7066bda',
162021
- lottieUrl: 'https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Flotties%2FstorySplatLottie.json?alt=media&token=d7edc19d-9cb8-4c6e-a94c-cba1d2b65d5e',
162022
- uiColor: (finalViewerOptions === null || finalViewerOptions === void 0 ? void 0 : finalViewerOptions.uiColor) || '#4CAF50'
162023
- };
162024
- const preloaderConfig = Object.assign(Object.assign({}, defaultPreloaderConfig), finalViewerOptions === null || finalViewerOptions === void 0 ? void 0 : finalViewerOptions.preloaderConfig);
162025
- preloaderUI = new PreloaderUI(targetElement, preloaderConfig);
162026
- preloaderUI.create();
162027
- // Start Button
162028
- if (finalViewerOptions === null || finalViewerOptions === void 0 ? void 0 : finalViewerOptions.showStartExperience) {
162029
- startButtonUI = new StartButtonUI(targetElement, finalViewerOptions, () => {
162030
- // Callback to initialize audio context on first interaction
162031
- audioManager === null || audioManager === void 0 ? void 0 : audioManager.initialize(); // Use the correct initialize method
162032
- });
162033
- startButtonUI.create();
162034
- }
162035
- // Watermark
162036
- watermarkUI = new WatermarkUI(targetElement, finalViewerOptions);
162037
- watermarkUI.create(); // Creates conditionally based on options inside the class
162038
- // Help Panel
162039
- helpPanelUI = new HelpPanelUI(targetElement, finalViewerOptions);
162040
- helpPanelUI.create(); // Creates conditionally based on options inside the class
162041
- // Mute Button (Needs AudioManager instance)
162042
- // 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
+ });
162043
162957
  // --- 10. Load Splat Data ---
162044
162958
  console.log("StorySplat Viewer: Starting splat asset load...");
162045
162959
  splatRootMeshes = await loadSplatAsset(scene, data, (percentage, text) => {
162046
- // Update preloader UI via PreloaderUI instance
162047
- 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);
162048
162962
  });
162049
162963
  console.log("StorySplat Viewer: Splat asset loaded.");
162050
- 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
162051
162965
  // --- 11. Initialize/Setup Features ---
162052
162966
  // Setup Particle Systems (sync)
162053
162967
  // Ensure setupParticleSystems is correctly imported and called
@@ -162085,11 +162999,9 @@ options) {
162085
162999
  if (!analyticsManager)
162086
163000
  throw new Error("AnalyticsManager not initialized before AudioManager"); // Guard
162087
163001
  audioManager = new AudioManager(scene, finalViewerOptions, analyticsManager);
162088
- // Now initialize Mute Button UI, passing the audioManager instance
162089
- if ((_b = finalViewerOptions === null || finalViewerOptions === void 0 ? void 0 : finalViewerOptions.audio) === null || _b === void 0 ? void 0 : _b.showMuteButton) {
162090
- muteButtonUI = new MuteButtonUI(targetElement, finalViewerOptions, audioManager);
162091
- muteButtonUI.create();
162092
- }
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);
162093
163005
  // --- 12. Initialize Render Loop Manager ---
162094
163006
  // Needs engine, scene, cameraManager, navigationManager
162095
163007
  if (engine && scene && cameraManager) { // Ensure core components exist
@@ -162119,7 +163031,7 @@ options) {
162119
163031
  }
162120
163032
  }, 0);
162121
163033
  }
162122
- console.log(`StorySplat Viewer v${packageJson.version}: Initialized successfully.`);
163034
+ console.log(`StorySplat Viewer v${VIEWER_VERSION}: Initialized successfully.`);
162123
163035
  // --- 16. Define API Methods ---
162124
163036
  const setCameraMode = (mode) => cameraManager === null || cameraManager === void 0 ? void 0 : cameraManager.setCameraMode(mode);
162125
163037
  const getCurrentCameraMode = () => { var _a; return (_a = cameraManager === null || cameraManager === void 0 ? void 0 : cameraManager.currentMode) !== null && _a !== void 0 ? _a : 'explore'; }; // Provide default
@@ -162165,8 +163077,8 @@ options) {
162165
163077
  console.log(`StorySplat Viewer: Starting splat swap to ${newSplatUrl}`);
162166
163078
  analyticsManager === null || analyticsManager === void 0 ? void 0 : analyticsManager.trackSplatSwapStarted(newSplatUrl); // Corrected method call
162167
163079
  // Show preloader
162168
- preloaderUI === null || preloaderUI === void 0 ? void 0 : preloaderUI.show(); // Corrected method call (added in preloader.ts)
162169
- 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
162170
163082
  // Dispose existing splat meshes
162171
163083
  console.log("StorySplat Viewer: Disposing current splat meshes...");
162172
163084
  splatRootMeshes.forEach(mesh => {
@@ -162189,7 +163101,7 @@ options) {
162189
163101
  // Ensure other potentially relevant fields from data are present if needed
162190
163102
  waypoints: data.waypoints, hotspots: data.hotspots });
162191
163103
  splatRootMeshes = await loadSplatAsset(scene, tempData, (percentage, text) => {
162192
- preloaderUI === null || preloaderUI === void 0 ? void 0 : preloaderUI.updateProgress(percentage, text);
163104
+ templateManager === null || templateManager === void 0 ? void 0 : templateManager.updatePreloaderProgress(percentage, text);
162193
163105
  });
162194
163106
  currentSplatUrl = newSplatUrl; // Update the tracked URL
162195
163107
  console.log("StorySplat Viewer: New splat asset loaded successfully.");
@@ -162211,12 +163123,12 @@ options) {
162211
163123
  console.error(`StorySplat Viewer: Failed to load new splat asset from ${newSplatUrl}:`, error);
162212
163124
  analyticsManager === null || analyticsManager === void 0 ? void 0 : analyticsManager.trackSplatSwapFailed(error, newSplatUrl); // Corrected method call
162213
163125
  // Hide preloader even on error
162214
- preloaderUI === null || preloaderUI === void 0 ? void 0 : preloaderUI.hide();
163126
+ templateManager === null || templateManager === void 0 ? void 0 : templateManager.hidePreloader();
162215
163127
  // Re-throw error to signal failure to the caller
162216
163128
  throw error;
162217
163129
  }
162218
163130
  // Hide preloader on success
162219
- preloaderUI === null || preloaderUI === void 0 ? void 0 : preloaderUI.hide();
163131
+ templateManager === null || templateManager === void 0 ? void 0 : templateManager.hidePreloader();
162220
163132
  console.log("StorySplat Viewer: Splat swap complete.");
162221
163133
  };
162222
163134
  /**