aaurelions commited on
Commit
daa1998
·
verified ·
1 Parent(s): f193ee6

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +86 -87
index.html CHANGED
@@ -9,92 +9,96 @@
9
  @import url('https://rsms.me/inter/inter.css');
10
  html { font-family: 'Inter', sans-serif; }
11
 
12
- body { background-color: #111827; /* gray-900 */ }
 
 
 
 
 
 
13
 
14
- /* Smooth loading overlay for transitions */
15
  #loading-overlay {
16
- backdrop-filter: blur(8px);
17
- background-color: rgba(0, 0, 0, 0.5);
18
  transition: opacity 0.3s ease-in-out;
19
  }
20
  #loading-overlay .spinner {
21
- border-top-color: #3b82f6; /* blue-500 */
22
  }
23
 
24
- /* Slide-out Panel Styling */
25
  .side-panel {
26
- background-color: rgba(17, 24, 39, 0.75); /* gray-900 with opacity */
27
- backdrop-filter: blur(16px);
28
- border-color: rgba(255, 255, 255, 0.1);
29
  transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
30
  }
31
  .side-panel.left { transform: translateX(-100%); }
32
  .side-panel.right { transform: translateX(100%); }
33
  .side-panel.is-open { transform: translateX(0); }
34
 
35
- /* Panel Trigger Button Styling */
36
  .panel-trigger {
37
- background-color: rgba(17, 24, 39, 0.75);
38
- border-color: rgba(255, 255, 255, 0.1);
39
  transition: background-color 0.2s ease;
40
  }
41
  .panel-trigger:hover { background-color: rgba(59, 130, 246, 0.5); }
42
 
43
- /* Active state for list items in panels */
 
 
 
 
 
 
44
  .list-item.active {
45
- background-color: rgba(59, 130, 246, 0.2);
46
- box-shadow: inset 3px 0 0 0 #3b82f6; /* A nice left border highlight */
47
  }
48
 
49
- /* Custom scrollbar for panels */
50
  .panel-content::-webkit-scrollbar { width: 6px; }
51
  .panel-content::-webkit-scrollbar-track { background: transparent; }
52
  .panel-content::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2); border-radius: 10px; }
53
- .panel-content::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.4); }
54
  </style>
55
  </head>
56
- <body class="text-white select-none overflow-hidden">
57
 
58
- <!-- 3D Canvas -->
59
  <canvas id="bg-canvas" class="absolute top-0 left-0 w-full h-full outline-none z-10"></canvas>
60
 
61
- <!-- Loading Overlays -->
62
- <div id="loading-overlay" class="absolute inset-0 z-40 flex items-center justify-center opacity-0 pointer-events-none">
63
  <div class="spinner w-12 h-12 border-4 border-gray-600 rounded-full animate-spin"></div>
64
  </div>
65
  <div id="main-loader" class="absolute inset-0 bg-gray-900 flex flex-col items-center justify-center z-50 transition-opacity duration-500">
66
  <div class="w-16 h-16 border-4 border-dashed rounded-full animate-spin border-blue-500"></div>
67
- <p class="mt-4 text-xl tracking-wider">Loading Assets...</p>
68
  </div>
69
 
70
- <!-- Header -->
71
- <header class="absolute top-0 left-0 right-0 p-5 z-20">
72
- <h1 class="text-2xl font-bold" style="text-shadow: 0 2px 10px rgba(0,0,0,0.5);">3D Viewer</h1>
73
  </header>
74
 
75
- <!-- Left Panel (Models) -->
76
  <aside id="model-panel" class="side-panel fixed top-0 left-0 h-full w-72 max-w-[80vw] z-30 border-r">
77
  <div class="h-full flex flex-col">
78
- <h2 class="text-xl font-bold p-6 flex-shrink-0">Select Model</h2>
79
- <div id="model-selector" class="panel-content flex-grow overflow-y-auto px-4 space-y-2"></div>
 
 
 
 
 
80
  </div>
81
  </aside>
