// Gallery page for Little Bloom Photo Studio const { useState: useStateGal, useEffect: useEffectGal, useRef: useRefGal, useCallback: useCallbackGal } = React; function GalleryPage({ setPage }) { const [group, setGroup] = useStateGal('all'); const [lightbox, setLightbox] = useStateGal(null); // index into `photos`, or null const photos = group === 'all' ? STUDIO_GALLERY : STUDIO_GALLERY.filter(p => p.group === group); // Changing filter invalidates any open lightbox index const pickGroup = (id) => { setLightbox(null); setGroup(id); }; return (
{lightbox !== null && ( setLightbox(null)} /> )}
); } /* ========== Hero ========== */ function GalleryHero({ count }) { return (
The Gallery

Every corner of the studio

A closer look at the space — the daybed, the couch, the bassinet, and every prop and detail waiting for your next session.

{count} Photographs
); } /* ========== Filter pills ========== */ function GalleryFilters({ group, setGroup }) { return (
{GALLERY_GROUPS.map(g => { const on = g.id === group; return ( ); })}
); } /* ========== Masonry grid ========== */ function GalleryGrid({ photos, onOpen }) { return (
{photos.map((p, i) => ( ))}
); } /* ========== Lightbox ========== */ function Lightbox({ photos, index, setIndex, onClose }) { const photo = photos[index]; const touchX = useRefGal(null); const closeRef = useRefGal(null); const prev = useCallbackGal(() => setIndex(i => (i - 1 + photos.length) % photos.length), [photos.length, setIndex]); const next = useCallbackGal(() => setIndex(i => (i + 1) % photos.length), [photos.length, setIndex]); // Keyboard: Esc closes, arrows navigate useEffectGal(() => { const onKey = (e) => { if (e.key === 'Escape') onClose(); else if (e.key === 'ArrowLeft') prev(); else if (e.key === 'ArrowRight') next(); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [prev, next, onClose]); // Lock body scroll while open, compensating for the scrollbar so the // page behind doesn't jump sideways useEffectGal(() => { const { overflow, paddingRight } = document.body.style; const gap = window.innerWidth - document.documentElement.clientWidth; document.body.style.overflow = 'hidden'; if (gap > 0) document.body.style.paddingRight = gap + 'px'; return () => { document.body.style.overflow = overflow; document.body.style.paddingRight = paddingRight; }; }, []); // Move focus into the dialog so keyboard users land in the right place useEffectGal(() => { closeRef.current && closeRef.current.focus(); }, []); // Warm the neighbouring full-size images so arrowing through is instant useEffectGal(() => { [index - 1, index + 1].forEach(i => { const p = photos[(i + photos.length) % photos.length]; if (p) { const img = new Image(); img.src = p.src; } }); }, [index, photos]); const onTouchStart = (e) => { touchX.current = e.changedTouches[0].clientX; }; const onTouchEnd = (e) => { if (touchX.current === null) return; const dx = e.changedTouches[0].clientX - touchX.current; if (Math.abs(dx) > 50) (dx < 0 ? next : prev)(); touchX.current = null; }; return (
{/* Top bar */}
e.stopPropagation()}> {photo.label}  ·  {index + 1} / {photos.length}
{/* Stage */}
e.stopPropagation()}> {photo.label}

e.stopPropagation()}> Use ← → to browse · Esc to close Swipe to browse · Tap outside to close

); } /* ========== Closing CTA ========== */ function GalleryCTA({ setPage }) { return (
Come see it in person

Photos only go so far — book the space.

Every prop, backdrop, and piece of furniture you see here is included with your studio time. Reserve your hours and make it yours.

); } window.GalleryPage = GalleryPage;