/
Samyouright
/
itdgallery
Обзор
Документация
Войти
/
Samyouright
/
itdgallery
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
index.html
508 строк
17 KB
Samyouright
create: .gitignore, ARCHITECTURE.md, building_v2.json, index.html, LICENSE, README.md
25 апр 2026, 11:40
Верифицирован
25 апр 2026, 11:40
1b0cd58
Код
Авторство
О чём код?
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Virtual 3D Art Gallery | OpenVGal by LB Artworks</title> <meta name="description" content="Explore a stunning 3D virtual tour of your art gallery. Experience interactive WebGL visuals with OpenVGal immersive 3D environment."> <meta name="keywords" content="3D virtual gallery, WebGL environment, interactive 3D model, 3D virtual photo gallery, 3D virtual art gallery"> <meta name="robots" content="index, follow"> <link rel="canonical" href="https://openvgal.com/"> <!-- Open Graph --> <meta property="og:title" content="Interactive 3D Virtual Gallery | OpenVGAL"> <meta property="og:description" content="Step into a fully immersive 3D environment. Explore our WebGL-powered virtual tour of an art gallery."> <meta property="og:image" content="https://openvgal.com/preview.jpg"> <meta property="og:url" content="https://openvgal.com/"> <meta property="og:type" content="website"> <!-- Twitter Card --> <meta name="twitter:card" content="summary_large_image"> <meta name="twitter:title" content="3D Virtual Gallery | OpenVGAL"> <meta name="twitter:description" content="Experience an interactive 3D virtual gallery powered by WebGL. Explore now!"> <meta name="twitter:image" content="https://openvgal.com/preview.jpg"> <!-- Structured Data --> <script type="application/ld+json"> { "@context": "https://schema.org", "@type": "VirtualTour", "name": "3D Virtual Gallery", "description": "An interactive WebGL-based 3D virtual gallery by OpenVGAL.", "url": "https://openvgal.com/", "image": "https://openvgal.com/preview.jpg", "provider": { "@type": "Organization", "name": "OpenVGAL", "url": "https://openvgal.com" } } </script> <script> <!--/////////////////////// CUSTOMIZE THIS VALUE //////////////////////////////////--> window.openvgal_location = '.'; <!--/////////////////////// CUSTOMIZE THIS VALUE //////////////////////////////////--> // Amend header values with your choice. Only proceed if base is set and valid if (window.openvgal_location && window.openvgal_location.trim() !== '') { const baseUrl = window.openvgal_location; const fullPageUrl = baseUrl + '/virtual.html'; const imageUrl = baseUrl + '/preview.jpg'; const providerUrl = baseUrl.replace('/virtual', ''); // Set canonical const canonical = document.querySelector('link[rel="canonical"]'); if (canonical) canonical.href = fullPageUrl; // Set Open Graph const ogUrl = document.querySelector('meta[property="og:url"]'); if (ogUrl) ogUrl.content = fullPageUrl; const ogImage = document.querySelector('meta[property="og:image"]'); if (ogImage) ogImage.content = imageUrl; // Set Twitter const twitterImage = document.querySelector('meta[name="twitter:image"]'); if (twitterImage) twitterImage.content = imageUrl; // Set Structured Data const structuredData = document.querySelector('script[type="application/ld+json"]'); if (structuredData) { try { const data = JSON.parse(structuredData.textContent); data['@id'] = fullPageUrl; data.url = fullPageUrl; data.image = imageUrl; data.provider.url = providerUrl; structuredData.textContent = JSON.stringify(data, null, 2); } catch (e) { console.warn('Failed to update structured data:', e); } } }; </script> <style> html, body { overflow: hidden; width: 100%; height: 100%; margin: 0; padding: 0; } #renderCanvas { width: 100%; height: 100%; touch-action: none; } #loader { width: 100%; height: 100%; background: #000; margin: auto; display: flex; justify-content: center; align-items: center; font-family: 'Inter', system-ui, -apple-system, sans-serif; } #loader .loader-card { background: #18181b; border: 1px solid #27272a; border-radius: 16px; padding: 2rem 2.5rem; display: flex; flex-direction: column; gap: 0.2rem; min-width: 320px; width: min(420px, 80vw); } #loader p { color: #a1a1aa; font-size: 0.85rem; font-weight: 500; letter-spacing: 0.05em; text-transform: uppercase; margin: 0; padding-top: 1rem; } #loader .percent-text { color: #d4d4d8; font-size: 0.8rem; font-variant-numeric: tabular-nums; margin: 0; padding-top: 0.25rem; } #loaded { opacity: 0; transition: opacity 1s ease; } .loadingContainer { width: 100%; height: 12px; background: #27272a; border-radius: 6px; overflow: hidden; margin-top: 0.3rem; } .loadingBar { height: 100%; background: linear-gradient(90deg, #818cf8, #a78bfa); border-radius: 6px; width: 0%; } /* Navigation Map */ #map-button { position: fixed; top: 20px; left: 20px; z-index: 25; width: 44px; height: 44px; border-radius: 50%; border: 1px solid rgba(255, 255, 255, 0.15); background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); color: #d4d4d8; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.2s, border-color 0.2s, color 0.2s; } #map-button:hover { background: rgba(99, 102, 241, 0.15); border-color: rgba(99, 102, 241, 0.4); color: #a5b4fc; } #map-modal { display: none; position: fixed; inset: 0; z-index: 40; background: rgba(0, 0, 0, 0.92); backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px); flex-direction: column; align-items: center; padding: 0; overflow-y: auto; font-family: 'Inter', system-ui, -apple-system, sans-serif; } #map-header { width: 100%; max-width: 900px; display: flex; justify-content: space-between; align-items: center; padding: 24px 32px 8px; box-sizing: border-box; } #map-title { color: #fafafa; font-size: 1.25rem; font-weight: 600; letter-spacing: -0.01em; } #map-close { background: none; border: none; color: #71717a; font-size: 1.75rem; cursor: pointer; padding: 4px 8px; line-height: 1; transition: color 0.2s; } #map-close:hover { color: #fafafa; } #map-grid { width: 100%; max-width: 960px; display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 18px; padding: 16px 32px 40px; box-sizing: border-box; } .map-card { background: rgba(255, 255, 255, 0.04); border: 1px solid rgba(255, 255, 255, 0.07); border-radius: 14px; cursor: pointer; overflow: hidden; transition: transform 0.25s ease, border-color 0.25s, box-shadow 0.3s; } .map-card:hover { transform: translateY(-4px) scale(1.015); border-color: rgba(99, 102, 241, 0.45); box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(99, 102, 241, 0.15); } .map-card.map-card-active { border-color: #6366f1; box-shadow: 0 0 0 1px #6366f1, 0 6px 20px rgba(99, 102, 241, 0.2); } .map-card-thumb-wrap { position: relative; overflow: hidden; } .map-card-thumb-wrap::after { content: ''; position: absolute; bottom: 0; left: 0; right: 0; height: 40%; background: linear-gradient(to top, rgba(0,0,0,0.5), transparent); pointer-events: none; } .map-card-thumb { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; display: block; background: #18181b; transition: transform 0.3s ease; } .map-card:hover .map-card-thumb { transform: scale(1.05); } .map-card-placeholder { width: 100%; aspect-ratio: 3 / 2; background: linear-gradient(135deg, #18181b 0%, #27272a 50%, #1e1b4b 100%); display: flex; align-items: center; justify-content: center; } .map-card-info { padding: 12px 14px 14px; text-align: center; } .map-card-name { color: #e4e4e7; font-size: 0.875rem; font-weight: 500; margin: 0 0 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .map-card-count { color: #71717a; font-size: 0.75rem; font-weight: 400; margin: 0; } @media screen and (max-width: 600px) { #map-button { top: 12px; left: 12px; width: 38px; height: 38px; } #map-header { padding: 16px 16px 4px; } #map-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; padding: 10px 16px 24px; } .map-card-info { padding: 8px 10px 10px; } .map-card-name { font-size: 0.8rem; } } </style> <script src="babylon.js"></script> <script src="babylonjs.loaders.min.js"></script> <script src="https://code.jquery.com/pep/0.4.3/pep.js"></script> <!-- uncomment if you want to enable the functionality of saving as glb in the debug menu <script src="https://preview.babylonjs.com/serializers/babylonjs.serializers.min.js"></script> --> <script src="https://unpkg.com/earcut@2.1.1/dist/earcut.min.js"></script> <link rel="stylesheet" href="overlay.css"> <script> // Preview mode detection - must run before declarations.js // To use local templates/materials instead of CDN, set: window.openvgal_cdn_base = ''; (function() { const urlParams = new URLSearchParams(window.location.search); if (urlParams.get('preview') === 'iframe' && window.parent) { window.openvgal_preview_mode = true; window.openvgal_cdn_base = window.parent.openvgal_cdn_base || window.location.origin; console.log('Preview mode: CDN base =', window.openvgal_cdn_base); } })(); </script> </head> <body> <div id="loader"> <div class="loader-card"> <p>Loading template</p> <div class="loadingContainer"> <div class="loadingBar" id="loadingBar_template"></div> </div> <p class="percent-text" id="percentLoaded_template">0%</p> <p>Loading materials</p> <div class="loadingContainer"> <div class="loadingBar" id="loadingBar_materials"></div> </div> <p class="percent-text" id="percentLoaded_materials">0%</p> <p>Loading artworks</p> <div class="loadingContainer"> <div class="loadingBar" id="loadingBar_artwork"></div> </div> <p class="percent-text" id="percentLoaded_artwork">0%</p> </div> </div> <script src='declarations.js'></script> <canvas id="renderCanvas" touch-action="none"></canvas> <script src='room_builder_aux.js'></script> <script src='openvgal-lighting.js'></script> <script> // Load overlay.js: local first, CDN fallback (function() { var s = document.createElement('script'); s.src = 'overlay.js'; s.onerror = function() { if (typeof cdn_base !== 'undefined' && cdn_base) { var f = document.createElement('script'); f.src = cdn_base + '/core/overlay.js'; document.body.appendChild(f); } }; document.body.appendChild(s); })(); </script> <script src='openvgal-viewer.js'></script> <!-- Navigation Map --> <button id="map-button" onclick="toggleMap()" title="Gallery Map" style="display:none;"> <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <rect x="3" y="3" width="7" height="7" rx="1"/> <rect x="14" y="3" width="7" height="7" rx="1"/> <rect x="3" y="14" width="7" height="7" rx="1"/> <rect x="14" y="14" width="7" height="7" rx="1"/> </svg> </button> <div id="map-modal"> <div id="map-header"> <span id="map-title">Gallery Map</span> <button id="map-close" onclick="toggleMap()">×</button> </div> <div id="map-grid"></div> </div> <script> function toggleMap() { var modal = document.getElementById('map-modal'); if (!modal) return; var isHidden = modal.style.display === 'none' || modal.style.display === ''; if (isHidden) { populateMap(); modal.style.display = 'flex'; } else { modal.style.display = 'none'; } } function populateMap() { var grid = document.getElementById('map-grid'); if (!grid || typeof config_file_content === 'undefined') return; grid.innerHTML = ''; for (var galleryName in config_file_content) { if (galleryName === 'Technical') continue; var gallery = config_file_content[galleryName]; var imageKeys = Object.keys(gallery).filter(function(k) { return gallery[k] && gallery[k].resource_type === 'image'; }); var card = document.createElement('div'); card.className = 'map-card'; if (galleryName === current_gallery) card.className += ' map-card-active'; card.onclick = (function(name) { return function() { navigateToGallery(name); }; })(galleryName); var wrap = document.createElement('div'); wrap.className = 'map-card-thumb-wrap'; if (imageKeys.length > 0) { var img = document.createElement('img'); img.className = 'map-card-thumb'; var resourcePath = gallery[imageKeys[0]].resource; img.src = (typeof window.resolveImageUrl === 'function') ? window.resolveImageUrl(resourcePath) : hallspics_prefix + resourcePath; img.alt = galleryName; img.loading = 'lazy'; wrap.appendChild(img); } else { var placeholder = document.createElement('div'); placeholder.className = 'map-card-placeholder'; wrap.appendChild(placeholder); } card.appendChild(wrap); var info = document.createElement('div'); info.className = 'map-card-info'; var nameEl = document.createElement('p'); nameEl.className = 'map-card-name'; nameEl.textContent = galleryName === 'root' ? 'Main Hall' : galleryName.replace(/#/g, ' '); info.appendChild(nameEl); var countEl = document.createElement('p'); countEl.className = 'map-card-count'; countEl.textContent = imageKeys.length + (imageKeys.length === 1 ? ' artwork' : ' artworks'); info.appendChild(countEl); card.appendChild(info); grid.appendChild(card); } } function navigateToGallery(name) { var params = new URLSearchParams(window.location.search); params.set('gallery', name); window.location.search = params.toString(); } (function waitForConfig() { if (typeof config_file_content !== 'undefined' && config_file_content) { document.getElementById('map-button').style.display = 'flex'; } else { setTimeout(waitForConfig, 200); } })(); </script> </body> </html>