82
- <div id="model-panel-trigger" class="panel-trigger fixed top-1/4 left-0 h-20 w-8 z-20 flex items-center justify-center cursor-pointer rounded-r-lg border-t border-r border-b">
83
- <svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/></svg>
84
  </div>
85
 
86
- <!-- Right Panel (Panoramas) -->
87
  <aside id="panorama-panel" class="side-panel fixed top-0 right-0 h-full w-80 max-w-[80vw] z-30 border-l">
88
  <div class="h-full flex flex-col">
89
- <h2 class="text-xl font-bold p-6 flex-shrink-0">Select Panorama</h2>
90
- <div id="panorama-gallery" class="panel-content flex-grow overflow-y-auto px-6 grid grid-cols-2 gap-4"></div>
 
 
 
 
 
91
  </div>
92
  </aside>
93
- <div id="panorama-panel-trigger" class="panel-trigger fixed top-1/4 right-0 h-20 w-8 z-20 flex items-center justify-center cursor-pointer rounded-l-lg border-t border-l border-b">
94
- <svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l-1.586-1.586a2 2 0 00-2.828 0L6 14"/></svg>
95
  </div>
96
 
97
-
98
  <script type="importmap">{ "imports": { "three": "https://unpkg.com/three@0.160.0/build/three.module.js", "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/" } }</script>
99
 
100
  <script type="module">
@@ -106,6 +110,7 @@
106
  const modelFiles = [ 'gerbera.glb', 'shahed1.glb', 'shahed2.glb', 'shahed3.glb', 'supercam.glb', 'zala.glb', 'beaver.glb' ];
107
 
108
  let scene, camera, renderer, controls;
 
109
 
110
  const mainLoader = document.getElementById('main-loader');
111
  const loadingOverlay = document.getElementById('loading-overlay');
@@ -114,15 +119,12 @@
114
 
115
  async function init() {
116
  scene = new THREE.Scene();
117
- camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000);
118
- camera.position.set(0, 1.2, 4.5);
119
 
120
- renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('bg-canvas'), antialias: true });
121
  renderer.setPixelRatio(window.devicePixelRatio);
122
  renderer.setSize(window.innerWidth, window.innerHeight);
123
-
124
- // CRITICAL: Correct color space and tone mapping setup
125
- // This ensures the background panorama is not brightened and models are lit correctly.
126
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
127
  renderer.outputEncoding = THREE.sRGBEncoding;
128
 
@@ -130,18 +132,16 @@
130
  controls.enableDamping = true;
131
  controls.target.set(0, 1, 0);
132
 
133
- // Subtle lighting that affects models but doesn't over-brighten the scene
134
- scene.add(new THREE.AmbientLight(0xffffff, 0.4));
135
- const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
136
- dirLight.position.set(5, 10, 7);
137
  scene.add(dirLight);
138
 
139
  setupUI();
140
  setupEventListeners();
141
 
142
- await preloadAllModels();
143
  await setPanorama(panoramaFiles[0]);
144
- await switchModel(modelFiles[0]);
145
 
146
  mainLoader.style.opacity = '0';
147
  setTimeout(() => mainLoader.style.display = 'none', 500);
@@ -154,51 +154,59 @@
154
  return new Promise(resolve => {
155
  showLoadingOverlay(true);
156
  textureLoader.load(`/jpg/${imageName}`, texture => {
157
- // CRITICAL: Tell three.js the texture is in sRGB color space.
158
  texture.encoding = THREE.sRGBEncoding;
159
  texture.mapping = THREE.EquirectangularReflectionMapping;
160
-
161
  scene.background = texture;
162
- scene.environment = texture; // Use the same texture for model reflections/lighting
163
 
164
  document.querySelectorAll('#panorama-gallery .list-item').forEach(c => c.classList.remove('active'));
165
  document.querySelector(`[data-image="${imageName}"]`).classList.add('active');
166
 
167
  showLoadingOverlay(false);
168
  resolve();
 
 
 
169
  });
170
  });
