/* QF Tab Vimeo v1.4.0 (2026-10-26, page speed: the player loads only after the page has finished loading AND when its tab is active AND within 300px of the screen; v1.3.0 loaded it as soon as the tab counted as active, which on phones meant at page start, about 490 KB of Vimeo code) Plays a Vimeo video in a tab item when its "Vimeo URL" field is filled. Field accepts: 123456789 | 123456789/hash | any vimeo.com or player.vimeo.com link. Muted, looping, no controls. Plays only while its tab is active and on screen. Either/or with the Lottie URL: a filled Vimeo field disables the Lottie in that slot. The first-frame image is the poster until frames play; on failure the image stays. Reduced-motion visitors keep the image. */ (function () { if (window.__qfTabVimeo) return; window.__qfTabVimeo = true; var css = document.createElement('style'); css.textContent = // A tab item uses EITHER a Vimeo ID or a Lottie URL. With a Vimeo ID the // Lottie is ignored and the first-frame image is the poster (and fallback). '.media-tab-graphic.has-qf-vimeo{position:relative;width:100%}' + '.media-tab-graphic.has-qf-vimeo .lottie-card,' + '.media-tab-graphic.has-qf-vimeo .lottie-toggle:not(.qf-vimeo-toggle){display:none!important}' + '.media-tab-graphic.has-qf-vimeo > img{display:block!important;width:100%!important;height:auto;aspect-ratio:var(--qf-vimeo-ratio,1.6);object-fit:cover}' + '.media-tab-graphic.is-vimeo-ready > img{visibility:hidden!important}' + '.qf-vimeo-stage{position:absolute;inset:0;z-index:1;overflow:hidden;opacity:0;pointer-events:none;transition:opacity .3s}' + '.media-tab-graphic.is-vimeo-ready .qf-vimeo-stage{opacity:1}' + '.qf-vimeo-stage iframe{position:absolute;inset:0;width:100%;height:100%;border:0;pointer-events:none}' + '.qf-vimeo-toggle{position:absolute;right:12px;bottom:12px;z-index:2;width:44px;height:44px;padding:6px;border:0;border-radius:50%;background:transparent;cursor:pointer}' + '.qf-vimeo-toggle img{display:block;width:32px;height:32px;pointer-events:none}' + '.qf-vimeo-toggle:focus-visible{outline:2px solid #142936;outline-offset:3px}'; document.head.appendChild(css); function parse(value) { value = (value || '').trim(); if (!value) return null; var m = value.match(/^(\d{5,})(?:[\/?#:]+(?:h=)?([0-9a-f]{6,}))?$/i); if (!m) { try { var u = new URL(value.indexOf('//') === -1 ? 'https://' + value : value); if (!/(^|\.)vimeo\.com$/i.test(u.hostname)) return null; var parts = u.pathname.split('/').filter(Boolean); var i = parts.findIndex(function (p) { return /^\d{5,}$/.test(p); }); if (i < 0) return null; var hash = u.searchParams.get('h') || (parts[i + 1] && /^[0-9a-f]{6,}$/i.test(parts[i + 1]) ? parts[i + 1] : ''); return { id: parts[i], hash: hash }; } catch (e) { return null; } } return { id: m[1], hash: m[2] || '' }; } var motion = window.matchMedia('(prefers-reduced-motion: reduce)'); function setup(holder) { if (holder.__qfVimeo) return; holder.__qfVimeo = true; var video = parse(holder.getAttribute('data-qf-vimeo')); var wrapper = holder.closest('.media-tab-graphic'); if (!video || !wrapper) return; // Vimeo wins over Lottie: stop any Lottie in this slot and use the image as poster. wrapper.classList.add('has-qf-vimeo'); var poster = wrapper.querySelector(':scope > img'); if (poster) { poster.style.display = ''; } var lottieEl = wrapper.querySelector('.lottie-element'); if (lottieEl && lottieEl.__graphicLrLottie) lottieEl.__graphicLrLottie.destroy(); if (motion.matches) return; var item = wrapper.closest('.content-graphic-detailed'); var src = 'https://player.vimeo.com/video/' + video.id + '?background=1&muted=1&loop=1&autopause=0&dnt=1' + (video.hash ? '&h=' + encodeURIComponent(video.hash) : ''); var stage = document.createElement('div'); stage.className = 'qf-vimeo-stage'; var frame = document.createElement('iframe'); frame.title = holder.getAttribute('data-qf-vimeo-label') || 'Product walkthrough video'; frame.allow = 'autoplay; fullscreen; picture-in-picture; encrypted-media'; stage.appendChild(frame); var icon = function (name) { var img = document.createElement('img'); img.src = 'https://cdn.jsdelivr.net/npm/lucide-static@0.468.0/icons/' + name + '.svg'; img.alt = ''; img.setAttribute('aria-hidden', 'true'); img.loading = 'lazy'; return img; }; var button = document.createElement('button'); button.type = 'button'; button.className = 'lottie-toggle qf-vimeo-toggle'; var pauseIcon = icon('circle-pause'), playIcon = icon('circle-play'); playIcon.hidden = true; button.appendChild(pauseIcon); button.appendChild(playIcon); button.hidden = true; if (getComputedStyle(wrapper).position === 'static') wrapper.style.position = 'relative'; wrapper.appendChild(stage); wrapper.appendChild(button); var ready = false, visible = true, userPaused = false, playing = null; var started = false, failed = false, vw = 0, vh = 0; function post(method, value) { if (!ready || !frame.contentWindow) return; var msg = { method: method }; if (value !== undefined) msg.value = value; frame.contentWindow.postMessage(JSON.stringify(msg), 'https://player.vimeo.com'); } function isActive() { return !item || (item.classList.contains('is-active') && item.getAttribute('aria-hidden') !== 'true'); } function update() { var shouldPlay = ready && visible && isActive() && !userPaused && !document.hidden; if (shouldPlay !== playing) { post(shouldPlay ? 'play' : 'pause'); playing = shouldPlay; } pauseIcon.hidden = userPaused; playIcon.hidden = !userPaused; button.setAttribute('aria-label', userPaused ? 'Play video' : 'Pause video'); } button.addEventListener('click', function () { userPaused = !userPaused; update(); }); window.addEventListener('message', function (e) { if (e.source !== frame.contentWindow || !/^https:\/\/player\.vimeo\.com$/.test(e.origin)) return; var data; try { data = typeof e.data === 'string' ? JSON.parse(e.data) : e.data; } catch (x) { return; } if (!data) return; if (data.event === 'ready' && !ready) { ready = true; clearTimeout(timeout); post('addEventListener', 'playProgress'); post('getVideoWidth'); post('getVideoHeight'); playing = null; update(); } // Swap from image/Lottie to video only once frames are actually playing. // Vimeo's postMessage API reports progress as 'playProgress'. if ((data.event === 'playProgress' || data.event === 'timeupdate') && !started) { started = true; wrapper.classList.add('is-vimeo-ready'); button.hidden = false; } if (data.method === 'getVideoWidth') vw = data.value; if (data.method === 'getVideoHeight') vh = data.value; if (vw && vh) wrapper.style.setProperty('--qf-vimeo-ratio', vw + ' / ' + vh); if (data.event === 'error' && !started) fail(); }); function fail() { if (failed) return; failed = true; clearTimeout(timeout); ready = false; wrapper.classList.remove('is-vimeo-ready'); stage.remove(); button.remove(); console.warn('QF Tab Vimeo: video unavailable, keeping the image.', video.id); } var timeout; function load() { if (frame.src) return; frame.src = src; timeout = setTimeout(function () { if (!ready) fail(); }, 15000); } // Load the player only once the page has finished loading, its tab is active and it is near the screen. var near = false, pageLoaded = document.readyState === 'complete'; function maybeLoad() { if (!pageLoaded || !near || !isActive()) return; if (poster) poster.loading = 'eager'; load(); } if (!pageLoaded) window.addEventListener('load', function () { pageLoaded = true; maybeLoad(); }, { once: true }); if ('IntersectionObserver' in window) { new IntersectionObserver(function (entries) { entries.forEach(function (en) { near = en.isIntersecting && !!wrapper.offsetWidth; maybeLoad(); }); }, { rootMargin: '300px 0px' }).observe(wrapper); } else { near = true; } if (item) { new MutationObserver(function () { maybeLoad(); playing = null; update(); }).observe(item, { attributes: true, attributeFilter: ['class', 'aria-hidden'] }); } maybeLoad(); // Pause while scrolled out of view (only refines; never blocks loading). if ('IntersectionObserver' in window) { new IntersectionObserver(function (entries) { entries.forEach(function (en) { if (wrapper.offsetWidth) { visible = en.isIntersecting; update(); } }); }, { rootMargin: '200px 0px' }).observe(wrapper); } document.addEventListener('visibilitychange', function () { playing = null; update(); }); } function init() { document.querySelectorAll('.media-tab-graphic [data-qf-vimeo]').forEach(setup); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init, { once: true }); else init(); })();