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 = `
${photo.caption}
${photoTags.join(', ')}
${tagBadges}
`;
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();
});