feat(gallery): image detail view

This commit is contained in:
schreifuchs committed 2026-09-26 19:26:37 +02:00
1 parent bca93f6c6c
commit c62153ecf6
5 files changed
+96 -53

No files matched your search

+61 -40
View File
@@ -9,21 +9,37 @@ import (
const detailsID = "imgs-details"
// detailsContentID is the inner element of the modal that htmx swaps into.
// It is defined by components.Modal and must not be the dialog itself.
const detailsContentID = detailsID + "-content"
templ GallerySet(g gallery.Set) {
<div class="p-5">
@components.Modal(detailsID) {
@GalleryImage(g.SM.Images[0].UID, &gallery.Image{}, &gallery.Image{})
// Placeholder: only visible until the first image is swapped in.
// prev/next are nil because relative links would be resolved
// against the gallery index, not the image page.
if len(g.SM.Images) > 0 {
@GalleryImage(g.SM.Images[0].UID, nil, nil)
}
}
<div
class="grid gap-5 grid-cols-1 lg:hidden"
>
for _,img := range g.SM.Images {
<img
src={ "/images/" + img.UID + "?w=600" }
loading="lazy"
class="w-full"
alt="Gallery item"
/>
<button
hx-get={ templer.RelativePath(ctx, "./"+img.UID) }
hx-target={ "#" + detailsContentID }
hx-swap="innerHTML"
class="block w-full cursor-pointer"
>
<img
src={ "/images/" + img.UID + "?w=600" }
loading="lazy"
class="w-full"
alt="Gallery item"
/>
</button>
}
</div>
<div class="hidden lg:block 3xl:hidden">
@@ -39,37 +55,43 @@ templ GallerySet(g gallery.Set) {
}
templ GalleryImage(image string, next *gallery.Image, prev *gallery.Image) {
if next != nil {
}
<div>
<div class="relative inline-block w-full overflow-hidden">
<!-- 1. Preloaded Base Image (w=800) -->
<img
src={ "/images/" + image + "?w=800" }
alt=""
class="block w-full h-auto"
/>
<!-- 2. Responsive Image Layer (Overlaid on top) -->
<img
src={ "/images/" + image + "?w=800" }
srcset={ "/images/" + image + "?w=400 400w, /images/" + image + "?w=800 800w, /images/" + image + "?w=1200 1200w, /images/" + image + "?w=1600 1600w" }
sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px"
alt="Image description"
onload="this.classList.remove('opacity-0')"
class="absolute inset-0 w-full h-full object-cover opacity-0 transition-opacity duration-300"
/>
<!-- 3. Navigation Controls -->
if prev != nil {
<button class="absolute left-4 top-1/2 -translate-y-1/2 z-10 p-2 text-white">
&lt;
</button>
}
if next != nil {
<button class="absolute right-4 top-1/2 -translate-y-1/2 z-10 text-white">
&gt;
</button>
}
</div>
<div class="relative w-10/12 h-10/12 flex items-center justify-center overflow-hidden ">
<!-- 1. Preloaded Base Image (w=800) -->
<img
src={ "/images/" + image + "?w=800" }
alt=""
class="absolute inset-0 w-full h-full object-contain"
/>
<!-- 2. Responsive Image Layer (Overlaid on top) -->
<img
src={ "/images/" + image + "?w=800" }
srcset={ "/images/" + image + "?w=400 400w, /images/" + image + "?w=800 800w, /images/" + image + "?w=1200 1200w, /images/" + image + "?w=1600 1600w" }
sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px"
alt="Image description"
onload="this.classList.remove('opacity-0')"
class="absolute inset-0 w-full h-full object-contain transition-opacity duration-300"
/>
<!-- 3. Navigation Controls -->
if prev != nil {
<button
hx-get={ templer.RelativePath(ctx, "../"+prev.UID) }
hx-target={ "#" + detailsContentID }
hx-swap="innerHTML"
class="absolute left-4 top-1/2 -translate-y-1/2 z-10 p-2 text-white"
>
&lt;
</button>
}
if next != nil {
<button
hx-get={ templer.RelativePath(ctx, "../"+next.UID) }
hx-target={ "#" + detailsContentID }
hx-swap="innerHTML"
class="absolute right-4 top-1/2 -translate-y-1/2 z-10 p-2 text-white"
>
&gt;
</button>
}
</div>
}
@@ -84,9 +106,8 @@ templ Gallery(g gallery.Gallery) {
class="overflow-hidden shadow-sm"
>
<button
popovertarget={ detailsID }
hx-get={ templer.RelativePath(ctx, "./"+img.UID) }
hx-target={ "#" + detailsID }
hx-target={ "#" + detailsContentID }
hx-swap="innerHTML"
>
<img