document.addEventListener('DOMContentLoaded', () => { if (typeof window.BiggerPicture !== 'function') { console.error('[gallery-init] BiggerPicture not found. Check script path.'); return; } function setupGalleries(root = document) { const imgs = root.querySelectorAll('.figure img, img.org-svg'); const VIDEO_EXTENSIONS = ['mp4','webm','mov','m4v','ogv']; function isVideo(href){ if(!href) return false; href = href.toLowerCase(); return VIDEO_EXTENSIONS.some(ext => href.endsWith('.' + ext)); } imgs.forEach((img) => { if (img.closest('a')) return; const a = document.createElement('a'); const href = img.currentSrc || img.src; a.href = href; a.dataset.img = href; a.dataset.alt = img.alt || ''; const setDims = () => { a.dataset.width = img.naturalWidth || img.width || 1920; a.dataset.height = img.naturalHeight || img.height || 1080; }; if (img.complete) setDims(); else img.addEventListener('load', setDims); img.style.cursor = 'zoom-in'; img.parentElement.insertBefore(a, img); a.appendChild(img); }); const containers = root.querySelectorAll('main, article, .content, body'); containers.forEach((container) => { //const links = Array.from(container.querySelectorAll('.figure a, a:has(img.org-svg)')); const links = Array.from(container.querySelectorAll( '.figure a, a:has(img.org-svg), a[href$=".mp4"], a[href$=".webm"], a[href$=".mov"], a[href$=".m4v"], a[href$=".ogv"]' )); if (!links.length) return; links.forEach((link) => { if (link.dataset.bpBound) return; link.dataset.bpBound = 'true'; link.addEventListener('click', (e) => { e.preventDefault(); bp.open({ items: links, el: link, caption: (el) => el.querySelector('img')?.alt || el.title || '', maxZoom: 40, onOpen(containerEl){ const linkEl = bp.currItem; const href = linkEl?.href || ""; if (isVideo(href)) { teardownRotation(); const htmlLayer = containerEl.querySelector(".bp-html"); const imgLayer = containerEl.querySelector(".bp-img"); if (imgLayer) imgLayer.style.display = "none"; htmlLayer.innerHTML = ` `; return; } setupRotation(containerEl); enhanceSVG(containerEl); } }); }); }); }); } function setupVideo(root=document){ const links = root.querySelectorAll('a[href]'); links.forEach(link=>{ if(link.dataset.videoProcessed) return; const href = link.getAttribute("href"); if(!isVideo(href)) return; link.dataset.videoProcessed = "true"; const video = document.createElement("video"); video.controls = true; video.preload = "metadata"; video.style.maxWidth = "100%"; video.style.borderRadius = "6px"; const source = document.createElement("source"); source.src = href; source.type = "video/" + href.split('.').pop(); video.appendChild(source); link.parentNode.replaceChild(video, link); }); } function setupAudio(root = document) { const audioLinks = root.querySelectorAll('a[href$=".mp3"]'); audioLinks.forEach((link) => { if (link.dataset.audioProcessed) return; link.dataset.audioProcessed = "true"; const src = link.href; const wrapper = document.createElement("div"); wrapper.className = "audio-player"; const audio = document.createElement("audio"); audio.controls = true; audio.preload = "metadata"; audio.src = src; const label = document.createElement("div"); label.className = "audio-label"; label.textContent = link.textContent || src.split("/").pop(); wrapper.appendChild(label); wrapper.appendChild(audio); link.replaceWith(wrapper); }); } const bp = BiggerPicture({ target: document.body }); let activeContainer = null; let currentRotation = 0; let rotateControls = null; function ensureRotateControls() { if (rotateControls) return rotateControls; const wrapper = document.createElement('div'); wrapper.className = 'bp-rotate-controls'; Object.assign(wrapper.style, { position: 'fixed', bottom: '1.5rem', right: '1.5rem', display: 'flex', gap: '0.5rem', zIndex: '9999', pointerEvents: 'auto' }); const mkBtn = (label, title) => { const btn = document.createElement('button'); btn.type = 'button'; btn.textContent = label; btn.title = title; btn.setAttribute('aria-label', title); Object.assign(btn.style, { padding: '0.4rem 0.6rem', borderRadius: '999px', border: 'none', fontSize: '1.2rem', cursor: 'pointer', background: 'rgba(30,30,30,0.8)', color: '#fff' }); return btn; }; const left = mkBtn('⟲', 'Rotate image 90° left'); const right = mkBtn('⟳', 'Rotate image 90° right'); left.addEventListener('click', (e) => { e.stopPropagation(); if (!activeContainer) return; currentRotation = (currentRotation - 90 + 360) % 360; applyRotation(); }); right.addEventListener('click', (e) => { e.stopPropagation(); if (!activeContainer) return; currentRotation = (currentRotation + 90) % 360; applyRotation(); }); wrapper.append(left, right); document.body.appendChild(wrapper); rotateControls = wrapper; return rotateControls; } function ensureRotateWrapper() { if (!activeContainer) return null; const imgRoot = activeContainer.querySelector('.bp-img'); if (!imgRoot) return null; const imgEl = imgRoot.querySelector('img'); if (!imgEl) return null; const src = (imgEl.currentSrc || imgEl.src || '').toLowerCase(); if (src.endsWith('.svg')) return null; let wrapper = imgRoot.querySelector('.bp-rotate-wrapper'); if (!wrapper) { wrapper = document.createElement('div'); wrapper.className = 'bp-rotate-wrapper'; wrapper.style.display = 'inline-block'; wrapper.style.transformOrigin = 'center center'; imgRoot.appendChild(wrapper); wrapper.appendChild(imgEl); } else if (!wrapper.contains(imgEl)) { wrapper.innerHTML = ''; wrapper.appendChild(imgEl); } return wrapper; } function applyRotation() { const wrapper = ensureRotateWrapper(); if (!wrapper) return; wrapper.style.transform = `rotate(${currentRotation}deg)`; } function setupRotation(containerEl) { activeContainer = containerEl; currentRotation = 0; const controls = ensureRotateControls(); controls.style.display = 'flex'; applyRotation(); } function teardownRotation() { activeContainer = null; currentRotation = 0; if (rotateControls) rotateControls.style.display = 'none'; } let activePanZoom = null; const destroyPanZoom = () => { try { activePanZoom?.destroy(); } catch(_){} activePanZoom = null; }; async function enhanceSVG(containerEl) { try { destroyPanZoom(); const imgEl = containerEl.querySelector('.bp-img img'); if (!imgEl) return; const src = imgEl.currentSrc || imgEl.src || ''; const isSVG = src.toLowerCase().endsWith('.svg'); const htmlLayer = containerEl.querySelector('.bp-html'); if (!isSVG || !htmlLayer) { const old = htmlLayer?.querySelector('.bp-svg-holder'); if (old) old.remove(); imgEl.style.visibility = ''; return; } let holder = htmlLayer.querySelector('.bp-svg-holder'); if (!holder) { holder = document.createElement('div'); holder.className = 'bp-svg-holder'; holder.style.maxWidth = '95vw'; holder.style.maxHeight = '95vh'; htmlLayer.appendChild(holder); } holder.innerHTML = ''; imgEl.style.visibility = 'hidden'; const res = await fetch(src, { cache: 'force-cache' }); const text = await res.text(); holder.innerHTML = text; const svg = holder.querySelector('svg'); if (!svg) { imgEl.style.visibility = ''; return; } svg.style.maxWidth = '95vw'; svg.style.maxHeight = '95vh'; svg.style.display = 'block'; if (typeof window.svgPanZoom === 'function') { activePanZoom = svgPanZoom(svg, { zoomEnabled: true, controlIconsEnabled: true, fit: true, center: true, minZoom: 0.05, maxZoom: 400, zoomScaleSensitivity: 0.25, dblClickZoomEnabled: true }); holder.addEventListener('wheel', (e) => e.stopPropagation(), { passive: true }); } else { console.warn('[gallery-init] svg-pan-zoom not loaded'); } } catch (err) { console.error('[gallery-init] SVG enhance failed:', err); } } setupGalleries(document); setupAudio(document); const dynamicObserver = new MutationObserver((mutations) => { for (const m of mutations) { for (const node of m.addedNodes) { if (!(node instanceof HTMLElement)) continue; if (node.querySelectorAll){ setupGalleries(node); setupAudio(node); setupVideo(node); } } } }); dynamicObserver.observe(document.body, { childList: true, subtree: true }); let lastUrl = location.href; const urlObserver = new MutationObserver(() => { const currentUrl = location.href; if (currentUrl !== lastUrl) { lastUrl = currentUrl; console.log('[gallery-init] URL changed → reinitializing galleries'); setupGalleries(document); setupAudio(document); setupVideo(document); } }); urlObserver.observe(document, { subtree: true, childList: true }); });