Add Technical Kiwi website with Go, templ, and HTMX.
Single-page site with gallery by album and event, contact form over SMTP, Docker dev/prod setup, and on-server image derivatives. Gallery photos stay local (app/images/ is gitignored). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
112
app/templates/gallery.templ
Normal file
112
app/templates/gallery.templ
Normal file
@@ -0,0 +1,112 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"technical.kiwi/website/internal/gallery"
|
||||
)
|
||||
|
||||
templ GalleryGrid(images []gallery.Image) {
|
||||
if len(images) == 0 {
|
||||
<p class="gallery-empty">No images for this filter.</p>
|
||||
} else {
|
||||
for _, img := range images {
|
||||
<button
|
||||
type="button"
|
||||
class="gallery-item"
|
||||
hx-get={ fmt.Sprintf("/gallery/%s", gallery.URLPath(img.RelPath)) }
|
||||
hx-target="#modal-root"
|
||||
hx-swap="innerHTML"
|
||||
aria-label={ fmt.Sprintf("Open %s", gallery.AlbumLabel(img.Album)) }
|
||||
>
|
||||
if img.ThumbURL != "" {
|
||||
<img
|
||||
src={ img.ThumbURL }
|
||||
alt={ fmt.Sprintf("%s — %s", gallery.AlbumLabel(img.Album), formatDate(img.Date)) }
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
sizes="(max-width: 480px) 45vw, 160px"
|
||||
/>
|
||||
} else {
|
||||
<span class="gallery-placeholder" aria-hidden="true"></span>
|
||||
}
|
||||
if img.Collection != "" {
|
||||
<span class="gallery-album">{ img.Collection }</span>
|
||||
} else if img.Album != "" {
|
||||
<span class="gallery-album">{ gallery.AlbumLabel(img.Album) }</span>
|
||||
}
|
||||
if !img.Date.IsZero() {
|
||||
<span class="gallery-date">{ formatDate(img.Date) }</span>
|
||||
}
|
||||
</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
templ ImageModal(img gallery.Image, prevPath, nextPath string) {
|
||||
<div
|
||||
class="modal-backdrop"
|
||||
hx-on:click="if (event.target === this) this.remove()"
|
||||
>
|
||||
<div class="modal" role="dialog" aria-modal="true" aria-label="Image viewer">
|
||||
<button
|
||||
type="button"
|
||||
class="modal-close"
|
||||
onclick="document.getElementById('modal-root').innerHTML = ''"
|
||||
aria-label="Close"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<figure class="modal-figure">
|
||||
<img src={ img.URL } alt={ img.Filename }/>
|
||||
if !img.Date.IsZero() {
|
||||
<figcaption>{ modalCaption(img) }</figcaption>
|
||||
}
|
||||
</figure>
|
||||
<nav class="modal-nav" aria-label="Gallery navigation">
|
||||
if prevPath != "" {
|
||||
<button
|
||||
type="button"
|
||||
class="modal-nav-btn"
|
||||
hx-get={ fmt.Sprintf("/gallery/%s", gallery.URLPath(prevPath)) }
|
||||
hx-target="#modal-root"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
← Previous
|
||||
</button>
|
||||
} else {
|
||||
<span></span>
|
||||
}
|
||||
if nextPath != "" {
|
||||
<button
|
||||
type="button"
|
||||
class="modal-nav-btn"
|
||||
hx-get={ fmt.Sprintf("/gallery/%s", gallery.URLPath(nextPath)) }
|
||||
hx-target="#modal-root"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
Next →
|
||||
</button>
|
||||
}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
func formatDate(t time.Time) string {
|
||||
if t.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return t.Format("2 Jan 2006")
|
||||
}
|
||||
|
||||
func modalCaption(img gallery.Image) string {
|
||||
if img.Collection != "" {
|
||||
return img.Collection + " — " + formatDate(img.Date)
|
||||
}
|
||||
if img.Album != "" {
|
||||
return gallery.AlbumLabel(img.Album) + " — " + formatDate(img.Date)
|
||||
}
|
||||
return formatDate(img.Date)
|
||||
}
|
||||
Reference in New Issue
Block a user