<script> (function() { /* ==== SINGLETON GUARD ==== */ if (window.__WAO_FB_ACTIVE) { console && console.warn && console.warn('[wao-fb] already active; skipping.'); return; } window.__WAO_FB_ACTIVE = true; /* ===================== CONFIG ===================== */ var POI_SELECTOR = '.poi_embed'; // Keep the same "room switch" selectors for autoclose var ROOM_SWITCH_SELECTORS = [ '.rooms_alt', 'li.pointer_list', '.list_alt_menu .dropdown p', '.controls_btn', '.arrows_nav .next_arrow', '.arrows_nav .prev_arrow', '.fa-chevron-left', '.fa-chevron-right', 'a[href*="#scene"]' ]; // Auto-rotate (independent of opening) var AUTOROTATE_ENABLED = false; // toggle with WAO_Fancybox.toggleRotate(true/false) var ROTATE_INTERVAL_MS = 50; // WAO_Fancybox.setInterval(ms) var MIN_INTERVAL_MS = 1; var FIRST_STEP_DELAY_MS = 6000; // delay before first step after a new Fancybox instance appears var SWITCH_STEP_DELAY_MS = 6000; // not used for opening now, but kept for API parity // Manual control pause (only real user input) var USER_SUSPEND_AFTER_MS = 6000; // Crossfade blend between internal frames inside Fancybox var BLEND_ENABLED = false; // WAO_Fancybox.enableBlend(true/false) var BLEND_MS = 100; // ms var HIDE_POI = true; // keep POIs always hidden var DEBUG = false; /* ===================== utils ===================== */ function log() { if (DEBUG) { var a = [].slice.call(arguments); a.unshift('[wao-fb]'); console.log.apply(console, a); } } function now() { return Date.now(); } function hasLayout(el) { if (!el) return false; var s = el.ownerDocument.defaultView.getComputedStyle(el); if (s.display === 'none' || s.visibility === 'hidden') return false; var r = el.getBoundingClientRect(); return r.width > 0 && r.height > 0; } /* ===================== Fancybox ctx ===================== */ function fbContext() { function tryWin(w) { try { // v5 if (w.Fancybox && w.Fancybox.getInstance) { var i5 = w.Fancybox.getInstance(); if (i5) return { win: w, ver: 'v5', inst: i5, close: function() { w.Fancybox.close(); }, key: function() { return 'v5|' + (i5.id || i5.state || 0); }, slideRoot: function() { return w.document.querySelector('.fancybox__container .fancybox__slide.is-selected'); }, contentRoot: function() { return w.document.querySelector('.fancybox__container .fancybox__slide.is-selected .fancybox__content') || w.document.querySelector('.fancybox__container .fancybox__slide.is-selected') || w.document.querySelector('.fancybox__container'); } }; } // v3 if (w.jQuery && w.jQuery.fancybox && w.jQuery.fancybox.getInstance) { var i3 = w.jQuery.fancybox.getInstance(); if (i3) return { win: w, ver: 'v3', inst: i3, close: function() { w.jQuery.fancybox.close(true); }, key: function() { return 'v3|' + (i3.currIndex + ':' + (i3.group ? i3.group.length : 0)); }, slideRoot: function() { return w.document.querySelector('.fancybox-container .fancybox-slide--current'); }, contentRoot: function() { return w.document.querySelector('.fancybox-container .fancybox-slide--current .fancybox-content') || w.document.querySelector('.fancybox-wrap') || w.document.body; } }; } // v2 (legacy) if (w.jQuery && w.jQuery.fancybox && typeof w.jQuery.fancybox.getInstance === 'undefined') { var open = !!(w.jQuery.fancybox.isOpen) || !!w.document.querySelector('#fancybox-wrap,.fancybox-wrap,#fancybox-overlay,.fancybox-overlay'); if (open) return { win: w, ver: 'v2', inst: null, close: function() { if (w.jQuery && w.jQuery.fancybox && w.jQuery.fancybox.close) w.jQuery.fancybox.close(true); }, key: function() { return 'v2|' + (w.document.querySelector('#fancybox-wrap,.fancybox-wrap') ? 'wrap' : 'overlay'); }, slideRoot: function() { return w.document.querySelector('#fancybox-wrap') || w.document.querySelector('.fancybox-wrap') || w.document.body; }, contentRoot: function() { return w.document.querySelector('#fancybox-wrap .fancybox-inner') || w.document.querySelector('.fancybox-wrap') || w.document.body; } }; } } catch (e) {} return null; } var ctx = tryWin(window); if (ctx) return ctx; var ifrs = document.querySelectorAll('iframe'); for (var i = 0; i < ifrs.length; i++) { var w = ifrs[i].contentWindow; var c = tryWin(w); if (c) return c; } return null; } function closeFancybox() { var ctx = fbContext(); if (!ctx) return false; try { ctx.close && ctx.close(); } catch (e) {} log('autoclose: requested'); return true; } /* ===================== BLEND helper ===================== */ function makeBlendOverlay(ctx) { if (!BLEND_ENABLED || !BLEND_MS) return null; try { var w = ctx.win, d = w.document; var root = ctx.contentRoot && ctx.contentRoot() || d; // Prefer the largest visible IMG (fastest & sharpest) var img = [].slice.call(root.querySelectorAll('img')).filter(hasLayout).sort(function(a, b) { var ra = a.getBoundingClientRect(), rb = b.getBoundingClientRect(); return (rb.width * rb.height) - (ra.width * ra.height); })[0]; var overlay, child; if (img) { var r = img.getBoundingClientRect(); overlay = d.createElement('div'); overlay.style.cssText = 'position:fixed;left:' + r.left + 'px;top:' + r.top + 'px;width:' + r.width + 'px;height:' + r.height + 'px;' + 'pointer-events:none;z-index:2147483647;opacity:1;transition:opacity ' + BLEND_MS + 'ms linear;'; child = img.cloneNode(true); child.style.cssText = 'width:100%;height:100%;object-fit:contain;display:block;'; overlay.appendChild(child); d.body.appendChild(overlay); return function cleanup() { requestAnimationFrame(function() { overlay.style.opacity = '0'; setTimeout(function() { try { overlay.remove(); } catch (e) {} }, BLEND_MS + 80); }); }; } // Fallback: canvas snapshot (best-effort) var cvs = [].slice.call(root.querySelectorAll('canvas')).filter(hasLayout)[0]; if (cvs) { try { var data = cvs.toDataURL('image/png'); var r2 = cvs.getBoundingClientRect(); overlay = d.createElement('div'); overlay.style.cssText = 'position:fixed;left:' + r2.left + 'px;top:' + r2.top + 'px;width:' + r2.width + 'px;height:' + r2.height + 'px;' + 'pointer-events:none;z-index:2147483647;opacity:1;transition:opacity ' + BLEND_MS + 'ms linear;'; child = d.createElement('img'); child.src = data; child.style.cssText = 'width:100%;height:100%;object-fit:contain;display:block;'; overlay.appendChild(child); d.body.appendChild(overlay); return function cleanup() { requestAnimationFrame(function() { overlay.style.opacity = '0'; setTimeout(function() { try { overlay.remove(); } catch (e) {} }, BLEND_MS + 80); }); }; } catch (e) {} } } catch (e) {} return null; } /* ===================== advance inside content ===================== */ function advanceInsideContent(ctx) { if (!ctx) return false; var w = ctx.win, d = ctx.win.document; var root = ctx.contentRoot && ctx.contentRoot(); if (!root) root = d; var $ = w.jQuery; // Prepare crossfade overlay of the *current* frame BEFORE advancing var cleanupBlend = makeBlendOverlay(ctx); try { if ($) { var frames = $(root).find('.frame, .sly-frame'); for (var i = 0; i < frames.length; i++) { var inst = $(frames[i]).data('sly'); if (inst && typeof inst.next === 'function') { inst.next(); if (cleanupBlend) cleanupBlend(); log('rotate: Sly.next()'); return true; } } } } catch (e) {} try { if ($) { var slicks = $(root).find('.slick-slider.slick-initialized'); if (slicks.length) { $(slicks[0]).slick('slickNext'); if (cleanupBlend) cleanupBlend(); log('rotate: Slick.slickNext()'); return true; } } } catch (e) {} try { var sw = root.querySelector('.swiper, .swiper-container'); if (sw && sw.swiper && typeof sw.swiper.slideNext === 'function') { sw.swiper.slideNext(); if (cleanupBlend) cleanupBlend(); log('rotate: Swiper.slideNext()'); return true; } } catch (e) {} try { if ($) { var owls = $(root).find('.owl-carousel'); if (owls.length) { $(owls[0]).trigger('next.owl.carousel'); if (cleanupBlend) cleanupBlend(); log('rotate: OwlCarousel next'); return true; } } } catch (e) {} try { var glide = root.querySelector('.glide'); if (glide && glide._glide && glide._glide.go) { glide._glide.go('>'); if (cleanupBlend) cleanupBlend(); log('rotate: Glide.go(">")'); return true; } } catch (e) {} try { var spl = root.querySelector('.splide'); if (spl && spl.splide && spl.splide.go) { spl.splide.go('+'); if (cleanupBlend) cleanupBlend(); log('rotate: Splide.go("+")'); return true; } } catch (e) {} try { var nextBtn = root.querySelector( '.next, .btn-next, .arrow-right, .arrow_next, .fa-chevron-right, .fa-angle-right, [data-action="next"], [data-next], .carousel-control-next, .carousel__next, .slider-next, .swiper-button-next, .slick-next' ); if (nextBtn) { nextBtn.dispatchEvent(new w.MouseEvent('click', { bubbles: true, cancelable: true, view: w })); if (cleanupBlend) cleanupBlend(); log('rotate: clicked internal next button'); return true; } } catch (e) {} // Synthetic drag fallback (do NOT touch isTrusted) try { var candidates = [].slice.call(root.querySelectorAll('img, canvas, div')).filter(function(el) { if (!el) return false; var cs = w.getComputedStyle(el); if (cs.display === 'none' || cs.visibility === 'hidden' || cs.pointerEvents === 'none') return false; var r = el.getBoundingClientRect(); return r.width > 80 && r.height > 80; }).sort(function(a, b) { var ra = a.getBoundingClientRect(), rb = b.getBoundingClientRect(); return (rb.width * rb.height) - (ra.width * ra.height); }); var target = candidates[0] || root; var r = target.getBoundingClientRect(); var cx = r.left + r.width / 2; var cy = r.top + r.height / 2; function fire(type, x, y) { try { var e = new w.MouseEvent(type, { bubbles: true, cancelable: true, view: w, clientX: Math.round(x), clientY: Math.round(y), screenX: Math.round(x), screenY: Math.round(y), buttons: 1 }); target.dispatchEvent(e); } catch (err) {} } var dx = Math.max(12, Math.min(26, Math.round(r.width * 0.04))); fire('mousedown', cx, cy); fire('mousemove', cx - dx, cy); fire('mouseup', cx - dx, cy); fire('click', cx - dx, cy); try { var wheel = new w.WheelEvent('wheel', { bubbles: true, cancelable: true, deltaX: -40, deltaY: 0 }); target.dispatchEvent(wheel); } catch (e) {} if (cleanupBlend) cleanupBlend(); log('rotate: synthetic drag'); return true; } catch (e) {} if (cleanupBlend) cleanupBlend(); return false; } /* ===================== user input pause (trusted only) ===================== */ var suspendUntilTs = 0; var pointerDown = false; function isSuspended() { if (pointerDown) return true; return now() < suspendUntilTs; } function bumpSuspend(ms) { suspendUntilTs = Math.max(suspendUntilTs, now() + (ms || USER_SUSPEND_AFTER_MS)); } function guard(handler) { return function(ev) { // Only real user events should pause; ignore synthetic (isTrusted === false or undefined) if (!ev || ev.isTrusted !== true) return; handler(ev); }; } function wireInputGuardsFor(ctx) { if (!ctx) return; var w = ctx.win, d = w.document, slide = ctx.slideRoot && ctx.slideRoot(); var root = slide || d; if (!root || root.__wao_input_wired) return; root.__wao_input_wired = true; root.addEventListener('wheel', guard(function(){ bumpSuspend(USER_SUSPEND_AFTER_MS); }), { passive: true }); root.addEventListener('keydown', guard(function(){ bumpSuspend(USER_SUSPEND_AFTER_MS); }), true); root.addEventListener('mousedown', guard(function(){ pointerDown = true; bumpSuspend(USER_SUSPEND_AFTER_MS); }), true); root.addEventListener('mouseup', guard(function(){ pointerDown = false; bumpSuspend(USER_SUSPEND_AFTER_MS); }), true); root.addEventListener('mousemove', guard(function(ev){ if (ev.buttons) { pointerDown = true; bumpSuspend(USER_SUSPEND_AFTER_MS); } }), true); root.addEventListener('touchstart',guard(function(){ pointerDown = true; bumpSuspend(USER_SUSPEND_AFTER_MS); }), { passive: true }); root.addEventListener('touchend', guard(function(){ pointerDown = false; bumpSuspend(USER_SUSPEND_AFTER_MS); }), { passive: true }); w.addEventListener('blur', function(){ pointerDown = false; }, true); } /* ===================== HEARTBEAT (auto-rotate only) ===================== */ var lastKey = null; var lastAdvanceAt = 0; var nextAllowedAt = 0; // when first next() is allowed after FB context change var HEARTBEAT_MS = 10; // fast pulse; pacing comes from ROTATE_INTERVAL_MS function armFirstDelay(ms) { nextAllowedAt = now() + (ms || 0); } function beat() { var effectiveInterval = Math.max(MIN_INTERVAL_MS, ROTATE_INTERVAL_MS); var ctx = fbContext(); if (!ctx || !AUTOROTATE_ENABLED) { lastKey = null; lastAdvanceAt = 0; return; } try { wireInputGuardsFor(ctx); } catch (e) {} var key = ctx.key ? ctx.key() : 'ctx'; if (key !== lastKey) { lastKey = key; lastAdvanceAt = 0; armFirstDelay(FIRST_STEP_DELAY_MS); pointerDown = false; suspendUntilTs = 0; log('rotate: armed for', key, 'first step after', FIRST_STEP_DELAY_MS, 'ms'); } var t = now(); if (!lastAdvanceAt) lastAdvanceAt = t; if (t < nextAllowedAt) return; if (isSuspended()) return; if (t - lastAdvanceAt >= effectiveInterval) { var live = fbContext(); if (live && AUTOROTATE_ENABLED && !isSuspended()) { try { advanceInsideContent(live); } catch (e) {} } lastAdvanceAt = now(); } } if (!window.__wao_fb_heartbeat) window.__wao_fb_heartbeat = setInterval(beat, HEARTBEAT_MS); /* ===================== Autoclose on room switch ===================== */ document.addEventListener('click', function(e) { var path = e.composedPath ? e.composedPath() : (function p(n){var a=[]; while(n){a.push(n); n=n.parentNode;} a.push(window); return a;})(e.target); var isSwitch = ROOM_SWITCH_SELECTORS.some(function(sel) { return path.some(function(n) { return n.matches && n.matches(sel); }); }); if (isSwitch) { log('autoclose: room switch detected'); closeFancybox(); // <-- keep original behavior: close on switch } }, true); /* ===================== Boot (POI hide only) ===================== */ if (HIDE_POI) { var style = document.createElement('style'); style.textContent = POI_SELECTOR + '{opacity:0 !important;} ' + POI_SELECTOR + ' *{opacity:0 !important;}'; document.head.appendChild(style); } /* ===== Console API (no auto-open here) ===== */ window.WAO_Fancybox = window.WAO_Fancybox || {}; window.WAO_Fancybox.toggleRotate = function(on) { AUTOROTATE_ENABLED = !!on; return AUTOROTATE_ENABLED; }; window.WAO_Fancybox.setInterval = function(ms) { ROTATE_INTERVAL_MS = Math.max(MIN_INTERVAL_MS, +ms || ROTATE_INTERVAL_MS); return ROTATE_INTERVAL_MS; }; window.WAO_Fancybox.setFirstDelay = function(ms) { FIRST_STEP_DELAY_MS = Math.max(0, +ms || 0); SWITCH_STEP_DELAY_MS = FIRST_STEP_DELAY_MS; return { first: FIRST_STEP_DELAY_MS, postSwitch: SWITCH_STEP_DELAY_MS }; }; window.WAO_Fancybox.setSwitchDelay = function(ms) { SWITCH_STEP_DELAY_MS = Math.max(0, +ms || 0); return SWITCH_STEP_DELAY_MS; }; window.WAO_Fancybox.setSuspend = function(ms) { USER_SUSPEND_AFTER_MS = Math.max(50, +ms || USER_SUSPEND_AFTER_MS); return USER_SUSPEND_AFTER_MS; }; window.WAO_Fancybox.enableBlend = function(on) { BLEND_ENABLED = !!on; return BLEND_ENABLED; }; window.WAO_Fancybox.setBlendMs = function(ms) { BLEND_MS = Math.max(16, +ms || BLEND_MS); return BLEND_MS; }; window.WAO_Fancybox.pause = function() { AUTOROTATE_ENABLED = false; return true; }; window.WAO_Fancybox.resume = function() { AUTOROTATE_ENABLED = true; return true; }; window.WAO_Fancybox.debug = function(on) { DEBUG = !!on; return DEBUG; }; window.WAO_Fancybox.status = function() { var ctx = fbContext(); return { enabled: AUTOROTATE_ENABLED, intervalMs: ROTATE_INTERVAL_MS, minIntervalMs: MIN_INTERVAL_MS, firstStepDelayMs: FIRST_STEP_DELAY_MS, switchStepDelayMs: SWITCH_STEP_DELAY_MS, suspendMsRemaining: Math.max(0, suspendUntilTs - now()), pointerDown: pointerDown, blend: { enabled: BLEND_ENABLED, ms: BLEND_MS }, hasContext: !!ctx, version: ctx ? ctx.ver : null, key: ctx && ctx.key ? ctx.key() : null }; }; })(); </script> <script> (function() { if (window.__WAO_UI_ACTIVE) return; window.__WAO_UI_ACTIVE = true; /* === CONFIG === */ var BLOCK_ESC = true; // prevent ESC from closing Fancybox /* ===== Detect if Fancybox is open ===== */ function fancyboxOpen() { try { if (window.Fancybox && window.Fancybox.getInstance()) return true; } catch (e) {} try { if (window.jQuery && window.jQuery.fancybox && window.jQuery.fancybox.isOpen) return true; } catch (e) {} var d = document; if (d.querySelector('.fancybox__container, .fancybox-container')) return true; // v5/v3 if (d.querySelector('#fancybox-wrap, .fancybox-wrap, #fancybox-overlay, .fancybox-overlay')) return true; // v2 return false; } function escGuard(e) { if (!BLOCK_ESC) return; if ((e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27) && fancyboxOpen()) { e.preventDefault(); e.stopImmediatePropagation(); e.stopPropagation(); } } // Capture-phase so we intercept before Fancybox window.addEventListener('keydown', escGuard, true); // Optional: tiny UI API window.WAO_UI = { setBlockEsc: function(on) { BLOCK_ESC = !!on; return BLOCK_ESC; } }; })(); </script>
-
Average Rating
- comments