document.addEventListener('DOMContentLoaded', () => { const gallery = document.getElementById('photo-gallery'); const searchInput = document.getElementById('searchInput'); const filterBtns = document.querySelectorAll('.filter-btn'); const modal = document.getElementById('image-modal'); const modalImg = document.getElementById('modal-image-src'); const modalCaption = document.getElementById('modal-caption'); const modalCaptionTags = document.getElementById('modal-caption-tags'); const modalCloseBtn = modal.querySelector('.modal-close-btn') || modal.querySelector('.delete'); const modalBackground = modal.querySelector('.modal-background'); const resultCountEl = document.getElementById('result-count'); const noResults = document.getElementById('no-results'); const isTouchDevice = 'ontouchstart' in window || (navigator.maxTouchPoints || 0) > 0; const topButton = document.getElementById('top'); const fallbackPhotos = [ { path: 'assets/pics/gallery/classic/20230617_131551.webp', caption: "20' Classic Arch", tags: ['arch', 'classic', 'outdoor', 'wedding'] }, { path: 'assets/pics/gallery/classic/_Photos_20241207_083534.webp', caption: 'Classic Columns', tags: ['columns', 'classic', 'indoor', 'corporate', 'black-tie'] }, { path: 'assets/pics/gallery/centerpiece/20230108_112718.jpg}.webp', caption: 'Cocktail Arrangements', tags: ['centerpiece', 'cocktail', 'tablescape', 'baby-shower'] }, { path: 'assets/pics/gallery/organic/20241121_200047~2.jpg', caption: 'Organic Garland', tags: ['organic', 'garland', 'outdoor', 'birthday'] }, { path: 'assets/pics/gallery/organic/20250202_133930~2.jpg', caption: 'Organic Garland (Pastel)', tags: ['organic', 'garland', 'pastel'] } ]; let photos = []; const normalizeTags = (tags) => { if (Array.isArray(tags)) return tags; if (typeof tags === 'string') { return tags.split(',').map(tag => tag.trim()).filter(Boolean); } return []; }; async function fetchPhotos() { try { const response = await fetch(`${window.location.protocol}//${window.location.hostname}:5000/photos`); if (!response.ok) { throw new Error(`Fetch failed with status ${response.status}`); } const data = await response.json(); photos = Array.isArray(data) && data.length ? data : fallbackPhotos; } catch (error) { console.error('Error fetching photos:', error); photos = fallbackPhotos; } renderFlatGallery(photos); } function updateResultCount(count) { if (!resultCountEl) return; const total = photos.length; const totalText = total ? `${total}` : '0'; const countText = count !== undefined ? `${count}` : totalText; if (count === 0) { resultCountEl.innerHTML = `${countText} photos shown • ${totalText} total`; return; } resultCountEl.innerHTML = count === total ? `${countText} photos on display` : `${countText} shown • ${totalText} total`; } function renderFlatGallery(photoArray) { gallery.innerHTML = ''; // Clear skeleton or old photos updateResultCount(photoArray.length); if (photoArray.length === 0) { if (noResults) { gallery.appendChild(noResults); noResults.style.display = 'block'; } return; } if (noResults) { noResults.style.display = 'none'; } photoArray.forEach((photo, idx) => { const resolveUrl = (p) => { if (typeof p !== 'string') return ''; if (p.startsWith('http') || p.startsWith('assets') || p.startsWith('/assets')) return p; return `http://localhost:5000/${p}`; }; const src = resolveUrl(photo.path); const srcset = photo.variants ? [ photo.variants.thumb ? `${resolveUrl(photo.variants.thumb)} 640w` : null, photo.variants.medium ? `${resolveUrl(photo.variants.medium)} 1200w` : null, src ? `${src} 2000w` : null ].filter(Boolean).join(', ') : ''; const photoTags = normalizeTags(photo.tags); const photoCard = document.createElement('div'); photoCard.className = 'gallery-item'; const tagBadges = photoTags.map(tag => `${tag}`).join(''); photoCard.innerHTML = ` `; gallery.appendChild(photoCard); setTimeout(() => requestAnimationFrame(() => photoCard.classList.add('is-visible')), idx * 70); const imgEl = photoCard.querySelector('img'); imgEl.addEventListener('click', (e) => { const card = e.currentTarget.closest('.gallery-item'); if (isTouchDevice && card) { if (!card.classList.contains('touch-active')) { card.classList.add('touch-active'); setTimeout(() => card.classList.remove('touch-active'), 2200); return; } } openModal(e.target); if (card) card.classList.remove('touch-active'); }); }); } function filterPhotos() { const searchTerm = searchInput.value.toLowerCase(); // Deactivate tag buttons when searching filterBtns.forEach(btn => btn.classList.remove('is-active')); if (searchTerm) { const filteredPhotos = photos.filter(photo => { const photoTags = normalizeTags(photo.tags); const captionMatch = photo.caption.toLowerCase().includes(searchTerm); const tagMatch = photoTags.some(tag => tag.toLowerCase().includes(searchTerm)); return captionMatch || tagMatch; }); renderFlatGallery(filteredPhotos); } else { renderFlatGallery(photos); // Reactivate 'All' button if search is cleared document.querySelector('.filter-btn[data-tag="all"]').classList.add('is-active'); } } function filterByTag(tag) { searchInput.value = ''; if (tag === 'all') { renderFlatGallery(photos); } else { const filteredPhotos = photos.filter(photo => { const photoTags = normalizeTags(photo.tags); return photoTags.some(t => t.toLowerCase() === tag); }); renderFlatGallery(filteredPhotos); } } function openModal(imageElement) { const rect = imageElement.getBoundingClientRect(); const centerX = window.innerWidth / 2; const centerY = window.innerHeight / 2; const imgCenterX = rect.left + rect.width / 2; const imgCenterY = rect.top + rect.height / 2; const translateX = imgCenterX - centerX; const translateY = imgCenterY - centerY; const scaleStartRaw = Math.max( rect.width / (window.innerWidth * 0.8), rect.height / (window.innerHeight * 0.8), 0.55 ); const scaleStart = Math.min(Math.max(scaleStartRaw, 0.72), 0.96); document.documentElement.style.setProperty('--modal-img-translate-x', `${translateX}px`); document.documentElement.style.setProperty('--modal-img-translate-y', `${translateY}px`); document.documentElement.style.setProperty('--modal-img-scale', scaleStart.toFixed(3)); modalImg.src = imageElement.dataset.fullSrc || imageElement.src; modalCaption.textContent = imageElement.dataset.caption; if (modalCaptionTags) { const tags = (imageElement.dataset.tags || '').split(',').filter(Boolean); modalCaptionTags.innerHTML = tags.map(t => `${t}`).join(''); } modal.classList.remove('show-bg'); modal.classList.add('chrome-hidden'); modal.classList.add('is-active'); document.documentElement.classList.add('is-clipped'); document.body.classList.add('modal-open'); if (topButton) topButton.style.display = 'none'; // Fade in chrome and background immediately after paint requestAnimationFrame(() => { modal.classList.add('show-bg'); modal.classList.remove('chrome-hidden'); }); } function closeModal() { modal.classList.remove('is-active'); modal.classList.remove('show-bg'); modal.classList.remove('chrome-hidden'); document.documentElement.classList.remove('is-clipped'); document.body.classList.remove('modal-open'); document.documentElement.style.removeProperty('--modal-img-translate-x'); document.documentElement.style.removeProperty('--modal-img-translate-y'); document.documentElement.style.removeProperty('--modal-img-scale'); if (topButton) { const shouldShow = document.body.scrollTop > 130 || document.documentElement.scrollTop > 130; topButton.style.display = shouldShow ? 'block' : 'none'; } } searchInput.addEventListener('keyup', filterPhotos); filterBtns.forEach(btn => { btn.addEventListener('click', () => { const tag = btn.dataset.tag; filterByTag(tag.toLowerCase()); filterBtns.forEach(otherBtn => otherBtn.classList.remove('is-active')); btn.classList.add('is-active'); }); }); if (modalCloseBtn) modalCloseBtn.addEventListener('click', closeModal); modalBackground.addEventListener('click', closeModal); function renderSkeletonLoader() { gallery.innerHTML = ''; // Clear gallery for (let i = 0; i < 8; i++) { const skeletonItem = document.createElement('div'); skeletonItem.className = 'skeleton-item'; gallery.appendChild(skeletonItem); } } renderSkeletonLoader(); fetchPhotos(); });