The lightbox functions provide a full-screen image viewing experience with thumbnail navigation, keyboard shortcuts, and gesture controls.
State Variables
The lightbox system maintains the following state:
let lightboxImages = []; // Array of image URLs
let lightboxCurrentIndex = 0; // Currently displayed image index
let lightboxThumbPage = 0; // Current thumbnail page
const LIGHTBOX_THUMBS_PER_PAGE = 8; // Fixed: 8 thumbnails per page
openLightbox
Opens the lightbox modal at a specific image index.
const openLightbox = (index) => {
const lightbox = $("#lightbox");
lightboxCurrentIndex = index;
// Calculate which page this image is on
lightboxThumbPage = Math.floor(index / LIGHTBOX_THUMBS_PER_PAGE);
updateLightboxImage();
renderLightboxThumbnails();
lightbox.classList.remove("hidden");
lightbox.classList.add("flex");
document.body.style.overflow = "hidden";
};
Zero-based index of the image to display initially
Behavior
- Sets
lightboxCurrentIndex to provided index
- Calculates appropriate
lightboxThumbPage for the image
- Calls
updateLightboxImage() to load the image
- Calls
renderLightboxThumbnails() to show thumbnails
- Shows lightbox by toggling classes: removes
hidden, adds flex
- Disables body scroll with
overflow: hidden
Location: ~/workspace/source/app.js:56-66
Example Usage
// Open lightbox at third image
openLightbox(2);
// Typically called from gallery thumbnail double-click
thumb.addEventListener("dblclick", () => openLightbox(i));
closeLightbox
Closes the lightbox modal and restores page scrolling.
const closeLightbox = () => {
const lightbox = $("#lightbox");
lightbox.classList.add("hidden");
lightbox.classList.remove("flex");
document.body.style.overflow = "";
};
Behavior
- Hides lightbox by toggling classes: adds
hidden, removes flex
- Restores body scroll by resetting
overflow style
Location: ~/workspace/source/app.js:68-73
Triggered By
- Close button click
- Escape key press
- Backdrop click (clicking outside the image)
updateLightboxImage
Updates the displayed image and counter in the lightbox.
const updateLightboxImage = () => {
const lightboxImage = $("#lightbox-image");
const lightboxCounter = $("#lightbox-counter");
if (lightboxImages.length > 0) {
lightboxImage.src = lightboxImages[lightboxCurrentIndex];
lightboxImage.alt = `Imagen ${lightboxCurrentIndex + 1}`;
lightboxCounter.textContent = `${lightboxCurrentIndex + 1} / ${lightboxImages.length}`;
}
// Update thumbnail highlights
updateLightboxThumbHighlights();
};
Behavior
- Updates
#lightbox-image src and alt attributes
- Updates
#lightbox-counter text with “current / total” format
- Calls
updateLightboxThumbHighlights() to update thumbnail styling
Location: ~/workspace/source/app.js:75-87
Return Value
None (void). Modifies DOM directly.
updateLightboxThumbHighlights
Updates thumbnail styling to indicate the active image.
const updateLightboxThumbHighlights = () => {
const thumbs = document.querySelectorAll("#lightbox-thumbnails .lightbox-thumb");
const startIndex = lightboxThumbPage * LIGHTBOX_THUMBS_PER_PAGE;
thumbs.forEach((thumb, i) => {
const actualIndex = startIndex + i;
const isActive = actualIndex === lightboxCurrentIndex;
thumb.classList.toggle("ring-2", isActive);
thumb.classList.toggle("ring-white", isActive);
thumb.classList.toggle("opacity-50", !isActive);
});
};
Behavior
- Queries all
.lightbox-thumb elements
- Calculates actual image index based on current page
- Toggles CSS classes:
- Active:
ring-2 ring-white, removes opacity-50
- Inactive:
opacity-50, removes ring classes
Location: ~/workspace/source/app.js:89-100
renderLightboxThumbnails
Renders the current page of lightbox thumbnails with navigation controls.
const renderLightboxThumbnails = () => {
const container = $("#lightbox-thumbnails");
const paginationContainer = $("#lightbox-pagination");
const prevBtn = $("#lightbox-thumb-prev");
const nextBtn = $("#lightbox-thumb-next");
container.innerHTML = "";
const totalPages = getTotalPages(lightboxImages.length, LIGHTBOX_THUMBS_PER_PAGE);
const startIndex = lightboxThumbPage * LIGHTBOX_THUMBS_PER_PAGE;
const endIndex = Math.min(startIndex + LIGHTBOX_THUMBS_PER_PAGE, lightboxImages.length);
// Creates thumbnail elements...
// Updates navigation buttons...
// Renders pagination dots...
}
Behavior
- Clears thumbnail container
- Calculates visible image range for current page
- Creates thumbnail DOM elements (8 per page)
- Binds click handlers to thumbnails:
thumb.addEventListener("click", () => {
lightboxCurrentIndex = i;
updateLightboxImage();
});
- Updates prev/next button disabled states
- Renders pagination dots with light styling (
isLight = true)
Location: ~/workspace/source/app.js:102-142
DOM Structure
Each thumbnail:
- Classes:
lightbox-thumb w-12 h-12 md:w-14 md:h-14 rounded-lg overflow-hidden cursor-pointer transition-all flex-shrink-0
- Active:
ring-2 ring-white
- Inactive:
opacity-50 hover:opacity-80
lightboxNext
Navigates to the next image in the lightbox, with wraparound and auto-pagination.
const lightboxNext = () => {
lightboxCurrentIndex = (lightboxCurrentIndex + 1) % lightboxImages.length;
// Auto-advance page if needed
const newPage = Math.floor(lightboxCurrentIndex / LIGHTBOX_THUMBS_PER_PAGE);
if (newPage !== lightboxThumbPage) {
lightboxThumbPage = newPage;
renderLightboxThumbnails();
} else {
updateLightboxImage();
}
};
Behavior
- Increments index with modulo wraparound (last → first)
- Calculates if new image is on a different thumbnail page
- If page changed: updates
lightboxThumbPage and calls renderLightboxThumbnails()
- If same page: calls
updateLightboxImage() only
Location: ~/workspace/source/app.js:144-154
Triggered By
- Next button click (
#lightbox-next)
- Right arrow key press
lightboxPrev
Navigates to the previous image in the lightbox, with wraparound and auto-pagination.
const lightboxPrev = () => {
lightboxCurrentIndex = (lightboxCurrentIndex - 1 + lightboxImages.length) % lightboxImages.length;
// Auto-advance page if needed
const newPage = Math.floor(lightboxCurrentIndex / LIGHTBOX_THUMBS_PER_PAGE);
if (newPage !== lightboxThumbPage) {
lightboxThumbPage = newPage;
renderLightboxThumbnails();
} else {
updateLightboxImage();
}
};
Behavior
- Decrements index with modulo wraparound (first → last)
- Calculates if new image is on a different thumbnail page
- If page changed: updates
lightboxThumbPage and calls renderLightboxThumbnails()
- If same page: calls
updateLightboxImage() only
Location: ~/workspace/source/app.js:156-166
Triggered By
- Previous button click (
#lightbox-prev)
- Left arrow key press
lightboxThumbNextPage
Advances to the next page of lightbox thumbnails.
const lightboxThumbNextPage = () => {
const totalPages = getTotalPages(lightboxImages.length, LIGHTBOX_THUMBS_PER_PAGE);
if (lightboxThumbPage < totalPages - 1) {
lightboxThumbPage++;
renderLightboxThumbnails();
}
};
Behavior
- Calculates total pages
- Checks if next page exists
- Increments
lightboxThumbPage and re-renders
Location: ~/workspace/source/app.js:168-174
lightboxThumbPrevPage
Goes back to the previous page of lightbox thumbnails.
const lightboxThumbPrevPage = () => {
if (lightboxThumbPage > 0) {
lightboxThumbPage--;
renderLightboxThumbnails();
}
};
Behavior
- Checks if previous page exists
- Decrements
lightboxThumbPage and re-renders
Location: ~/workspace/source/app.js:176-181
initLightbox
Initializes all lightbox event listeners and keyboard shortcuts.
const initLightbox = () => {
const lightbox = $("#lightbox");
const closeBtn = $("#lightbox-close");
const prevBtn = $("#lightbox-prev");
const nextBtn = $("#lightbox-next");
const thumbPrevBtn = $("#lightbox-thumb-prev");
const thumbNextBtn = $("#lightbox-thumb-next");
if (!lightbox) return;
closeBtn?.addEventListener("click", closeLightbox);
prevBtn?.addEventListener("click", lightboxPrev);
nextBtn?.addEventListener("click", lightboxNext);
thumbPrevBtn?.addEventListener("click", lightboxThumbPrevPage);
thumbNextBtn?.addEventListener("click", lightboxThumbNextPage);
// Close on backdrop click
lightbox.addEventListener("click", (e) => {
if (e.target === lightbox) closeLightbox();
});
// Keyboard navigation
document.addEventListener("keydown", (e) => {
if (lightbox.classList.contains("hidden")) return;
if (e.key === "Escape") closeLightbox();
if (e.key === "ArrowRight") lightboxNext();
if (e.key === "ArrowLeft") lightboxPrev();
});
};
Behavior
- Binds click handlers to all lightbox control buttons
- Registers backdrop click handler (clicking the dark overlay closes lightbox)
- Registers keyboard shortcuts:
- Escape: Close lightbox
- Arrow Right: Next image
- Arrow Left: Previous image
- Only processes keyboard events when lightbox is visible
Location: ~/workspace/source/app.js:183-212
When to Call
Call once during page initialization, before any lightbox interactions:
const loadData = async () => {
initLightbox(); // Initialize event listeners first
// ... then load and display property data
};
Integration Flow
- Initialization:
initLightbox() sets up event listeners
- Opening:
openLightbox(index) displays lightbox at specific image
- Navigation: User clicks next/prev or uses arrow keys →
lightboxNext()/lightboxPrev()
- Thumbnail selection: User clicks thumbnail → updates
lightboxCurrentIndex → updateLightboxImage()
- Page navigation:
lightboxThumbNextPage()/lightboxThumbPrevPage() for thumbnail pagination
- Closing: User presses Escape, clicks close button, or clicks backdrop →
closeLightbox()
Key Features
- Keyboard navigation: Arrow keys and Escape
- Auto-pagination: Thumbnails automatically advance when navigating to images on different pages
- Wraparound: Last image → first image, first image → last image
- Backdrop dismiss: Click outside image to close
- Image counter: “3 / 12” format display