171
  }
172
 
173
- function switchModel(modelName) {
174
  scene.children.forEach(child => {
175
  if (child.isGroup) child.visible = (child.name === modelName);
176
  });
177
  document.querySelectorAll('#model-selector .list-item').forEach(b => b.classList.toggle('active', b.dataset.model === modelName));
178
  }
179
 
180
- function preloadAllModels() {
181
- const modelPromises = modelFiles.map(fileName => new Promise(resolve => {
182
- gltfLoader.load(`/glb/${fileName}`, gltf => {
 
 
 
 
 
 
183
  const model = gltf.scene;
184
- model.name = fileName;
185
- model.visible = false;
186
-
187
  const box = new THREE.Box3().setFromObject(model);
188
  const center = box.getCenter(new THREE.Vector3());
189
  model.position.sub(center);
190
-
191
  const size = box.getSize(new THREE.Vector3());
192
  const maxDim = Math.max(size.x, size.y, size.z);
193
- const scale = 2.5 / maxDim;
194
  model.scale.set(scale, scale, scale);
195
  model.position.y += size.y * scale / 2;
196
-
197
  scene.add(model);
 
 
 
198
  resolve();
 
 
 
199
  });
200
- }));
201
- return Promise.all(modelPromises);
202
  }
203
 
204
  function setupUI() {
@@ -206,21 +214,21 @@
206
  modelFiles.forEach(fileName => {
207
  const btn = document.createElement('button');
208
  btn.textContent = fileName.replace('.glb', '').replace(/(\d)/, ' $1').toUpperCase();
209
- btn.className = 'list-item w-full text-left p-4 rounded-lg hover:bg-gray-700 transition-colors duration-200';
210
  btn.dataset.model = fileName;
211
- btn.onclick = () => switchModel(fileName);
212
  modelSelector.appendChild(btn);
213
  });
214
-
215
  const panoramaGallery = document.getElementById('panorama-gallery');
216
  panoramaFiles.forEach(fileName => {
217
  const container = document.createElement('div');
218
- container.className = 'list-item aspect-video rounded-lg overflow-hidden cursor-pointer transition-all ring-2 ring-transparent hover:ring-blue-500';
219
  container.dataset.image = fileName;
220
  container.onclick = () => setPanorama(fileName);
221
  const thumb = document.createElement('img');
222
  thumb.src = `/jpg/thumbnails/${fileName}`;
223
  thumb.className = 'w-full h-full object-cover';
 
224
  container.appendChild(thumb);
225
  panoramaGallery.appendChild(container);
226
  });
@@ -230,30 +238,21 @@
230
  const modelPanel = document.getElementById('model-panel');
231
  const panoramaPanel = document.getElementById('panorama-panel');
232
 
233
- document.getElementById('model-panel-trigger').addEventListener('click', (e) => {
234
- e.stopPropagation();
235
- modelPanel.classList.toggle('is-open');
236
- panoramaPanel.classList.remove('is-open');
237
- });
238
- document.getElementById('panorama-panel-trigger').addEventListener('click', (e) => {
239
- e.stopPropagation();
240
- panoramaPanel.classList.toggle('is-open');
241
- modelPanel.classList.remove('is-open');
242
- });
243
 
244
- // Close panels when clicking outside
245
- document.body.addEventListener('click', (e) => {
246
- if (!modelPanel.contains(e.target)) modelPanel.classList.remove('is-open');
247
- if (!panoramaPanel.contains(e.target)) panoramaPanel.classList.remove('is-open');
248
- });
249
  }
250
 
251
  function showLoadingOverlay(show) {
252
- if (show) {
253
- loadingOverlay.classList.remove('opacity-0', 'pointer-events-none');
254
- } else {
255
- loadingOverlay.classList.add('opacity-0', 'pointer-events-none');
256
- }
257
  }
258
 
