feat(dailies): replace custom lightbox with PhotoSwipe v5

Drops ~80 lines of fragile custom lightbox JS/HTML/CSS in favour of
PhotoSwipe v5 loaded from CDN. Slides are now <a> tags (the pswp-gallery
children) which always fire click events on iOS even inside scroll
containers — the root cause of the mobile tap issue. Pinch-to-zoom and
swipe-to-dismiss come for free. Dot sync kept as a separate vanilla JS
block.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
This commit is contained in:
2026-06-21 19:31:27 +02:00
parent 770a96b099
commit 30c8937566
2 changed files with 39 additions and 114 deletions
+10 -54
View File
@@ -202,13 +202,18 @@ body::after {
color: var(--color-ink-muted); color: var(--color-ink-muted);
} }
.journal-photo-wrap {
position: relative;
margin-bottom: var(--space-3);
border-radius: var(--radius-md);
overflow: hidden;
}
.journal-photo-strip { .journal-photo-strip {
display: flex; display: flex;
overflow-x: scroll; overflow-x: scroll;
scroll-snap-type: x mandatory; scroll-snap-type: x mandatory;
scrollbar-width: none; scrollbar-width: none;
border-radius: var(--radius-md);
margin-bottom: var(--space-3);
} }
.journal-photo-strip::-webkit-scrollbar { display: none; } .journal-photo-strip::-webkit-scrollbar { display: none; }
@@ -218,11 +223,8 @@ body::after {
scroll-snap-align: start; scroll-snap-align: start;
aspect-ratio: 4 / 3; aspect-ratio: 4 / 3;
overflow: hidden; overflow: hidden;
cursor: zoom-in;
background: none;
border: none;
padding: 0;
display: block; display: block;
text-decoration: none;
} }
.journal-photo-slide img { .journal-photo-slide img {
@@ -230,11 +232,8 @@ body::after {
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
display: block; display: block;
transition: opacity 0.15s;
} }
.journal-photo-slide:hover img { opacity: 0.9; }
.journal-photo-dots { .journal-photo-dots {
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -403,52 +402,9 @@ body::after {
.gallery-thumb:focus { outline: 2px solid var(--color-accent); outline-offset: 2px; } .gallery-thumb:focus { outline: 2px solid var(--color-accent); outline-offset: 2px; }
/* ── Lightbox ────────────────────────────────────────────────────────────────── */ /* ── PhotoSwipe overrides ─────────────────────────────────────────────────────── */
.lightbox { .pswp__bg { background: rgba(0,0,0,0.96); }
position: fixed;
inset: 0;
background: rgba(0,0,0,0.94);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}
.lightbox[hidden] { display: none; }
.lightbox-img {
max-width: 92vw;
max-height: 90vh;
object-fit: contain;
border-radius: var(--radius-sm);
display: block;
}
.lightbox-close,
.lightbox-prev,
.lightbox-next {
position: absolute;
background: rgba(255,255,255,0.12);
border: none;
color: #fff;
cursor: pointer;
border-radius: 50%;
width: 44px;
height: 44px;
font-size: 1.4rem;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.15s;
}
.lightbox-close { top: 1rem; right: 1rem; }
.lightbox-prev { left: 0.75rem; top: 50%; transform: translateY(-50%); }
.lightbox-next { right: 0.75rem; top: 50%; transform: translateY(-50%); }
.lightbox-close:hover,
.lightbox-prev:hover,
.lightbox-next:hover { background: rgba(255,255,255,0.26); }
/* ── Map page ────────────────────────────────────────────────────────────────── */ /* ── Map page ────────────────────────────────────────────────────────────────── */
+29 -60
View File
@@ -121,12 +121,19 @@ feedMap.on('load', function () {
{% set images = entry.media.images %} {% set images = entry.media.images %}
{% if images|length > 0 %} {% if images|length > 0 %}
<div class="journal-photo-strip" data-slides="{{ images|length }}"> {% do assets.addCss('https://cdn.jsdelivr.net/npm/photoswipe@5/dist/photoswipe.css') %}
{% for img in images %} <div class="journal-photo-wrap">
<button class="journal-photo-slide" data-full="{{ img.url }}" data-alt="{{ entry.title }}" aria-label="View photo"> <div class="journal-photo-strip pswp-gallery" id="gallery-{{ entry.slug }}">
<img src="{{ img.cropResize(900, 675).url }}" alt="{{ entry.title }}" loading="lazy"> {% for img in images %}
</button> <a class="journal-photo-slide"
{% endfor %} href="{{ img.url }}"
data-pswp-width="{{ img.width }}"
data-pswp-height="{{ img.height }}"
target="_blank">
<img src="{{ img.cropResize(900, 675).url }}" alt="{{ entry.title }}" loading="lazy">
</a>
{% endfor %}
</div>
</div> </div>
{% if images|length > 1 %} {% if images|length > 1 %}
<div class="journal-photo-dots" aria-hidden="true"> <div class="journal-photo-dots" aria-hidden="true">
@@ -164,73 +171,35 @@ feedMap.on('load', function () {
{% endif %} {% endif %}
</div> </div>
<div class="lightbox" id="feed-lightbox" role="dialog" aria-modal="true" aria-label="Photo viewer" hidden> <script type="module">
<button class="lightbox-close" id="feed-lb-close" aria-label="Close">✕</button> import PhotoSwipeLightbox from 'https://cdn.jsdelivr.net/npm/photoswipe@5/dist/photoswipe-lightbox.esm.min.js';
<button class="lightbox-prev" id="feed-lb-prev" aria-label="Previous"></button> const lightbox = new PhotoSwipeLightbox({
<img class="lightbox-img" id="feed-lb-img" src="" alt=""> gallery: '.pswp-gallery',
<button class="lightbox-next" id="feed-lb-next" aria-label="Next"></button> children: 'a.journal-photo-slide',
</div> pswpModule: () => import('https://cdn.jsdelivr.net/npm/photoswipe@5/dist/photoswipe.esm.min.js')
});
lightbox.init();
</script>
<script> <script>
/* ── Journal photo strip: dot sync + lightbox ───────────────── */ /* ── Dot sync ────────────────────────────────────────────────── */
(function () { (function () {
/* Dot sync */ document.querySelectorAll('.journal-photo-wrap').forEach(function (wrap) {
document.querySelectorAll('.journal-photo-strip').forEach(function (strip) { var strip = wrap.querySelector('.journal-photo-strip');
var slides = strip.querySelectorAll('.journal-photo-slide'); var slides = Array.from(strip.querySelectorAll('.journal-photo-slide'));
var strip_id = strip.closest('article'); var article = wrap.closest('article');
if (!strip_id) return; var dots = article ? Array.from(article.querySelectorAll('.journal-photo-dot')) : [];
var dots = strip_id.querySelectorAll('.journal-photo-dot');
if (!dots.length) return; if (!dots.length) return;
var io = new IntersectionObserver(function (entries) { var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) { entries.forEach(function (e) {
if (!e.isIntersecting) return; if (!e.isIntersecting) return;
var idx = Array.from(slides).indexOf(e.target); var idx = slides.indexOf(e.target);
dots.forEach(function (d) { d.classList.remove('is-active'); }); dots.forEach(function (d) { d.classList.remove('is-active'); });
if (dots[idx]) dots[idx].classList.add('is-active'); if (dots[idx]) dots[idx].classList.add('is-active');
}); });
}, { root: strip, threshold: 0.5 }); }, { root: strip, threshold: 0.5 });
slides.forEach(function (s) { io.observe(s); }); slides.forEach(function (s) { io.observe(s); });
}); });
/* Lightbox */
var allSlides = Array.from(document.querySelectorAll('.journal-photo-slide[data-full]'));
if (!allSlides.length) return;
var lightbox = document.getElementById('feed-lightbox');
var lbImg = document.getElementById('feed-lb-img');
var current = 0;
function open(index) {
current = index;
lbImg.src = allSlides[index].dataset.full;
lbImg.alt = allSlides[index].dataset.alt;
lightbox.hidden = false;
document.body.style.overflow = 'hidden';
document.getElementById('feed-lb-close').focus();
}
function close() {
lightbox.hidden = true;
document.body.style.overflow = '';
allSlides[current].focus();
}
function prev() { open((current - 1 + allSlides.length) % allSlides.length); }
function next() { open((current + 1) % allSlides.length); }
allSlides.forEach(function (slide, i) {
slide.addEventListener('click', function () { open(i); });
});
document.getElementById('feed-lb-close').addEventListener('click', close);
document.getElementById('feed-lb-prev').addEventListener('click', prev);
document.getElementById('feed-lb-next').addEventListener('click', next);
lightbox.addEventListener('click', function (e) { if (e.target === lightbox) close(); });
document.addEventListener('keydown', function (e) {
if (lightbox.hidden) return;
if (e.key === 'Escape') close();
if (e.key === 'ArrowLeft') prev();
if (e.key === 'ArrowRight') next();
});
})(); })();
</script> </script>
{% endblock %} {% endblock %}