diff --git a/links/templates/links/link_list.html b/links/templates/links/link_list.html
index 538f93f..a09a2ea 100644
--- a/links/templates/links/link_list.html
+++ b/links/templates/links/link_list.html
@@ -466,6 +466,33 @@
.fan-lightbox-close:hover, .fan-lightbox-close:focus-visible { background: rgba(255, 255, 255, 0.28); }
.fan-lightbox-close svg { width: 1.2rem; height: 1.2rem; }
+ /* ---------- Fan lightbox: prev / next navigation ---------- */
+ .fan-lightbox-nav {
+ position: absolute; top: 50%; transform: translateY(-50%);
+ width: 3rem; height: 3rem; border-radius: 50%; border: none;
+ display: flex; align-items: center; justify-content: center;
+ background: rgba(255, 255, 255, 0.14); color: #fff; cursor: pointer;
+ transition: background 0.2s ease, opacity 0.2s ease;
+ -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
+ }
+ .fan-lightbox-nav:hover, .fan-lightbox-nav:focus-visible { background: rgba(255, 255, 255, 0.28); }
+ .fan-lightbox-nav svg { width: 1.6rem; height: 1.6rem; }
+ .fan-lightbox-nav.prev { left: 1.1rem; }
+ .fan-lightbox-nav.next { right: 1.1rem; }
+ .fan-lightbox-counter {
+ position: absolute; bottom: 1.3rem; left: 50%; transform: translateX(-50%);
+ padding: 0.35rem 1rem; border-radius: 980px;
+ background: rgba(255, 255, 255, 0.14); color: #fff;
+ font-size: 0.85rem; font-weight: 500; letter-spacing: 0.01em;
+ -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
+ pointer-events: none;
+ }
+ @media (max-width: 640px) {
+ .fan-lightbox-nav { width: 2.6rem; height: 2.6rem; }
+ .fan-lightbox-nav.prev { left: 0.6rem; }
+ .fan-lightbox-nav.next { right: 0.6rem; }
+ }
+
/* ---------- Accessibility: visible keyboard focus ---------- */
a:focus-visible, button:focus-visible {
outline: 2px solid var(--apple-blue);
@@ -729,6 +756,17 @@
+
+
+
@@ -871,11 +909,41 @@
const fanLightbox = document.getElementById('fan-lightbox');
const fanLightboxImg = document.getElementById('fan-lightbox-img');
const fanLightboxClose = document.getElementById('fan-lightbox-close');
+ const fanLightboxPrev = document.getElementById('fan-lightbox-prev');
+ const fanLightboxNext = document.getElementById('fan-lightbox-next');
+ const fanLightboxCounter = document.getElementById('fan-lightbox-counter');
+
+ // Snapshot of the five fan-card images, captured once so navigation works
+ // even if the DOM shuffles later. We store the live
elements and
+ // read their current src each time, so refreshed (re-shuffled) images are
+ // reflected without rebuilding this list.
+ const fanImages = Array.from(document.querySelectorAll('.fan-card img'));
+ let fanIndex = -1;
+
+ function showFanImage(index) {
+ if (!fanImages.length || !fanLightboxImg) return;
+ // Wrap around the ends so left/right never dead-end.
+ fanIndex = ((index % fanImages.length) + fanImages.length) % fanImages.length;
+ const src = fanImages[fanIndex].currentSrc || fanImages[fanIndex].src;
+ // Preserve any inline opacity transition state from the source image.
+ fanLightboxImg.src = src;
+ fanLightboxImg.alt = fanImages[fanIndex].alt;
+ if (fanLightboxCounter) {
+ fanLightboxCounter.textContent = (fanIndex + 1) + ' / ' + fanImages.length;
+ }
+ }
window.openFanLightbox = function (img) {
if (!img || !fanLightbox || !fanLightboxImg) return;
- fanLightboxImg.src = img.currentSrc || img.src;
- fanLightboxImg.alt = img.alt;
+ // Track which of the five images was clicked so navigation starts there.
+ fanIndex = fanImages.indexOf(img);
+ if (fanIndex < 0) {
+ // Fallback: match by current src if the element reference was replaced.
+ const src = img.currentSrc || img.src;
+ fanIndex = fanImages.findIndex(i => (i.currentSrc || i.src) === src);
+ if (fanIndex < 0) fanIndex = 0;
+ }
+ showFanImage(fanIndex);
fanLightbox.hidden = false;
document.body.style.overflow = 'hidden';
fanLightboxClose.focus();
@@ -885,17 +953,35 @@
if (!fanLightbox) return;
fanLightbox.hidden = true;
fanLightboxImg.src = '';
+ fanIndex = -1;
document.body.style.overflow = '';
}
if (fanLightbox) {
fanLightboxClose.addEventListener('click', closeFanLightbox);
- // Click the dimmed backdrop (not the image itself) to dismiss.
+ if (fanLightboxPrev) {
+ fanLightboxPrev.addEventListener('click', function (event) {
+ event.stopPropagation();
+ showFanImage(fanIndex - 1);
+ fanLightboxPrev.focus();
+ });
+ }
+ if (fanLightboxNext) {
+ fanLightboxNext.addEventListener('click', function (event) {
+ event.stopPropagation();
+ showFanImage(fanIndex + 1);
+ fanLightboxNext.focus();
+ });
+ }
+ // Click the dimmed backdrop (not the image or nav buttons) to dismiss.
fanLightbox.addEventListener('click', function (event) {
if (event.target === fanLightbox) closeFanLightbox();
});
document.addEventListener('keydown', function (event) {
- if (event.key === 'Escape' && !fanLightbox.hidden) closeFanLightbox();
+ if (fanLightbox.hidden) return;
+ if (event.key === 'Escape') closeFanLightbox();
+ else if (event.key === 'ArrowLeft') { event.preventDefault(); showFanImage(fanIndex - 1); }
+ else if (event.key === 'ArrowRight') { event.preventDefault(); showFanImage(fanIndex + 1); }
});
}