Overview
Ficha Dubai features a comprehensive image gallery system with thumbnail pagination, responsive layouts, and smooth navigation. The gallery adapts to different screen sizes and provides an intuitive browsing experience.
Gallery Features
Main Image Display
The gallery displays a large main image with navigation arrows and click-to-expand functionality:
const mainImage = $ ( "#gallery-main-image" );
// Make main image clickable to open lightbox
mainImage . classList . add ( "cursor-pointer" );
mainImage . onclick = () => openLightbox ( galleryCurrentIndex );
The gallery uses a sophisticated pagination system that adapts to screen size:
const getThumbsPerPage = () => {
return window . innerWidth < 768 ? 4 : 8 ;
};
const getTotalPages = ( totalItems , perPage ) => {
return Math . ceil ( totalItems / perPage );
};
On mobile devices (< 768px), the gallery shows 4 thumbnails per page. On desktop, it displays 8 thumbnails per page.
Rendering Thumbnails
The renderThumbnails() function handles the display of paginated thumbnails:
const renderThumbnails = () => {
thumbContainer . innerHTML = "" ;
galleryThumbsPerPage = getThumbsPerPage ();
const totalPages = getTotalPages ( resolvedImages . length , galleryThumbsPerPage );
const startIndex = galleryThumbPage * galleryThumbsPerPage ;
const endIndex = Math . min ( startIndex + galleryThumbsPerPage , resolvedImages . length );
for ( let i = startIndex ; i < endIndex ; i ++ ) {
const src = resolvedImages [ i ];
const thumb = document . createElement ( "div" );
const isActive = i === galleryCurrentIndex ;
thumb . className = `thumbnail-item aspect-square rounded-md overflow-hidden bg-slate-100 dark:bg-slate-700 cursor-pointer transition ${ isActive ? "ring-2 ring-primary opacity-100" : "opacity-80 hover:opacity-100" } ` ;
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" , () => setActive ( i ));
thumb . addEventListener ( "dblclick" , () => openLightbox ( i ));
thumbContainer . appendChild ( thumb );
}
// Update navigation buttons
if ( prevBtn ) prevBtn . disabled = galleryThumbPage === 0 ;
if ( nextBtn ) nextBtn . disabled = galleryThumbPage >= totalPages - 1 ;
// Render pagination dots
if ( paginationContainer ) {
renderPaginationDots ( paginationContainer , galleryThumbPage , totalPages , ( page ) => {
galleryThumbPage = page ;
renderThumbnails ();
}, false );
}
};
Code location: app.js:288-322
Gallery State Management
The gallery maintains its state through several global variables:
// Gallery state
let galleryImages = [];
let galleryCurrentIndex = 0 ;
let galleryThumbPage = 0 ;
let galleryThumbsPerPage = 8 ; // Will be adjusted for mobile
Code location: app.js:10-13
Setting Active Image
The setActive() function updates the main image and highlights the corresponding thumbnail:
const setActive = ( index ) => {
galleryCurrentIndex = index ;
mainImage . src = resolvedImages [ index ];
mainImage . alt = `Imagen ${ index + 1 } ` ;
updateGalleryThumbHighlights ();
// Show 360 button only on first image
const btn360Container = $ ( "#btn-360-container" );
if ( btn360Container && btn360Container . dataset . has360 === "true" ) {
btn360Container . style . display = index === 0 ? "flex" : "none" ;
}
};
Code location: app.js:261-272
Navigation Controls
Image Navigation
Users can navigate through images using arrow buttons or keyboard shortcuts:
const galleryNextImage = () => {
const newIndex = ( galleryCurrentIndex + 1 ) % resolvedImages . length ;
setActive ( newIndex );
// Auto-advance page if needed
const newPage = Math . floor ( newIndex / galleryThumbsPerPage );
if ( newPage !== galleryThumbPage ) {
galleryThumbPage = newPage ;
renderThumbnails ();
}
};
const galleryPrevImage = () => {
const newIndex = ( galleryCurrentIndex - 1 + resolvedImages . length ) % resolvedImages . length ;
setActive ( newIndex );
// Auto-advance page if needed
const newPage = Math . floor ( newIndex / galleryThumbsPerPage );
if ( newPage !== galleryThumbPage ) {
galleryThumbPage = newPage ;
renderThumbnails ();
}
};
Code location: app.js:324-344
Thumbnail Page Navigation
const thumbNextPage = () => {
const totalPages = getTotalPages ( resolvedImages . length , galleryThumbsPerPage );
if ( galleryThumbPage < totalPages - 1 ) {
galleryThumbPage ++ ;
renderThumbnails ();
}
};
const thumbPrevPage = () => {
if ( galleryThumbPage > 0 ) {
galleryThumbPage -- ;
renderThumbnails ();
}
};
Code location: app.js:346-359
Visual pagination indicators help users track their position:
const renderPaginationDots = ( container , currentPage , totalPages , onPageClick , isLight = false ) => {
container . innerHTML = "" ;
if ( totalPages <= 1 ) return ;
for ( let i = 0 ; i < totalPages ; i ++ ) {
const dot = document . createElement ( "button" );
const activeClass = isLight
? ( i === currentPage ? "bg-white" : "bg-white/40 hover:bg-white/60" )
: ( i === currentPage ? "bg-primary" : "bg-slate-300 dark:bg-slate-600 hover:bg-slate-400" );
dot . className = `w-2.5 h-2.5 rounded-full transition-colors ${ activeClass } ` ;
dot . addEventListener ( "click" , () => onPageClick ( i ));
container . appendChild ( dot );
}
};
Code location: app.js:39-52
Responsive Behavior
The gallery automatically adjusts when the window is resized:
// Handle window resize
window . addEventListener ( "resize" , () => {
const newPerPage = getThumbsPerPage ();
if ( newPerPage !== galleryThumbsPerPage ) {
galleryThumbsPerPage = newPerPage ;
galleryThumbPage = Math . floor ( galleryCurrentIndex / galleryThumbsPerPage );
renderThumbnails ();
}
});
Code location: app.js:380-387
HTML Structure
The gallery HTML uses Tailwind CSS for styling:
< section class = "bg-white dark:bg-slate-800 p-4 rounded-2xl shadow-xl" >
< div class = "relative aspect-[16/9] w-full rounded-xl overflow-hidden mb-4 group" >
< img id = "gallery-main-image" alt = "Imagen principal" class = "w-full h-full object-cover" src = "" />
<!-- Navigation arrows on main image -->
< button id = "gallery-main-prev" class = "absolute left-2 top-1/2 -translate-y-1/2 p-2 rounded-full bg-black/40 hover:bg-black/60 text-white transition-colors z-10" >
< span class = "material-icons-outlined" > chevron_left </ span >
</ button >
< button id = "gallery-main-next" class = "absolute right-2 top-1/2 -translate-y-1/2 p-2 rounded-full bg-black/40 hover:bg-black/60 text-white transition-colors z-10" >
< span class = "material-icons-outlined" > chevron_right </ span >
</ button >
</ div >
<!-- Thumbnails with pagination -->
< div class = "relative" >
< div class = "flex items-center gap-2" >
< button id = "thumb-prev" class = "flex-shrink-0 p-2 rounded-full bg-slate-100 dark:bg-slate-700 hover:bg-slate-200 dark:hover:bg-slate-600 text-slate-600 dark:text-slate-300 transition-colors disabled:opacity-30 disabled:cursor-not-allowed" >
< span class = "material-icons-outlined text-xl" > chevron_left </ span >
</ button >
< div id = "gallery-thumbnails" class = "flex-1 grid grid-cols-4 md:grid-cols-8 gap-2" ></ div >
< button id = "thumb-next" class = "flex-shrink-0 p-2 rounded-full bg-slate-100 dark:bg-slate-700 hover:bg-slate-200 dark:hover:bg-slate-600 text-slate-600 dark:text-slate-300 transition-colors disabled:opacity-30 disabled:cursor-not-allowed" >
< span class = "material-icons-outlined text-xl" > chevron_right </ span >
</ button >
</ div >
<!-- Pagination dots -->
< div id = "thumb-pagination" class = "flex justify-center gap-2 mt-3" ></ div >
</ div >
</ section >
Code location: index.html:100-137
Double-click any thumbnail to open the full-screen lightbox viewer.
Key Features
Responsive Thumbnails Automatically adjusts thumbnail count based on screen size (4 on mobile, 8 on desktop)
Pagination Visual pagination dots and arrow navigation for browsing large image sets
Highlight Active Active thumbnail is highlighted with a colored ring for easy identification
Lightbox Integration Click main image or double-click thumbnails to open full-screen lightbox