Stage raw files in upload/, publish with make sync-media/publish, and polish the lightbox: autoplay, remembered volume, Escape to close, and image/video icons without poster or caption clutter. Co-authored-by: Cursor <cursoragent@cursor.com>
134 lines
4.0 KiB
Plaintext
134 lines
4.0 KiB
Plaintext
package templates
|
|
|
|
templ Layout(title string, preloadImage string, content templ.Component) {
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8"/>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
|
|
<meta name="theme-color" content="#14100c"/>
|
|
<title>{ title }</title>
|
|
<meta name="description" content="Technical Kiwi Limited — electronic engineering and interactive art from New Zealand."/>
|
|
<link rel="stylesheet" href="/static/style.css"/>
|
|
if preloadImage != "" {
|
|
<link rel="preload" as="image" href={ preloadImage } fetchpriority="high"/>
|
|
}
|
|
</head>
|
|
<body hx-boost="true">
|
|
<header class="site-header">
|
|
<a href="/" class="logo">
|
|
<span class="logo-mark" aria-hidden="true">T</span>
|
|
<span class="logo-text">Technical Kiwi</span>
|
|
</a>
|
|
<nav class="site-nav">
|
|
<a href="#services">Services</a>
|
|
<a href="https://git.technical.kiwi/" rel="noopener noreferrer" target="_blank">Code</a>
|
|
<a href="#contact">Contact</a>
|
|
</nav>
|
|
</header>
|
|
<main>
|
|
@content
|
|
</main>
|
|
<footer class="site-footer">
|
|
<p>© { currentYear() } Technical Kiwi Limited. New Zealand.</p>
|
|
</footer>
|
|
<div id="modal-root"></div>
|
|
<script src="/static/htmx.min.js" defer></script>
|
|
<script>
|
|
(function () {
|
|
function hydrateLazyThumbs(root) {
|
|
var lazyImages = root.querySelectorAll("img[data-src]");
|
|
if (!lazyImages.length) return;
|
|
|
|
if (!("IntersectionObserver" in window)) {
|
|
lazyImages.forEach(function (img) {
|
|
img.src = img.dataset.src;
|
|
img.removeAttribute("data-src");
|
|
});
|
|
return;
|
|
}
|
|
|
|
var observer = new IntersectionObserver(
|
|
function (entries) {
|
|
entries.forEach(function (entry) {
|
|
if (!entry.isIntersecting) return;
|
|
var img = entry.target;
|
|
img.src = img.dataset.src;
|
|
img.removeAttribute("data-src");
|
|
observer.unobserve(img);
|
|
});
|
|
},
|
|
{ rootMargin: "300px 0px" },
|
|
);
|
|
|
|
lazyImages.forEach(function (img) {
|
|
observer.observe(img);
|
|
});
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
hydrateLazyThumbs(document);
|
|
var modalRoot = document.getElementById("modal-root");
|
|
if (!modalRoot) return;
|
|
modalRoot.addEventListener("volumechange", function (event) {
|
|
if (event.target.tagName !== "VIDEO") return;
|
|
saveVideoVolume(event.target.volume);
|
|
});
|
|
});
|
|
|
|
function closeModal() {
|
|
var modal = document.getElementById("modal-root");
|
|
if (modal) modal.innerHTML = "";
|
|
}
|
|
|
|
var galleryVideoVolumeKey = "gallery-video-volume";
|
|
|
|
function getSavedVideoVolume() {
|
|
try {
|
|
var stored = localStorage.getItem(galleryVideoVolumeKey);
|
|
if (stored === null) return null;
|
|
var v = parseFloat(stored);
|
|
if (!isFinite(v) || v < 0 || v > 1) return null;
|
|
return v;
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function saveVideoVolume(volume) {
|
|
try {
|
|
localStorage.setItem(galleryVideoVolumeKey, String(volume));
|
|
} catch (e) {}
|
|
}
|
|
|
|
function playModalVideo() {
|
|
var video = document.querySelector("#modal-root video");
|
|
if (!video) return;
|
|
var saved = getSavedVideoVolume();
|
|
if (saved !== null) video.volume = saved;
|
|
var p = video.play();
|
|
if (p && typeof p.catch === "function") {
|
|
p.catch(function () {});
|
|
}
|
|
}
|
|
|
|
document.addEventListener("keydown", function (event) {
|
|
if (event.key !== "Escape") return;
|
|
var modal = document.getElementById("modal-root");
|
|
if (!modal || !modal.firstElementChild) return;
|
|
event.preventDefault();
|
|
closeModal();
|
|
});
|
|
|
|
document.body.addEventListener("htmx:afterSwap", function (event) {
|
|
hydrateLazyThumbs(event.target);
|
|
if (event.detail && event.detail.target && event.detail.target.id === "modal-root") {
|
|
playModalVideo();
|
|
}
|
|
});
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|
|
}
|