Skip to main content

Overview

The lightbox provides a full-screen, immersive image viewing experience with keyboard navigation, thumbnail pagination, and smooth transitions. It’s optimized for both desktop and mobile devices. The lightbox maintains its own state independently from the main gallery:
// Lightbox state
let lightboxImages = [];
let lightboxCurrentIndex = 0;
let lightboxThumbPage = 0;
const LIGHTBOX_THUMBS_PER_PAGE = 8;
Code location: app.js:3-7
The lightbox always displays 8 thumbnails per page, unlike the main gallery which adjusts based on screen size.

Opening the Lightbox

The openLightbox() function initializes the lightbox and displays the selected image:
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";
};
Code location: app.js:56-66
The lightbox automatically calculates which thumbnail page to display based on the selected image index.

Closing the Lightbox

The lightbox can be closed via the close button, Escape key, or clicking the backdrop:
const closeLightbox = () => {
  const lightbox = $("#lightbox");
  lightbox.classList.add("hidden");
  lightbox.classList.remove("flex");
  document.body.style.overflow = "";
};
Code location: app.js:68-73

Image Navigation

Next/Previous Image

Navigate through images with arrow buttons or keyboard shortcuts:
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();
  }
};

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();
  }
};
Code location: app.js:144-166
Navigation wraps around: clicking “next” on the last image returns to the first image, and vice versa.

Updating the Display

The updateLightboxImage() function updates the main image and counter:
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();
};
Code location: app.js:75-87

Thumbnail Navigation

Rendering Thumbnails

The lightbox renders its own paginated thumbnail strip:
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);
  
  for (let i = startIndex; i < endIndex; i++) {
    const src = lightboxImages[i];
    const thumb = document.createElement("div");
    const isActive = i === lightboxCurrentIndex;
    thumb.className = `lightbox-thumb w-12 h-12 md:w-14 md:h-14 rounded-lg overflow-hidden cursor-pointer transition-all flex-shrink-0 ${isActive ? "ring-2 ring-white" : "opacity-50 hover:opacity-80"}`;
    const img = document.createElement("img");
    img.src = src;
    img.alt = `Miniatura ${i + 1}`;
    img.className = "w-full h-full object-cover";
    thumb.appendChild(img);
    thumb.addEventListener("click", () => {
      lightboxCurrentIndex = i;
      updateLightboxImage();
    });
    container.appendChild(thumb);
  }
  
  // Update navigation buttons
  if (prevBtn) prevBtn.disabled = lightboxThumbPage === 0;
  if (nextBtn) nextBtn.disabled = lightboxThumbPage >= totalPages - 1;
  
  // Render pagination dots
  if (paginationContainer) {
    renderPaginationDots(paginationContainer, lightboxThumbPage, totalPages, (page) => {
      lightboxThumbPage = page;
      renderLightboxThumbnails();
    }, true);
  }
};
Code location: app.js:102-142

Highlight Active Thumbnail

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);
  });
};
Code location: app.js:89-100
const lightboxThumbNextPage = () => {
  const totalPages = getTotalPages(lightboxImages.length, LIGHTBOX_THUMBS_PER_PAGE);
  if (lightboxThumbPage < totalPages - 1) {
    lightboxThumbPage++;
    renderLightboxThumbnails();
  }
};

const lightboxThumbPrevPage = () => {
  if (lightboxThumbPage > 0) {
    lightboxThumbPage--;
    renderLightboxThumbnails();
  }
};
Code location: app.js:168-181

Keyboard Shortcuts

The lightbox supports keyboard navigation for efficient browsing:
// 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();
});
Code location: app.js:205-211

Escape

Close the lightbox

Arrow Right

Next image

Arrow Left

Previous image

Initialization

The lightbox is initialized when the page loads:
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();
  });
};
Code location: app.js:183-212

HTML Structure

The lightbox modal overlays the entire screen with a dark backdrop:
<!-- Lightbox Modal -->
<div id="lightbox" class="fixed inset-0 z-[100] hidden items-center justify-center bg-black/90 backdrop-blur-sm">
  <button id="lightbox-close" class="absolute top-4 right-4 p-3 rounded-full bg-white/10 hover:bg-white/20 text-white transition-colors z-10">
    <span class="material-icons-outlined text-2xl">close</span>
  </button>
  <button id="lightbox-prev" class="absolute left-4 top-1/2 -translate-y-1/2 p-3 md:p-4 rounded-full bg-white/10 hover:bg-white/20 text-white transition-colors z-10">
    <span class="material-icons-outlined text-2xl md:text-3xl">chevron_left</span>
  </button>
  <button id="lightbox-next" class="absolute right-4 top-1/2 -translate-y-1/2 p-3 md:p-4 rounded-full bg-white/10 hover:bg-white/20 text-white transition-colors z-10">
    <span class="material-icons-outlined text-2xl md:text-3xl">chevron_right</span>
  </button>
  <div class="relative max-w-[90vw] max-h-[70vh] md:max-h-[75vh] flex items-center justify-center">
    <img id="lightbox-image" class="max-w-full max-h-[65vh] md:max-h-[70vh] object-contain rounded-lg shadow-2xl" src="" alt=""/>
  </div>
  <!-- Lightbox bottom controls -->
  <div class="absolute bottom-4 md:bottom-6 left-0 right-0 flex flex-col items-center gap-3">
    <!-- Thumbnails with pagination -->
    <div class="flex items-center gap-2 max-w-[95vw] md:max-w-[80vw]">
      <button id="lightbox-thumb-prev" class="flex-shrink-0 p-2 rounded-full bg-white/10 hover:bg-white/20 text-white transition-colors disabled:opacity-30 disabled:cursor-not-allowed">
        <span class="material-icons-outlined">chevron_left</span>
      </button>
      <div id="lightbox-thumbnails" class="flex gap-2 p-2 bg-black/50 rounded-xl overflow-hidden"></div>
      <button id="lightbox-thumb-next" class="flex-shrink-0 p-2 rounded-full bg-white/10 hover:bg-white/20 text-white transition-colors disabled:opacity-30 disabled:cursor-not-allowed">
        <span class="material-icons-outlined">chevron_right</span>
      </button>
    </div>
    <!-- Counter and pagination dots -->
    <div class="flex items-center gap-3">
      <span id="lightbox-counter" class="text-white/80 text-sm font-medium bg-black/50 px-4 py-2 rounded-full">1 / 10</span>
      <div id="lightbox-pagination" class="flex gap-1.5"></div>
    </div>
  </div>
</div>
Code location: index.html:243-275

Features

Keyboard Control

Navigate with arrow keys, close with Escape

Backdrop Click

Click outside the image to close the lightbox

Image Counter

Shows current image number and total count

Thumbnail Preview

Bottom thumbnail strip with pagination for quick navigation

User Experience

1

Open Lightbox

Click the main gallery image or double-click any thumbnail to open the lightbox
2

Navigate

Use arrow buttons, keyboard arrows, or click thumbnails to browse images
3

Close

Press Escape, click the close button, or click outside the image to exit

Build docs developers (and LLMs) love