259
  function onWindowResize() {
 
9
  @import url('https://rsms.me/inter/inter.css');
10
  html { font-family: 'Inter', sans-serif; }
11
 
12
+ body { background-color: #111827; }
13
+
14
+ .glass-ui {
15
+ background-color: rgba(17, 24, 39, 0.75);
16
+ backdrop-filter: blur(16px);
17
+ border-color: rgba(255, 255, 255, 0.1);
18
+ }
19
 
 
20
  #loading-overlay {
 
 
21
  transition: opacity 0.3s ease-in-out;
22
  }
23
  #loading-overlay .spinner {
24
+ border-top-color: #3b82f6;
25
  }
26
 
 
27
  .side-panel {
 
 
 
28
  transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
29
  }
30
  .side-panel.left { transform: translateX(-100%); }
31
  .side-panel.right { transform: translateX(100%); }
32
  .side-panel.is-open { transform: translateX(0); }
33
 
 
34
  .panel-trigger {
 
 
35
  transition: background-color 0.2s ease;
36
  }
37
  .panel-trigger:hover { background-color: rgba(59, 130, 246, 0.5); }
38
 
39
+ .list-item {
40
+ transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
41
+ }
42
+ .list-item:hover {
43
+ background-color: rgba(59, 130, 246, 0.1);
44
+ transform: translateX(4px);
45
+ }
46
  .list-item.active {
47
+ background-color: rgba(59, 130, 246, 0.25);
48
+ box-shadow: inset 4px 0 0 0 #3b82f6;
49
  }
50
 
 
51
  .panel-content::-webkit-scrollbar { width: 6px; }
52
  .panel-content::-webkit-scrollbar-track { background: transparent; }
53
  .panel-content::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2); border-radius: 10px; }
 
54
  </style>
55
  </head>
56
+ <body class="text-gray-100 select-none overflow-hidden">
57
 
 
58
  <canvas id="bg-canvas" class="absolute top-0 left-0 w-full h-full outline-none z-10"></canvas>
59
 
60
+ <div id="loading-overlay" class="glass-ui absolute inset-0 z-40 flex items-center justify-center opacity-0 pointer-events-none">
 
61
  <div class="spinner w-12 h-12 border-4 border-gray-600 rounded-full animate-spin"></div>
62
  </div>
63
  <div id="main-loader" class="absolute inset-0 bg-gray-900 flex flex-col items-center justify-center z-50 transition-opacity duration-500">
64
  <div class="w-16 h-16 border-4 border-dashed rounded-full animate-spin border-blue-500"></div>
65
+ <p class="mt-4 text-xl tracking-wider text-white">Initializing Viewer...</p>
66
  </div>
67
 
68
+ <header class="absolute top-0 left-0 right-0 p-5 z-20 glass-ui rounded-b-xl max-w-sm mx-auto border-b border-x">
69
+ <h1 class="text-xl font-bold text-white text-center">Immersive Viewer</h1>
 
70
  </header>
71
 
 
72
  <aside id="model-panel" class="side-panel fixed top-0 left-0 h-full w-72 max-w-[80vw] z-30 border-r">
73
  <div class="h-full flex flex-col">
74
+ <div class="flex-shrink-0 flex justify-between items-center p-5">
75
+ <h2 class="text-xl font-bold text-white">Models</h2>
76
+ <button id="close-model-panel" class="p-1 rounded-full text-gray-400 hover:bg-gray-700 hover:text-white transition-colors">
77
+ <svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>
78
+ </button>
79
+ </div>
80
+ <div id="model-selector" class="panel-content flex-grow overflow-y-auto px-2 space-y-1 pb-5"></div>
81
  </div>
82
  </aside>
83
+ <div id="model-panel-trigger" class="panel-trigger glass-ui fixed top-1/3 left-0 h-20 w-8 z-20 flex items-center justify-center cursor-pointer rounded-r-lg border-t border-r border-b">
84
+ <svg class="w-6 h-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h8m-8 6h16" /></svg>
85
  </div>
