PTF × MEDIA SUITE

/* Shared enhancement for .hhec-media-suite. No libraries or external APIs. */ (function () { ‘use strict’; function safeImageURL(value) { if (!value) return ”; try { var url = new URL(value, document.baseURI); return /^https?:$/.test(url.protocol) ? url.href : ”; } catch (_) { return ”; } } function boot() { document.querySelectorAll(‘.hhec-media-suite’).forEach(function (root) { if (root.dataset.hmsReady) return; root.dataset.hmsReady = ‘true’; var photos = Array.from(root.querySelectorAll(‘[data-hms-photo]’)); var dialog = root.querySelector(‘[data-hms-dialog]’); var current = 0; var opener = null; var loadVersion = 0; var savedScroll = null; var image = dialog.querySelector(‘[data-hms-full-image]’); var placeholder = dialog.querySelector(‘[data-hms-full-placeholder]’); var number = dialog.querySelector(‘[data-hms-full-number]’); var status = dialog.querySelector(‘[data-hms-full-status]’); var caption = dialog.querySelector(‘[data-hms-caption]’); var credit = dialog.querySelector(‘[data-hms-credit]’); var counter = dialog.querySelector(‘[data-hms-counter]’); var closer = dialog.querySelector(‘[data-hms-close]’); var stage = dialog.querySelector(‘[data-hms-stage]’); photos.forEach(function (photo, index) { var src = safeImageURL(photo.dataset.thumb || photo.dataset.full); if (src) { var thumb = document.createElement(‘img’); thumb.className = ‘hms-photo-img’; thumb.alt = photo.dataset.alt || photo.dataset.caption || ‘Gallery photo ‘ + (index + 1); thumb.width = 900; thumb.height = 675; thumb.loading = ‘lazy’; thumb.decoding = ‘async’; thumb.addEventListener(‘load’, function () { photo.querySelector(‘.hms-photo-placeholder’).hidden = true; photo.querySelector(‘.hms-photo-state’).textContent = ‘View photo’; }); thumb.addEventListener(‘error’, function () { thumb.hidden = true; photo.querySelector(‘.hms-photo-state’).textContent = ‘Preview unavailable’; }); thumb.src = src; photo.querySelector(‘.hms-photo-media’).appendChild(thumb); } photo.setAttribute(‘aria-haspopup’, ‘dialog’); photo.setAttribute(‘aria-controls’, dialog.id); photo.addEventListener(‘click’, function () { if (typeof dialog.showModal !== ‘function’) { var full = safeImageURL(photo.dataset.full || photo.dataset.thumb); if (full) window.open(full, ‘_blank’, ‘noopener,noreferrer’); return; } opener = photo; render(index); savedScroll = { x: window.scrollX, y: window.scrollY, overflow: document.documentElement.style.overflow, paddingRight: document.documentElement.style.paddingRight }; var scrollbar = window.innerWidth – document.documentElement.clientWidth; if (scrollbar > 0) { document.documentElement.style.paddingRight = (parseFloat(getComputedStyle(document.documentElement).paddingRight) + scrollbar) + ‘px’; } document.documentElement.style.overflow = ‘hidden’; dialog.showModal(); closer.focus({ preventScroll: true }); }); }); function render(index) { current = (index + photos.length) % photos.length; var photo = photos[current]; var version = ++loadVersion; var src = safeImageURL(photo.dataset.full || photo.dataset.thumb); image.hidden = true; image.onload = null; image.onerror = null; image.removeAttribute(‘src’); placeholder.hidden = false; number.textContent = String(current + 1).padStart(2, ‘0’); status.textContent = src ? ‘Loading photo…’ : ‘Photo coming soon’; caption.textContent = photo.dataset.caption || photo.querySelector(‘.hms-photo-name’).textContent; credit.textContent = photo.dataset.credit ? ‘Photo: ‘ + photo.dataset.credit : ”; credit.hidden = !photo.dataset.credit; counter.textContent = (current + 1) + ‘ / ‘ + photos.length; image.alt = photo.dataset.alt || caption.textContent; if (!src) return; image.onload = function () { if (version !== loadVersion) return; image.hidden = false; placeholder.hidden = true; }; image.onerror = function () { if (version !== loadVersion) return; image.hidden = true; status.textContent = ‘Photo could not load’; }; image.src = src; } closer.addEventListener(‘click’, function () { dialog.close(); }); dialog.querySelector(‘[data-hms-prev]’).addEventListener(‘click’, function () { render(current – 1); }); dialog.querySelector(‘[data-hms-next]’).addEventListener(‘click’, function () { render(current + 1); }); dialog.addEventListener(‘keydown’, function (event) { if (event.key === ‘ArrowRight’) { event.preventDefault(); render(current + 1); } if (event.key === ‘ArrowLeft’) { event.preventDefault(); render(current – 1); } }); var backdropDown = false; dialog.addEventListener(‘pointerdown’, function (event) { var rect = dialog.getBoundingClientRect(); backdropDown = event.target === dialog && (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom); }); dialog.addEventListener(‘click’, function (event) { var rect = dialog.getBoundingClientRect(); if (backdropDown && event.target === dialog && (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom)) dialog.close(); backdropDown = false; }); dialog.addEventListener(‘close’, function () { ++loadVersion; image.onload = null; image.onerror = null; image.hidden = true; image.removeAttribute(‘src’); if (savedScroll) { document.documentElement.style.overflow = savedScroll.overflow; document.documentElement.style.paddingRight = savedScroll.paddingRight; window.scrollTo(savedScroll.x, savedScroll.y); savedScroll = null; } if (opener && opener.isConnected) opener.focus({ preventScroll: true }); }); var touchStart = null; stage.addEventListener(‘touchstart’, function (event) { touchStart = event.touches.length === 1 ? { x: event.touches[0].clientX, y: event.touches[0].clientY } : null; }, { passive: true }); stage.addEventListener(‘touchend’, function (event) { if (!touchStart || !event.changedTouches.length) return; var dx = event.changedTouches[0].clientX – touchStart.x; var dy = event.changedTouches[0].clientY – touchStart.y; if (Math.abs(dx) > 60 && Math.abs(dx) > Math.abs(dy) * 1.5) render(current + (dx < 0 ? 1 : -1)); touchStart = null; }, { passive: true }); stage.addEventListener('touchcancel', function () { touchStart = null; }, { passive: true }); root.querySelectorAll('[data-youtube-id]').forEach(function (shell) { var id = shell.dataset.youtubeId.trim(); if (!/^[A-Za-z0-9_-]{11}$/.test(id)) return; var title = shell.dataset.title || 'HHEC video'; var button = document.createElement('button'); button.type = 'button'; button.className = 'hms-video-load'; button.setAttribute('aria-label', 'Play ' + title); var art = shell.querySelector('.hms-play-disc').cloneNode(true); var posterURL = safeImageURL(shell.dataset.poster); if (posterURL) { var poster = document.createElement('img'); poster.src = posterURL; poster.className = 'hms-video-poster'; poster.alt = ''; poster.loading = 'lazy'; poster.decoding = 'async'; poster.addEventListener('error', function () { poster.remove(); }); button.appendChild(poster); } var playText = document.createElement('span'); playText.className = 'hms-video-status'; playText.textContent = 'Play video'; button.append(art, playText); shell.replaceChildren(button); var card = shell.closest('article'); var cardStatus = card && card.querySelector('[data-hms-video-state]'); if (cardStatus) cardStatus.textContent = 'Ready to watch'; button.addEventListener('click', function () { var frame = document.createElement('iframe'); frame.title = title; frame.width = '640'; frame.height = '360'; frame.referrerPolicy = 'strict-origin-when-cross-origin'; frame.allow = 'autoplay; encrypted-media; picture-in-picture; fullscreen'; frame.allowFullscreen = true; frame.tabIndex = 0; frame.src = 'https://www.youtube.com/embed/' + id + '?autoplay=1&playsinline=1&rel=0'; shell.replaceChildren(frame); frame.focus({ preventScroll: true }); }); }); }); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot, { once: true }); else boot(); })();