diff options
Diffstat (limited to 'public/js')
| -rw-r--r-- | public/js/album.js | 111 | ||||
| -rw-r--r-- | public/js/slideshow.js | 113 |
2 files changed, 224 insertions, 0 deletions
diff --git a/public/js/album.js b/public/js/album.js new file mode 100644 index 0000000..74d524c --- /dev/null +++ b/public/js/album.js @@ -0,0 +1,111 @@ +'use strict'; + +let lbIndex = 0; + +function openLightbox(i) { + lbIndex = i; + renderLightbox(); + document.getElementById('lightbox').classList.remove('hidden'); + document.body.style.overflow = 'hidden'; + document.addEventListener('keydown', lbKey); +} + +function closeLightbox() { + document.getElementById('lightbox').classList.add('hidden'); + document.body.style.overflow = ''; + document.removeEventListener('keydown', lbKey); + ['lb-video', 'lb-audio'].forEach(id => { + const el = document.getElementById(id); + el.pause && el.pause(); + }); + history.replaceState(null, '', location.pathname + location.search); +} + +function lbNav(delta) { + let next = lbIndex + delta; + while (next >= 0 && next < ENTRIES.length && !ENTRIES[next].type) next += delta; + if (next >= 0 && next < ENTRIES.length) { + lbIndex = next; + renderLightbox(); + } +} + +function renderLightbox() { + const e = ENTRIES[lbIndex]; + const img = document.getElementById('lb-img'); + const vid = document.getElementById('lb-video'); + const aud = document.getElementById('lb-audio'); + + [img, vid, aud].forEach(el => { el.classList.add('hidden'); el.pause && el.pause(); }); + + if (e.type === 'image') { + img.src = e.src; img.alt = e.title; + img.classList.remove('hidden'); + } else if (e.type === 'video') { + vid.src = e.src; + vid.classList.remove('hidden'); + vid.play().catch(() => {}); + } else if (e.type === 'audio') { + aud.src = e.src; + aud.classList.remove('hidden'); + aud.play().catch(() => {}); + } + + document.getElementById('lb-title').textContent = e.title !== e.name ? e.title : ''; + document.getElementById('lb-caption').textContent = e.caption || ''; + document.getElementById('lb-counter').textContent = `${lbIndex + 1} / ${ENTRIES.length}`; + + const dl = document.getElementById('lb-download'); + dl.href = e.src; + dl.download = e.name; + + // Update URL hash so the address bar is the shareable link + history.replaceState(null, '', location.pathname + location.search + '#photo=' + encodeURIComponent(e.name)); +} + +function lbCopyLink() { + navigator.clipboard.writeText(location.href).then(() => { + const btn = document.getElementById('lb-copylink'); + const orig = btn.textContent; + btn.textContent = '✓ Copied!'; + setTimeout(() => { btn.textContent = orig; }, 1800); + }).catch(() => { + // Fallback: select a temporary input + const tmp = document.createElement('input'); + tmp.value = location.href; + document.body.appendChild(tmp); + tmp.select(); + document.execCommand('copy'); + document.body.removeChild(tmp); + }); +} + +function lbKey(ev) { + if (ev.key === 'Escape') closeLightbox(); + else if (ev.key === 'ArrowLeft') lbNav(-1); + else if (ev.key === 'ArrowRight') lbNav(1); +} + +// Restore lightbox from URL hash on page load +window.addEventListener('DOMContentLoaded', () => { + const m = location.hash.match(/^#photo=(.+)$/); + if (m) { + const name = decodeURIComponent(m[1]); + const idx = ENTRIES.findIndex(e => e.name === name); + if (idx >= 0) openLightbox(idx); + } +}); + +// Touch swipe +(function () { + let startX = null; + const lb = document.getElementById('lightbox'); + if (!lb) return; + lb.addEventListener('touchstart', e => { startX = e.changedTouches[0].clientX; }, { passive: true }); + lb.addEventListener('touchend', e => { + if (startX === null) return; + const dx = e.changedTouches[0].clientX - startX; + if (Math.abs(dx) > 50) lbNav(dx < 0 ? 1 : -1); + startX = null; + }, { passive: true }); +})(); diff --git a/public/js/slideshow.js b/public/js/slideshow.js new file mode 100644 index 0000000..2260034 --- /dev/null +++ b/public/js/slideshow.js @@ -0,0 +1,113 @@ +'use strict'; + +let ssIdx = 0; +let ssTimer = null; +let ssPlaying = true; + +const img = document.getElementById('ss-img'); +const vid = document.getElementById('ss-video'); +const title = document.getElementById('ss-title'); +const cap = document.getElementById('ss-caption'); +const ctr = document.getElementById('ss-counter'); +const btn = document.getElementById('ss-play-btn'); + +function ssInterval() { + return (parseFloat(document.getElementById('ss-interval').value) || 4) * 1000; +} + +function ssShow(i, instant) { + const e = SS_ENTRIES[i]; + if (!e) return; + ssIdx = i; + + title.textContent = e.title !== e.name ? e.title : ''; + cap.textContent = e.caption || ''; + ctr.textContent = `${i + 1} / ${SS_ENTRIES.length}`; + + if (instant) { + // First load — show without transition + applyEntry(e); + return; + } + + if (e.type === 'video') { + // No preload possible for video; fade out then swap + crossFade(() => applyEntry(e)); + } else { + // Preload the image so the fade-in shows it immediately + const preload = new Image(); + preload.onload = preload.onerror = () => crossFade(() => applyEntry(e)); + preload.src = e.src; + } +} + +function applyEntry(e) { + if (e.type === 'video') { + img.style.display = 'none'; + vid.style.display = ''; + vid.src = e.src; + void vid.offsetWidth; // flush so transition fires + vid.classList.remove('fading'); + vid.play().catch(() => {}); + } else { + vid.pause && vid.pause(); + vid.style.display = 'none'; + img.style.display = ''; + img.src = e.src; + img.alt = e.title; + void img.offsetWidth; // flush so transition fires + img.classList.remove('fading'); + } +} + +function crossFade(cb) { + img.classList.add('fading'); + vid.classList.add('fading'); + setTimeout(cb, 500); // matches CSS transition duration +} + +function ssSchedule() { + clearTimeout(ssTimer); + if (ssPlaying && SS_ENTRIES.length > 1) { + ssTimer = setTimeout(() => { + ssShow((ssIdx + 1) % SS_ENTRIES.length); + ssSchedule(); + }, ssInterval()); + } +} + +function ssNext() { + ssShow((ssIdx + 1) % SS_ENTRIES.length); + if (ssPlaying) ssSchedule(); +} + +function ssPrev() { + ssShow((ssIdx - 1 + SS_ENTRIES.length) % SS_ENTRIES.length); + if (ssPlaying) ssSchedule(); +} + +function ssToggle() { + ssPlaying = !ssPlaying; + btn.textContent = ssPlaying ? '⏸ Pause' : '▶ Play'; + if (ssPlaying) ssSchedule(); else clearTimeout(ssTimer); +} + +document.addEventListener('keydown', e => { + if (e.key === 'ArrowRight') ssNext(); + if (e.key === 'ArrowLeft') ssPrev(); + if (e.key === ' ') { e.preventDefault(); ssToggle(); } +}); + +let swipeX = null; +document.addEventListener('touchstart', e => { swipeX = e.changedTouches[0].clientX; }, { passive: true }); +document.addEventListener('touchend', e => { + if (swipeX === null) return; + const dx = e.changedTouches[0].clientX - swipeX; + if (Math.abs(dx) > 50) (dx < 0 ? ssNext : ssPrev)(); + swipeX = null; +}, { passive: true }); + +if (SS_ENTRIES.length > 0) { + ssShow(0, true); + ssSchedule(); +} |
