/* ============================================================ Gallery page — with pagination (12 photos per page) ============================================================ */ const { useState: useGalState, useEffect: useGalEffect } = React; const CATEGORIES = ["All", "Classrooms", "Outdoor", "Events", "Art", "Music"]; const SIZES = ["s1","s2","s5","s4","s3","s7","s8","s6"]; const PER_PAGE = 12; /* 44 real photos from /img — categories distributed evenly */ const TILES = [ /* ── Classrooms (001 – 009) ─────────────────────────────── */ { cat:"Classrooms", caption:"Morning Circle", src:"img/photo-001.jpg" }, { cat:"Classrooms", caption:"Reading Nook", src:"img/photo-002.jpg" }, { cat:"Classrooms", caption:"Storytime", src:"img/photo-003.jpg" }, { cat:"Classrooms", caption:"Block Corner", src:"img/photo-004.jpg" }, { cat:"Classrooms", caption:"Puzzle Hour", src:"img/photo-005.jpg" }, { cat:"Classrooms", caption:"Busy Hands", src:"img/photo-006.jpg" }, { cat:"Classrooms", caption:"Cozy Corner", src:"img/photo-007.jpg" }, { cat:"Classrooms", caption:"Learning Together", src:"img/photo-008.jpg" }, { cat:"Classrooms", caption:"Discovery Table", src:"img/photo-009.jpg" }, /* ── Outdoor (010 – 018) ────────────────────────────────── */ { cat:"Outdoor", caption:"Garden Day", src:"img/photo-010.jpg" }, { cat:"Outdoor", caption:"Playtime", src:"img/photo-011.jpg" }, { cat:"Outdoor", caption:"Sports Day", src:"img/photo-012.jpg" }, { cat:"Outdoor", caption:"Sandbox Fun", src:"img/photo-013.jpg" }, { cat:"Outdoor", caption:"Nature Walk", src:"img/photo-014.jpg" }, { cat:"Outdoor", caption:"Tiny Gardeners", src:"img/photo-015.jpg" }, { cat:"Outdoor", caption:"Running Free", src:"img/photo-016.jpg" }, { cat:"Outdoor", caption:"Sunny Afternoon", src:"img/photo-017.jpg" }, { cat:"Outdoor", caption:"Water Play", src:"img/photo-018.jpg" }, /* ── Events (019 – 027) ─────────────────────────────────── */ { cat:"Events", caption:"Birthday Bash", src:"img/photo-019.jpg" }, { cat:"Events", caption:"Annual Day", src:"img/photo-020.jpg" }, { cat:"Events", caption:"Diwali Glow", src:"img/photo-021.jpg" }, { cat:"Events", caption:"Sports Meet", src:"img/photo-022.jpg" }, { cat:"Events", caption:"Carnival Day", src:"img/photo-023.jpg" }, { cat:"Events", caption:"Welcome Day", src:"img/photo-024.jpg" }, { cat:"Events", caption:"Celebration", src:"img/photo-025.jpg" }, { cat:"Events", caption:"Special Event", src:"img/photo-026.jpg" }, { cat:"Events", caption:"Fun Day", src:"img/photo-027.jpg" }, /* ── Art (028 – 036) ────────────────────────────────────── */ { cat:"Art", caption:"Messy Hands", src:"img/photo-028.jpg" }, { cat:"Art", caption:"Finger Painting", src:"img/photo-029.jpg" }, { cat:"Art", caption:"Art Show", src:"img/photo-030.jpg" }, { cat:"Art", caption:"Colour Play", src:"img/photo-031.jpg" }, { cat:"Art", caption:"Craft Time", src:"img/photo-032.jpg" }, { cat:"Art", caption:"Creative Minds", src:"img/photo-033.jpg" }, { cat:"Art", caption:"Clay Day", src:"img/photo-034.jpg" }, { cat:"Art", caption:"Drawing Fun", src:"img/photo-035.jpg" }, { cat:"Art", caption:"Rainbow Art", src:"img/photo-036.jpg" }, /* ── Music (037 – 044) ──────────────────────────────────── */ { cat:"Music", caption:"Tiny Band", src:"img/photo-037.jpg" }, { cat:"Music", caption:"Singing Time", src:"img/photo-038.jpg" }, { cat:"Music", caption:"Rhythm Class", src:"img/photo-039.jpg" }, { cat:"Music", caption:"Dance Day", src:"img/photo-040.jpg" }, { cat:"Music", caption:"Music Fun", src:"img/photo-041.jpg" }, { cat:"Music", caption:"Little Stars", src:"img/photo-042.jpg" }, { cat:"Music", caption:"Sound Play", src:"img/photo-043.jpg" }, { cat:"Music", caption:"Concert Time", src:"img/photo-044.jpg" }, ]; /* 12 photos spread across the set for the Instagram-style strip */ const INSTA_PHOTOS = [ "img/photo-001.jpg", "img/photo-005.jpg", "img/photo-010.jpg", "img/photo-015.jpg", "img/photo-019.jpg", "img/photo-023.jpg", "img/photo-028.jpg", "img/photo-032.jpg", "img/photo-036.jpg", "img/photo-039.jpg", "img/photo-042.jpg", "img/photo-044.jpg", ]; const UPLOAD_SIZES = ["s1","s2","s5","s4","s3","s7","s8","s6"]; const imgStyle = { position:"absolute", inset:0, width:"100%", height:"100%", objectFit:"cover" }; /* ── Pagination control component ──────────────────────────── */ const Pagination = ({ page, total, perPage, onChange }) => { const numPages = Math.ceil(total / perPage); if (numPages <= 1) return null; const pages = Array.from({ length: numPages }, (_, i) => i); return (
{pages.map(p => ( ))}
); }; const GalleryContent = () => { const [filter, setFilter] = useGalState("All"); const [page, setPage] = useGalState(0); const [uploaded, setUploaded] = useGalState([]); useGalEffect(() => { if (typeof XCELA_DB === "undefined") return; XCELA_DB.getAll("gallery").then(rows => setUploaded(rows) // server already returns rows with .url property ).catch(() => {}); }, []); /* Re-run reveal observer whenever tiles change (DB load, page flip, filter) */ useGalEffect(() => { const t = setTimeout(initReveal, 80); return () => clearTimeout(t); }, [uploaded, page, filter]); /* Reset to page 0 whenever the filter changes */ const handleFilter = (cat) => { setFilter(cat); setPage(0); }; /* All items for current filter (admin uploads first, then static tiles) */ const upFiltered = filter === "All" ? uploaded : uploaded.filter(u => u.category === filter); const tilesFiltered = filter === "All" ? TILES : TILES.filter(t => t.cat === filter); const allFiltered = [ ...upFiltered.map(u => ({ isUpload: true, ...u })), ...tilesFiltered, ]; const total = allFiltered.length; const pageItems = allFiltered.slice(page * PER_PAGE, (page + 1) * PER_PAGE); /* Scroll back to top of gallery on page change */ const handlePage = (p) => { setPage(p); document.querySelector(".filter-bar").scrollIntoView({ behavior: "smooth", block: "start" }); }; return ( <>
{/* Category filter */}
{CATEGORIES.map((c) => ( ))}
{/* Photo count + page info */}

{total === 0 ? "" : `Showing ${page * PER_PAGE + 1}–${Math.min((page + 1) * PER_PAGE, total)} of ${total} photos`}

{/* Grid */}
{pageItems.map((item, i) => { const size = SIZES[i % SIZES.length]; if (item.isUpload) { return (
{item.type === "video" ?
); } return (
{item.caption}
{item.caption}
); })}
{total === 0 && (
No photos yet — check back soon!
)} {/* Pagination controls */}
{/* Instagram-style feed */}
Follow @xceladreamland

Tiny moments, all week long

We post a peek into the day on Instagram. Mostly kids, sometimes a teacher dressed as a dinosaur.

{INSTA_PHOTOS.map((src, i) => (
school moment
))}
View Full Feed →
{/* Tour CTA */}
Better in person

Photos are great.
Visits are better.

Walk through, peek into a class, see the joyful chaos for yourself.

Book a Visit →
); }; renderPage("gallery.html", GalleryContent);