diff --git a/_includes/layouts/base.njk b/_includes/layouts/base.njk index f1fd24a..5d47014 100644 --- a/_includes/layouts/base.njk +++ b/_includes/layouts/base.njk @@ -72,6 +72,7 @@ {# Alpine.js components — MUST load before Alpine core (Alpine.data() registration via alpine:init) #} + diff --git a/_includes/layouts/post.njk b/_includes/layouts/post.njk index 76ee848..6f8e38c 100644 --- a/_includes/layouts/post.njk +++ b/_includes/layouts/post.njk @@ -2,7 +2,7 @@ layout: layouts/base.njk withBlogSidebar: true --- -
+
{% if title %}

{{ title }}

{% endif %} @@ -245,6 +245,23 @@ withBlogSidebar: true "image": ["{% if postImage.startsWith('http') %}{{ postImage }}{% elif '/' in postImage and postImage[0] == '/' %}{{ site.url }}{{ postImage }}{% else %}{{ site.url }}/{{ postImage }}{% endif %}"]{% endif %} } + + {# Lightbox overlay for article images #} +
{# Comments section #} diff --git a/js/lightbox.js b/js/lightbox.js new file mode 100644 index 0000000..d8df430 --- /dev/null +++ b/js/lightbox.js @@ -0,0 +1,80 @@ +/** + * 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, + + init() { + const container = this.$root; + const imgs = container.querySelectorAll( + ".e-content img:not(.u-photo)" + ); + this.images = Array.from(imgs); + + this.images.forEach((img, i) => { + img.style.cursor = "zoom-in"; + img.addEventListener("click", (e) => { + e.preventDefault(); + this.show(i); + }); + }); + }, + + show(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"; + }, + + close() { + this.open = false; + this.src = ""; + document.body.style.overflow = ""; + }, + + 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(); + }, + })); +});