/* Graphic media Lottie loader v2 (2026-10-23, page speed). v1 (graphic-lottie-external-fixed.txt) needed lottie-web on every page and built every animation at page load. v2 loads lottie-web only when an animation is about to show (its tab or accordion item is active and it is within 300px of the viewport) and builds each animation at that moment. Everything else (pause/play, reduced motion, fallbacks) is unchanged. */ (function () { 'use strict'; var LOTTIE_SRC = 'https://cdn.jsdelivr.net/npm/lottie-web@5.13.0/build/player/lottie.min.js'; // The older inline "Graphic Left/Right" embed on some pages expects lottie-web at // DOMContentLoaded. This loader covers the same wrappers, so switch that duplicate off. window.__graphicLrLottieInitialized = true; var libraryPromise = null; function loadLibrary() { if (window.lottie && window.lottie.loadAnimation) return Promise.resolve(window.lottie); if (!libraryPromise) { libraryPromise = new Promise(function (resolve, reject) { var script = document.createElement('script'); script.src = LOTTIE_SRC; script.async = true; script.onload = function () { if (window.lottie && window.lottie.loadAnimation) resolve(window.lottie); else reject(new Error('Lottie library missing after load')); }; script.onerror = function () { reject(new Error('Lottie library could not load')); }; document.head.appendChild(script); }).catch(function (error) { libraryPromise = null; throw error; }); } return libraryPromise; } function init() { if (window.__graphicMediaLottieInitialized) return; window.__graphicMediaLottieInitialized = true; var selector = '.media-graphic-lr-wrapper, ' + '[data-wf--cmpt-four-reasons-block--variant] .lottie-card.decor-section, ' + '.graphic-accordion .media-item-graphic-accordion, ' + '.graphic-detailed-tab .media-tab-graphic, ' + '.graphic-detailed-accord .media-content-accord_layout.is-rendered .media-accord_image-wrap, ' + '.accordion-tab .media-accordion'; var motion = window.matchMedia('(prefers-reduced-motion: reduce)'); var controllers = []; var observer = 'IntersectionObserver' in window ? new IntersectionObserver(function (entries) { entries.forEach(function (entry) { controllers.forEach(function (controller) { if (controller.wrapper === entry.target) { controller.setVisible(entry.isIntersecting); } }); }); }, { threshold: 0 }) : null; var nearObserver = 'IntersectionObserver' in window ? new IntersectionObserver(function (entries) { entries.forEach(function (entry) { controllers.forEach(function (controller) { if (controller.wrapper === entry.target) { controller.setNear(entry.isIntersecting); } }); }); }, { rootMargin: '300px 0px' }) : null; function initializeWrapper(wrapper) { var root = wrapper.querySelector('.lottie-element'); if (!root || root.__graphicLrLottie) return; var managedMedia = wrapper.matches( '.media-item-graphic-accordion, .media-tab-graphic, ' + '.media-accord_image-wrap, .media-accordion' ); var holder = root.querySelector('.lottie-data-url'); var source = holder ? holder.textContent.trim() : ''; source = source || (root.getAttribute('data-lottie-src') || '').trim(); if (managedMedia) { root.style.display = 'none'; if (holder) holder.hidden = true; } if (!source) return; var url; try { url = new URL(source, document.baseURI); if (!/^https?:$/.test(url.protocol)) throw new Error('Invalid URL protocol'); } catch (error) { console.warn('Graphic media: invalid Lottie URL.', error); return; } var stage = document.createElement('div'); stage.className = 'lottie-stage'; stage.setAttribute('role', 'img'); stage.setAttribute('aria-label', root.getAttribute('data-lottie-label') || 'Product animation'); if (managedMedia) { stage.style.width = '100%'; stage.style.height = '100%'; } root.appendChild(stage); var button = document.createElement('button'); button.type = 'button'; button.className = 'lottie-toggle'; button.hidden = true; var pauseIcon = document.createElement('img'); pauseIcon.className = 'lottie-toggle-icon'; pauseIcon.src = 'https://cdn.jsdelivr.net/npm/lucide-static@0.468.0/icons/circle-pause.svg'; pauseIcon.alt = ''; pauseIcon.width = 32; pauseIcon.height = 32; pauseIcon.setAttribute('aria-hidden', 'true'); var playIcon = document.createElement('img'); playIcon.className = 'lottie-toggle-icon'; playIcon.src = 'https://cdn.jsdelivr.net/npm/lucide-static@0.468.0/icons/circle-play.svg'; playIcon.alt = ''; playIcon.width = 32; playIcon.height = 32; playIcon.hidden = true; playIcon.setAttribute('aria-hidden', 'true'); button.appendChild(pauseIcon); button.appendChild(playIcon); if (managedMedia) { if (window.getComputedStyle(wrapper).position === 'static') { wrapper.style.position = 'relative'; } Object.assign(button.style, { position: 'absolute', right: '12px', bottom: '12px', zIndex: '2', width: '44px', height: '44px', padding: '6px', border: '0', borderRadius: '50%', background: 'transparent', cursor: 'pointer' }); } wrapper.appendChild(button); var selection = wrapper.closest( '.item-graphic-accordion-wrap, .content-graphic-detailed, ' + '.item-accord_layout, .item-accordion-wrap, .media-content-accord_layout' ); function isActive() { if (!selection) return true; if (selection.classList.contains('item-graphic-accordion-wrap')) { return !selection.classList.contains('off'); } if (selection.classList.contains('content-graphic-detailed')) { return selection.classList.contains('is-active') && selection.getAttribute('aria-hidden') !== 'true'; } if (selection.classList.contains('media-content-accord_layout')) { return !selection.classList.contains('is-changing'); } return selection.classList.contains('is-on'); } var animation; var ready = false; var failed = false; var destroyed = false; var visible = !observer; var userPaused = false; var focusPaused = false; var ended = false; var active = isActive(); var restartPending = !!selection && active; var wasReduced = motion.matches; var timeout; var selectionObserver; var near = !nearObserver; var started = false; var fallbackImages = managedMedia ? Array.from(wrapper.children).filter(function (element) { return element.tagName === 'IMG'; }).map(function (element) { return { element: element, display: element.style.display }; }) : []; function update() { if (!ready || failed || destroyed) return; if (wasReduced && !motion.matches) { restartPending = true; if (ended) { ended = false; userPaused = false; } } wasReduced = motion.matches; button.hidden = motion.matches || !active; var label = userPaused ? 'Play animation' : 'Pause animation'; pauseIcon.hidden = userPaused; playIcon.hidden = !userPaused; button.setAttribute('aria-label', label); button.title = label; if (motion.matches) { animation.goToAndStop(Math.max(0, Math.floor(animation.totalFrames) - 1), true); } else { if (restartPending && active) { restartPending = false; animation.goToAndStop(0, true); } if (!active || userPaused || focusPaused || !visible || document.hidden || ended) { animation.pause(); } else { animation.play(); } } } function cleanup() { ready = false; clearTimeout(timeout); if (observer) observer.unobserve(wrapper); if (nearObserver) nearObserver.unobserve(wrapper); if (selectionObserver) selectionObserver.disconnect(); wrapper.classList.remove('is-lottie-ready'); wrapper.removeEventListener('focusin', onFocusIn); wrapper.removeEventListener('focusout', onFocusOut); button.remove(); if (animation) animation.destroy(); stage.remove(); if (managedMedia) root.style.display = 'none'; fallbackImages.forEach(function (image) { image.element.style.display = image.display; }); } function fail(error) { if (failed || destroyed) return; failed = true; cleanup(); console.warn('Graphic media: animation failed; keeping the image.', url.href, error); } var controller = { wrapper: wrapper, update: update, setVisible: function (value) { visible = value; update(); }, setNear: function (value) { near = value; maybeStart(); }, destroy: function () { if (destroyed) return; destroyed = true; cleanup(); delete root.__graphicLrLottie; } }; controllers.push(controller); root.__graphicLrLottie = controller; // Existing tabs and accordions keep ownership of their selected item. if (selection) { selectionObserver = new MutationObserver(function () { var nextActive = isActive(); if (nextActive === active) return; active = nextActive; if (active) { restartPending = true; userPaused = false; focusPaused = false; ended = false; if (ready) animation.resize(); } maybeStart(); update(); }); selectionObserver.observe(selection, { attributes: true, attributeFilter: ['class', 'aria-hidden'] }); } function maybeStart() { if (started || failed || destroyed || !near || !active) return; started = true; if (nearObserver) nearObserver.unobserve(wrapper); timeout = setTimeout(function () { if (!ready) fail(new Error('Loading timed out')); }, 30000); loadLibrary().then(function (lottie) { if (failed || destroyed) return; build(lottie); }).catch(fail); } function build(lottie) { try { animation = lottie.loadAnimation({ container: stage, renderer: 'svg', path: url.href, loop: root.getAttribute('data-lottie-loop') !== 'false', autoplay: false, rendererSettings: { preserveAspectRatio: 'xMidYMid meet' } }); animation.addEventListener('DOMLoaded', function () { if (failed || destroyed) return; ready = true; clearTimeout(timeout); wrapper.classList.add('is-lottie-ready'); if (managedMedia) { root.style.display = 'block'; fallbackImages.forEach(function (image) { image.element.style.display = 'none'; }); } animation.resize(); update(); }); animation.addEventListener('data_failed', fail); animation.addEventListener('error', fail); animation.addEventListener('complete', function () { ended = true; userPaused = true; update(); }); } catch (error) { fail(error); } } button.addEventListener('click', function (event) { event.stopPropagation(); userPaused = !userPaused; focusPaused = false; if (ended && !userPaused) { ended = false; animation.goToAndStop(0, true); } update(); }); function onFocusIn() { focusPaused = true; update(); } function onFocusOut(event) { if (wrapper.contains(event.relatedTarget)) return; focusPaused = false; update(); } wrapper.addEventListener('focusin', onFocusIn); wrapper.addEventListener('focusout', onFocusOut); if (observer) observer.observe(wrapper); if (nearObserver) nearObserver.observe(wrapper); maybeStart(); } function scan(scope) { scope.querySelectorAll(selector).forEach(initializeWrapper); } scan(document); document.querySelectorAll('.accordion-tab').forEach(function (accordion) { function syncStickyPointers() { accordion.querySelectorAll('.item-accordion-wrap').forEach(function (item) { var sticky = item.querySelector('.wrapper-sticky'); if (sticky) { sticky.style.pointerEvents = item.classList.contains('is-on') ? '' : 'none'; } }); } syncStickyPointers(); var pointerObserver = new MutationObserver(function (records) { if (records.some(function (record) { return record.target.classList.contains('item-accordion-wrap'); })) syncStickyPointers(); }); pointerObserver.observe(accordion, { attributes: true, subtree: true, attributeFilter: ['class'] }); }); // Detailed Accordion clones source media; initialize only the rendered copy. document.querySelectorAll('.graphic-detailed-accord .media-content-accord_layout') .forEach(function (rendered) { var mediaObserver = new MutationObserver(function () { controllers = controllers.filter(function (controller) { if (controller.wrapper.isConnected) return true; controller.destroy(); return false; }); scan(rendered); }); mediaObserver.observe(rendered, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] }); }); function updateAll() { controllers.forEach(function (controller) { controller.update(); }); } document.addEventListener('visibilitychange', updateAll); if (motion.addEventListener) motion.addEventListener('change', updateAll); else motion.addListener(updateAll); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init, { once: true }); } else { init(); } })();