86
 
 
87
  <aside id="panorama-panel" class="side-panel fixed top-0 right-0 h-full w-80 max-w-[80vw] z-30 border-l">
88
  <div class="h-full flex flex-col">
89
+ <div class="flex-shrink-0 flex justify-between items-center p-5">
90
+ <h2 class="text-xl font-bold text-white">Panoramas</h2>
91
+ <button id="close-panorama-panel" class="p-1 rounded-full text-gray-400 hover:bg-gray-700 hover:text-white transition-colors">
92
+ <svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>
93
+ </button>
94
+ </div>
95
+ <div id="panorama-gallery" class="panel-content flex-grow overflow-y-auto px-4 grid grid-cols-2 gap-3 pb-5"></div>
96
  </div>
97
  </aside>
98
+ <div id="panorama-panel-trigger" class="panel-trigger glass-ui fixed top-1/3 right-0 h-20 w-8 z-20 flex items-center justify-center cursor-pointer rounded-l-lg border-t border-l border-b">
99
+ <svg class="w-6 h-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l-1.586-1.586a2 2 0 00-2.828 0L6 14" /></svg>
100
  </div>
101
 
 
102
  <script type="importmap">{ "imports": { "three": "https://unpkg.com/three@0.160.0/build/three.module.js", "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/" } }</script>
103
 
104
  <script type="module">
 
110
  const modelFiles = [ 'gerbera.glb', 'shahed1.glb', 'shahed2.glb', 'shahed3.glb', 'supercam.glb', 'zala.glb', 'beaver.glb' ];
111
 
112
  let scene, camera, renderer, controls;
113
+ const loadedModels = new Map();
114
 
115
  const mainLoader = document.getElementById('main-loader');
116
  const loadingOverlay = document.getElementById('loading-overlay');
 
119
 
120
  async function init() {
121
  scene = new THREE.Scene();
122
+ camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 2000);
123
+ camera.position.set(0, 1.5, 5);
124
 
125
+ renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('bg-canvas'), antialias: true, powerPreference: "high-performance" });
126
  renderer.setPixelRatio(window.devicePixelRatio);
127
  renderer.setSize(window.innerWidth, window.innerHeight);
 
 
 
128
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
129
  renderer.outputEncoding = THREE.sRGBEncoding;
130
 
 
132
  controls.enableDamping = true;
133
  controls.target.set(0, 1, 0);
134
 
135
+ scene.add(new THREE.AmbientLight(0xffffff, 0.5));
136
+ const dirLight = new THREE.DirectionalLight(0xffffff, 1.0);
137
+ dirLight.position.set(8, 15, 10);
 
138
  scene.add(dirLight);
139
 
140
  setupUI();
141
  setupEventListeners();
142
 
 
143
  await setPanorama(panoramaFiles[0]);
144
+ await loadAndSwitchModel(modelFiles[0]);
145
 
146
  mainLoader.style.opacity = '0';
147
  setTimeout(() => mainLoader.style.display = 'none', 500);
 
154
  return new Promise(resolve => {
155
  showLoadingOverlay(true);
156
  textureLoader.load(`/jpg/${imageName}`, texture => {
 
157
  texture.encoding = THREE.sRGBEncoding;
158
  texture.mapping = THREE.EquirectangularReflectionMapping;
 
159
  scene.background = texture;
160
+ scene.environment = texture;
161
 
162
  document.querySelectorAll('#panorama-gallery .list-item').forEach(c => c.classList.remove('active'));
163
  document.querySelector(`[data-image="${imageName}"]`).classList.add('active');
164
 
165
  showLoadingOverlay(false);
166
  resolve();
167
+ }, undefined, () => {
168
+ alert(`Error: Could not load panorama "${imageName}".`);
169
+ showLoadingOverlay(false);
170
  });
171
  });
172
  }
173
 
174
+ function switchActiveModel(modelName) {
175
  scene.children.forEach(child => {
176
  if (child.isGroup) child.visible = (child.name === modelName);
177
  });
178
  document.querySelectorAll('#model-selector .list-item').forEach(b => b.classList.toggle('active', b.dataset.model === modelName));
179
  }
