1/** 2 * Frontend Lightbox JavaScript with Gallery Navigation for Historicos JCF 3 */ 4document.addEventListener('DOMContentLoaded', function() { 5 let currentGalleryItems = []; 6 let currentIndex = -1; 7 8 // Create lightbox HTML structure with navigation arrows and optional sidebar 9 const overlay = document.createElement('div'); 10 overlay.className = 'jcf-lightbox-overlay'; 11 overlay.innerHTML = ` 12 <div class="jcf-lightbox-content"> 13 <button class="jcf-lightbox-close" aria-label="Cerrar">×</button> 14 <button class="jcf-lightbox-prev" aria-label="Anterior">❮</button> 15 <button class="jcf-lightbox-next" aria-label="Siguiente">❯</button> 16 <div class="jcf-lightbox-main"> 17 <img src="" alt="" /> 18 <div class="jcf-lightbox-caption"></div> 19 </div> 20 <div class="jcf-lightbox-sidebar" style="display: none;"> 21 <h3 class="jcf-lightbox-sidebar-title">Corte de Honor</h3> 22 <ul class="jcf-lightbox-sidebar-list"></ul> 23 </div> 24 </div> 25 `; 26 document.body.appendChild(overlay); 27 28 const lightboxContent = overlay.querySelector('.jcf-lightbox-content'); 29 const lightboxImg = overlay.querySelector('.jcf-lightbox-main img'); 30 const lightboxCaption = overlay.querySelector('.jcf-lightbox-caption'); 31 const lightboxSidebar = overlay.querySelector('.jcf-lightbox-sidebar'); 32 const lightboxSidebarTitle = overlay.querySelector('.jcf-lightbox-sidebar-title'); 33 const lightboxSidebarList = overlay.querySelector('.jcf-lightbox-sidebar-list'); 34 const closeBtn = overlay.querySelector('.jcf-lightbox-close'); 35 const prevBtn = overlay.querySelector('.jcf-lightbox-prev'); 36 const nextBtn = overlay.querySelector('.jcf-lightbox-next'); 37 38 /** 39 * Update the content of the lightbox based on target index. 40 */ 41 function updateLightbox(index) { 42 if (index < 0 || index >= currentGalleryItems.length) return; 43 currentIndex = index; 44 45 const item = currentGalleryItems[currentIndex]; 46 const imgUrl = item.getAttribute('href'); 47 const title = item.getAttribute('title'); 48 49 const type = item.dataset.type || 'fmv'; 50 const year = item.dataset.year || ''; 51 const artist = item.dataset.artist || ''; 52 const lema = item.dataset.lema || ''; 53 const cortesanas = item.dataset.cortesanas || ''; 54 55 // Set image and caption 56 lightboxImg.src = imgUrl; 57 lightboxImg.alt = title; 58 59 let captionHTML = ''; 60 if (type === 'falla_mun' || type === 'falla_mun_inf') { 61 captionHTML = `<strong>${title}</strong> (${year})`; 62 if (artist) { 63 captionHTML += `<br/><span class="jcf-lightbox-artist-sub" style="font-size: 0.9em; opacity: 0.85; font-style: italic; display: block; margin-top: 4px;">Artista: ${artist}</span>`; 64 } 65 } else if (type === 'ninot' || type === 'ninot_inf') { 66 captionHTML = `<strong>${title}</strong> (${year})`; 67 let extra = ''; 68 if (artist) { 69 extra += `Artista: ${artist}`; 70 } 71 if (lema) { 72 if (extra) extra += ' - '; 73 extra += `Lema: "${lema}"`; 74 } 75 if (extra) { 76 captionHTML += `<br/><span class="jcf-lightbox-artist-sub" style="font-size: 0.9em; opacity: 0.85; font-style: italic; display: block; margin-top: 4px;">${extra}</span>`; 77 } 78 } else if (type === 'cartel') { 79 captionHTML = `<strong>${title}</strong>`; 80 } else if (type === 'corte_no_photos') { 81 captionHTML = `<strong>Cort d´Honor de la Fallera Mayor de Valencia de ${year}</strong>`; 82 } else if (type === 'corte_inf_no_photos') { 83 captionHTML = `<strong>Cort d´Honor de la Fallera Mayor Infantil de Valencia de ${year}</strong>`; 84 } else { 85 captionHTML = `<strong>${title}</strong>${year ? ' (' + year + ')' : ''}`; 86 } 87 lightboxCaption.innerHTML = captionHTML; 88 89 // Side panel logic for Cortes without photos 90 if (type === 'corte_no_photos' || type === 'corte_inf_no_photos') { 91 lightboxContent.classList.add('has-sidebar'); 92 lightboxSidebar.style.display = 'flex'; 93 94 const isInfantil = (type === 'corte_inf_no_photos'); 95 lightboxSidebarTitle.textContent = isInfantil ? 'Cort d´Honor Infantil ' + year : 'Cort d´Honor ' + year; 96 97 lightboxSidebarList.innerHTML = ''; 98 if (cortesanas) { 99 const names = cortesanas.split('|');
100 names.forEach(name => { 101 if (name.trim() !== '') { 102 const li = document.createElement('li'); 103 li.className = 'jcf-lightbox-sidebar-item'; 104 li.textContent = name; 105 lightboxSidebarList.appendChild(li); 106 } 107 }); 108 } 109 } else { 110 lightboxContent.classList.remove('has-sidebar'); 111 lightboxSidebar.style.display = 'none'; 112 lightboxSidebarList.innerHTML = ''; 113 } 114 115 // Toggle navigation buttons visibility if there's only 1 item 116 if (currentGalleryItems.length <= 1) { 117 prevBtn.style.display = 'none'; 118 nextBtn.style.display = 'none'; 119 } else { 120 prevBtn.style.display = ''; 121 nextBtn.style.display = ''; 122 } 123 } 124 125 // Handle click on grid items to open the lightbox 126 document.addEventListener('click', function(e) { 127 const link = e.target.closest('.jcf-grid-lightbox'); 128 if (link) { 129 e.preventDefault(); 130 131 // Gather all items inside the same grid container to enable navigation 132 const container = link.closest('.jcf-grid-container'); 133 if (container) { 134 currentGalleryItems = Array.from(container.querySelectorAll('.jcf-grid-lightbox')); 135 } else { 136 currentGalleryItems = [link]; 137 } 138 139 // Find current index 140 currentIndex = currentGalleryItems.indexOf(link); 141 142 // Update content and open 143 updateLightbox(currentIndex); 144 overlay.classList.add('active'); 145 document.body.classList.add('jcf-lightbox-open'); 146 } 147 }); 148 149 /** 150 * Close the lightbox modal. 151 */ 152 function closeLightbox() { 153 overlay.classList.remove('active'); 154 document.body.classList.remove('jcf-lightbox-open'); 155 // Clear source after transition finishes to avoid a flash of previous image 156 setTimeout(function() { 157 lightboxImg.src = ''; 158 lightboxImg.alt = ''; 159 lightboxContent.classList.remove('has-sidebar'); 160 lightboxSidebar.style.display = 'none'; 161 lightboxSidebarList.innerHTML = ''; 162 currentGalleryItems = []; 163 currentIndex = -1; 164 }, 300); 165 } 166 167 /** 168 * Navigate to next image. 169 */ 170 function showNext() { 171 if (currentGalleryItems.length <= 1) return; 172 let nextIndex = currentIndex + 1; 173 if (nextIndex >= currentGalleryItems.length) { 174 nextIndex = 0; // Loop back to the first item 175 } 176 updateLightbox(nextIndex); 177 } 178 179 /** 180 * Navigate to previous image. 181 */ 182 function showPrev() { 183 if (currentGalleryItems.length <= 1) return; 184 let prevIndex = currentIndex - 1; 185 if (prevIndex < 0) { 186 prevIndex = currentGalleryItems.length - 1; // Loop to the last item 187 } 188 updateLightbox(prevIndex); 189 } 190 191 // Close and Navigation Event Listeners 192 closeBtn.addEventListener('click', closeLightbox); 193 prevBtn.addEventListener('click', function(e) { 194 e.stopPropagation(); 195 showPrev(); 196 }); 197 nextBtn.addEventListener('click', function(e) { 198 e.stopPropagation(); 199 showNext(); 200 }); 201 202 overlay.addEventListener('click', function(e) { 203 // Close if click is on the overlay backdrop itself 204 if (e.target === overlay) { 205 closeLightbox(); 206 } 207 }); 208 209 // Keyboard navigation (Escape, Left Arrow, Right Arrow) 210 document.addEventListener('keydown', function(e) { 211 if (!overlay.classList.contains('active')) return; 212 213 if (e.key === 'Escape') { 214 closeLightbox(); 215 } else if (e.key === 'ArrowRight') { 216 showNext(); 217 } else if (e.key === 'ArrowLeft') { 218 showPrev(); 219 } 220 }); 221});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.