mirror of
https://github.com/svemagie/blog-eleventy-indiekit.git
synced 2026-04-02 08:44:56 +02:00
- Add skip-to-main-content link and main content ID target - Add prefers-reduced-motion media queries for all animations - Enhance visible focus indicators (2px offset, high-contrast ring) - Replace ~160 text-surface-500 instances with text-surface-600/dark:text-surface-400 for 4.5:1+ contrast ratio compliance - Add aria-hidden="true" to ~30+ decorative SVG icons across sidebars/widgets - Convert facepile containers from div to semantic ul/li with role="list" - Add aria-label to icon-only buttons (share, sort controls) - Add sr-only labels to form inputs (webmention, search) - Add aria-live="polite" to dynamically loaded webmentions - Add aria-label with relative+absolute date to time-difference component - Add keyboard handlers (Enter/Space) to custom interactive elements - Add aria-label to nav landmarks (table of contents) - Fix modal focus trap and dialog accessibility - Fix lightbox keyboard navigation and screen reader announcements Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88
102 lines
2.9 KiB
JavaScript
102 lines
2.9 KiB
JavaScript
/**
|
|
* Alpine.js lightbox component for article images.
|
|
* Registers via alpine:init so it's available before Alpine starts.
|
|
* Click any image inside .e-content to view fullscreen.
|
|
* Navigate with arrow keys, close with Escape or click outside.
|
|
*/
|
|
document.addEventListener("alpine:init", () => {
|
|
Alpine.data("lightbox", () => ({
|
|
open: false,
|
|
src: "",
|
|
alt: "",
|
|
images: [],
|
|
currentIndex: 0,
|
|
triggerElement: null,
|
|
|
|
init() {
|
|
const container = this.$root;
|
|
const imgs = container.querySelectorAll(
|
|
".e-content img:not(.u-photo), .photo-gallery img.u-photo"
|
|
);
|
|
this.images = Array.from(imgs);
|
|
|
|
this.images.forEach((img, i) => {
|
|
img.style.cursor = "zoom-in";
|
|
img.setAttribute("tabindex", "0");
|
|
img.setAttribute("role", "button");
|
|
img.setAttribute("aria-label", (img.alt || "Image") + " — click to enlarge");
|
|
img.addEventListener("click", (e) => {
|
|
e.preventDefault();
|
|
this.show(i);
|
|
});
|
|
img.addEventListener("keydown", (e) => {
|
|
if (e.key === "Enter" || e.key === " ") {
|
|
e.preventDefault();
|
|
this.show(i);
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
show(index) {
|
|
this.triggerElement = this.images[index];
|
|
this.currentIndex = index;
|
|
const img = this.images[index];
|
|
// Use the largest source available
|
|
const picture = img.closest("picture");
|
|
if (picture) {
|
|
const source = picture.querySelector("source");
|
|
if (source) {
|
|
// Extract the URL from srcset (strip width descriptor)
|
|
const srcset = source.getAttribute("srcset") || "";
|
|
this.src = srcset.split(/\s+/)[0] || img.src;
|
|
} else {
|
|
this.src = img.src;
|
|
}
|
|
} else {
|
|
this.src = img.src;
|
|
}
|
|
this.alt = img.alt || "";
|
|
this.open = true;
|
|
document.body.style.overflow = "hidden";
|
|
// Move focus to close button for keyboard users
|
|
this.$nextTick(() => {
|
|
const closeBtn = document.querySelector('[x-ref="closeBtn"]');
|
|
if (closeBtn) closeBtn.focus();
|
|
});
|
|
},
|
|
|
|
close() {
|
|
this.open = false;
|
|
this.src = "";
|
|
document.body.style.overflow = "";
|
|
// Return focus to the image that triggered the lightbox
|
|
if (this.triggerElement) {
|
|
this.triggerElement.focus();
|
|
this.triggerElement = null;
|
|
}
|
|
},
|
|
|
|
next() {
|
|
if (this.images.length > 1) {
|
|
this.show((this.currentIndex + 1) % this.images.length);
|
|
}
|
|
},
|
|
|
|
prev() {
|
|
if (this.images.length > 1) {
|
|
this.show(
|
|
(this.currentIndex - 1 + this.images.length) % this.images.length
|
|
);
|
|
}
|
|
},
|
|
|
|
onKeydown(e) {
|
|
if (!this.open) return;
|
|
if (e.key === "Escape") this.close();
|
|
if (e.key === "ArrowRight") this.next();
|
|
if (e.key === "ArrowLeft") this.prev();
|
|
},
|
|
}));
|
|
});
|