@@ -100,19 +100,89 @@ const year = date.getFullYear();
100100 <span class="lightbox-title">Full-size image</span>
101101 <span class="lightbox-hint">Click outside or press Esc to close</span>
102102 </div>
103+ <button class="lightbox-nav lightbox-prev" aria-label="Previous image">‹</button>
103104 <div class="lightbox-container">
104105 <img src="" alt="" />
105106 </div >
107+ <button class =" lightbox-nav lightbox-next" aria-label =" Next image" >› </button >
106108 <button class =" lightbox-close" aria-label =" Close" >× </button >
107109 <div class =" lightbox-footer" >
108- <span >Click anywhere outside the image to close </span >
110+ <span class = " lightbox-counter " > </span >
109111 </div >
110112 `;
111113 document.body.appendChild(lightbox);
112114
113115 const lightboxImg = lightbox.querySelector('.lightbox-container img');
114116 const closeBtn = lightbox.querySelector('.lightbox-close');
115117 const container = lightbox.querySelector('.lightbox-container');
118+ const prevBtn = lightbox.querySelector('.lightbox-prev');
119+ const nextBtn = lightbox.querySelector('.lightbox-next');
120+ const counter = lightbox.querySelector('.lightbox-counter');
121+
122+ // Get year and slug from data attributes
123+ const article = document.querySelector('article[data-year][data-slug]');
124+ const year = article?.dataset.year;
125+ const slug = article?.dataset.slug;
126+
127+ // Collect all prose images (excluding those wrapped in links)
128+ const proseImages = Array.from(document.querySelectorAll('.prose img')).filter(
129+ (img) => img.parentElement?.tagName !== 'A'
130+ );
131+ let currentIndex = 0;
132+
133+ // Function to get original image path
134+ function getOriginalPath(src, baseName) {
135+ if (year && slug && baseName ) {
136+ return ` /originals/${year }/${slug }/${baseName }.png ` ;
137+ }
138+ return src ;
139+ }
140+
141+ // Function to load image with fallbacks
142+ function loadImage(src, alt, baseName) {
143+ lightboxImg .src = getOriginalPath (src , baseName );
144+ lightboxImg .alt = alt ;
145+
146+ if (baseName && year && slug ) {
147+ lightboxImg .onerror = () => {
148+ lightboxImg .src = ` /originals/${year }/${slug }/${baseName }.jpg ` ;
149+ lightboxImg .onerror = () => {
150+ lightboxImg .src = src ;
151+ };
152+ };
153+ }
154+ }
155+
156+ // Function to update navigation state
157+ function updateNavigation() {
158+ prevBtn .classList .toggle (' disabled' , currentIndex === 0 );
159+ nextBtn .classList .toggle (' disabled' , currentIndex === proseImages .length - 1 );
160+ counter .textContent = ` ${currentIndex + 1 } / ${proseImages .length } ` ;
161+ }
162+
163+ // Function to show image at index
164+ function showImage(index) {
165+ if (index < 0 || index >= proseImages .length ) return ;
166+ currentIndex = index ;
167+ const img = proseImages [index ];
168+ const src = img .getAttribute (' src' ) || ' ' ;
169+ const alt = img .getAttribute (' alt' ) || ' ' ;
170+ const match = src .match (/ \/ _astro\/ ([^ . ] + )\. / );
171+ const baseName = match ? match [1 ] : null ;
172+ loadImage (src , alt , baseName );
173+ updateNavigation ();
174+ }
175+
176+ // Navigation handlers
177+ prevBtn.addEventListener('click', (e) => {
178+ e .stopPropagation ();
179+ if (currentIndex > 0 ) showImage (currentIndex - 1 );
180+ } );
181+
182+ nextBtn.addEventListener('click', (e) => {
183+ e .stopPropagation ();
184+ if (currentIndex < proseImages .length - 1 ) showImage (currentIndex + 1 );
185+ } );
116186
117187 // Close lightbox on overlay click (but not container), close button, or Escape key
118188 lightbox.addEventListener('click', (e) => {
@@ -122,52 +192,26 @@ const year = date.getFullYear();
122192 } );
123193 // Prevent closing when clicking the container
124194 container.addEventListener('click', (e) => e.stopPropagation());
195+
196+ // Keyboard navigation
125197 document.addEventListener('keydown', (e) => {
198+ if (! lightbox .classList .contains (' active' )) return ;
126199 if (e .key === ' Escape' ) lightbox .classList .remove (' active' );
200+ if (e .key === ' ArrowLeft' && currentIndex > 0 ) showImage (currentIndex - 1 );
201+ if (e .key === ' ArrowRight' && currentIndex < proseImages .length - 1 ) showImage (currentIndex + 1 );
127202 } );
128203
129- // Get year and slug from data attributes
130- const article = document.querySelector('article[data-year][data-slug]');
131- const year = article?.dataset.year;
132- const slug = article?.dataset.slug;
133-
134204 // Make prose images clickable to open lightbox with original
135- document.querySelectorAll('.prose img').forEach((img) => {
136- // Skip if already wrapped in a link
137- if (img .parentElement ?.tagName === ' A' ) return ;
138-
205+ proseImages.forEach((img, index) => {
139206 img .addEventListener (' click' , () => {
140- // Extract the filename from the optimized src
207+ currentIndex = index ;
141208 const src = img .getAttribute (' src' ) || ' ' ;
142209 const alt = img .getAttribute (' alt' ) || ' ' ;
143-
144- // Try to find the original image
145- // The optimized path is like /_astro/filename.hash.webp
146- // We need to map it back to /originals/YEAR/SLUG/filename.png
147210 const match = src .match (/ \/ _astro\/ ([^ . ] + )\. / );
148- if (match && year && slug ) {
149- const baseName = match [1 ];
150- // Try to load the original
151- const originalPath = ` /originals/${year }/${slug }/${baseName }.png ` ;
152- lightboxImg .src = originalPath ;
153- lightboxImg .alt = alt ;
154- lightbox .classList .add (' active' );
155-
156- // Fallback to optimized if original fails
157- lightboxImg .onerror = () => {
158- // Try jpg
159- lightboxImg .src = ` /originals/${year }/${slug }/${baseName }.jpg ` ;
160- lightboxImg .onerror = () => {
161- // Fall back to optimized version
162- lightboxImg .src = src ;
163- };
164- };
165- } else {
166- // Use the src as-is if we can't parse it
167- lightboxImg .src = src ;
168- lightboxImg .alt = alt ;
169- lightbox .classList .add (' active' );
170- }
211+ const baseName = match ? match [1 ] : null ;
212+ loadImage (src , alt , baseName );
213+ updateNavigation ();
214+ lightbox .classList .add (' active' );
171215 });
172216 } );
173217 </script >
0 commit comments