180
 
181
+ function loadAndSwitchModel(modelName) {
182
+ return new Promise(resolve => {
183
+ if (loadedModels.has(modelName)) {
184
+ switchActiveModel(modelName);
185
+ resolve();
186
+ return;
187
+ }
188
+ showLoadingOverlay(true);
189
+ gltfLoader.load(`/glb/${modelName}`, gltf => {
190
  const model = gltf.scene;
191
+ model.name = modelName;
 
 
192
  const box = new THREE.Box3().setFromObject(model);
193
  const center = box.getCenter(new THREE.Vector3());
194
  model.position.sub(center);
 
195
  const size = box.getSize(new THREE.Vector3());
196
  const maxDim = Math.max(size.x, size.y, size.z);
197
+ const scale = 3.0 / maxDim;
198
  model.scale.set(scale, scale, scale);
199
  model.position.y += size.y * scale / 2;
 
200
  scene.add(model);
201
+ loadedModels.set(modelName, model);
202
+ switchActiveModel(modelName);
203
+ showLoadingOverlay(false);
204
  resolve();
205
+ }, undefined, () => {
206
+ alert(`Error: Could not load model "${modelName}".`);
207
+ showLoadingOverlay(false);
208
  });
209
+ });
 
210
  }
211
 
212
  function setupUI() {
 
214
  modelFiles.forEach(fileName => {
215
  const btn = document.createElement('button');
216
  btn.textContent = fileName.replace('.glb', '').replace(/(\d)/, ' $1').toUpperCase();
217
+ btn.className = 'list-item w-full text-left px-4 py-3 rounded-lg text-white';
218
  btn.dataset.model = fileName;
219
+ btn.onclick = () => loadAndSwitchModel(fileName);
220
  modelSelector.appendChild(btn);
221
  });
 
222
  const panoramaGallery = document.getElementById('panorama-gallery');
223
  panoramaFiles.forEach(fileName => {
224
  const container = document.createElement('div');
225
+ container.className = 'list-item aspect-video rounded-lg overflow-hidden cursor-pointer ring-2 ring-transparent';
226
  container.dataset.image = fileName;
227
  container.onclick = () => setPanorama(fileName);
228
  const thumb = document.createElement('img');
229
  thumb.src = `/jpg/thumbnails/${fileName}`;
230
  thumb.className = 'w-full h-full object-cover';
231
+ thumb.alt = `Thumbnail for ${fileName}`;
232
  container.appendChild(thumb);
233
  panoramaGallery.appendChild(container);
234
  });
 
238
  const modelPanel = document.getElementById('model-panel');
239
  const panoramaPanel = document.getElementById('panorama-panel');
240
 
241
+ const closeModelPanel = () => modelPanel.classList.remove('is-open');
242
+ const closePanoramaPanel = () => panoramaPanel.classList.remove('is-open');
243
+
244
+ document.getElementById('model-panel-trigger').addEventListener('click', e => { e.stopPropagation(); modelPanel.classList.toggle('is-open'); closePanoramaPanel(); });
245
+ document.getElementById('panorama-panel-trigger').addEventListener('click', e => { e.stopPropagation(); panoramaPanel.classList.toggle('is-open'); closeModelPanel(); });
 
 
 
 
 
246
 
247
+ document.getElementById('close-model-panel').addEventListener('click', closeModelPanel);
248
+ document.getElementById('close-panorama-panel').addEventListener('click', closePanoramaPanel);
249
+
250
+ document.getElementById('bg-canvas').addEventListener('click', () => { closeModelPanel(); closePanoramaPanel(); });
 
251
  }
252
 
253
  function showLoadingOverlay(show) {
254
+ loadingOverlay.style.opacity = show ? '1' : '0';
255
+ loadingOverlay.style.pointerEvents = show ? 'auto' : 'none';
 
 
 
256
  }
257
 
258
  function onWindowResize() {