diff --git a/assets/css/components/image-zoom.css b/assets/css/components/image-zoom.css
new file mode 100644
index 0000000..1d9addc
--- /dev/null
+++ b/assets/css/components/image-zoom.css
@@ -0,0 +1,42 @@
+/* Minimal styles for Hextra image zoom overlay */
+.hextra-zoom-image-overlay {
+ position: fixed;
+ inset: 0;
+ background: var(--hextra-image-zoom-backdrop, rgba(0, 0, 0, 0.9));
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 9999;
+ opacity: 0;
+ transition: opacity 0.25s ease-out;
+ cursor: zoom-out;
+}
+
+.hextra-zoom-image-overlay.show {
+ opacity: 1;
+}
+
+.hextra-zoom-image {
+ max-width: min(95vw, 1200px);
+ max-height: 95vh;
+ border-radius: 8px;
+ box-shadow: 0 8px 40px rgba(0, 0, 0, 0.35);
+ transition: transform 0.3s ease-out;
+ transform: scale(0.98);
+}
+
+.hextra-zoom-image-overlay.show .hextra-zoom-image {
+ transform: scale(1);
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .hextra-zoom-image-overlay,
+ .hextra-zoom-image {
+ transition: none !important;
+ }
+}
+
+/* Show magnifier cursor over zoomable images in content */
+.content img:not([data-no-zoom]) {
+ cursor: zoom-in;
+}
diff --git a/assets/js/image-zoom.js b/assets/js/image-zoom.js
new file mode 100644
index 0000000..5e1eb81
--- /dev/null
+++ b/assets/js/image-zoom.js
@@ -0,0 +1,76 @@
+// Minimal, dependency-free image zoom for Hextra
+// - Activates on images inside `.content`
+// - Close on overlay click or Escape
+// - Opt-out with `data-no-zoom` on
+
+(function () {
+ function ready(fn) {
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", fn, { once: true });
+ } else {
+ fn();
+ }
+ }
+
+ function createOverlay(src, alt) {
+ const overlay = document.createElement("div");
+ overlay.className = "hextra-zoom-image-overlay";
+ overlay.setAttribute("role", "dialog");
+ overlay.setAttribute("aria-modal", "true");
+
+ const img = document.createElement("img");
+ img.className = "hextra-zoom-image";
+ img.src = src;
+ if (alt) img.alt = alt;
+
+ overlay.appendChild(img);
+
+ function close() {
+ overlay.classList.remove("show");
+ document.documentElement.style.removeProperty("overflow");
+ window.removeEventListener("keydown", onKeyDown, true);
+ overlay.addEventListener(
+ "transitionend",
+ () => overlay.remove(),
+ { once: true }
+ );
+ }
+
+ function onKeyDown(e) {
+ if (e.key === "Escape") close();
+ }
+
+ overlay.addEventListener("click", close, { once: true });
+ window.addEventListener("keydown", onKeyDown, true);
+
+ document.body.appendChild(overlay);
+ // lock scroll
+ document.documentElement.style.overflow = "hidden";
+
+ // trigger fade-in
+ requestAnimationFrame(() => overlay.classList.add("show"));
+ }
+
+ ready(function () {
+ const container = document.querySelector(".content");
+ if (!container) return;
+
+ container.addEventListener(
+ "click",
+ function (e) {
+ const target = e.target;
+ if (!(target instanceof HTMLImageElement)) return;
+ if (target.dataset.noZoom === "" || target.dataset.noZoom === "true") return;
+
+ // avoid following parent links when zooming
+ e.preventDefault();
+ e.stopPropagation();
+
+ const src = target.currentSrc || target.src;
+ if (!src) return;
+ createOverlay(src, target.alt || "");
+ },
+ true
+ );
+ });
+})();
diff --git a/docs/hugo.yaml b/docs/hugo.yaml
index 578d10e..03356e6 100644
--- a/docs/hugo.yaml
+++ b/docs/hugo.yaml
@@ -238,3 +238,6 @@ params:
# inputPosition: top
# lang: en
# theme: noborder_dark
+
+ imageZoom:
+ enable: true
diff --git a/layouts/_partials/scripts.html b/layouts/_partials/scripts.html
index bf49fc9..702bacb 100644
--- a/layouts/_partials/scripts.html
+++ b/layouts/_partials/scripts.html
@@ -13,3 +13,10 @@
{{- if (.Store.Get "hasAsciinema") -}}
{{- partial "scripts/asciinema.html" . -}}
{{- end -}}
+
+{{/* Image zoom */}}
+{{- with site.Params.imageZoom }}
+ {{- if .enable }}
+ {{- partial "scripts/image-zoom.html" $ -}}
+ {{- end -}}
+{{- end -}}
diff --git a/layouts/_partials/scripts/image-zoom.html b/layouts/_partials/scripts/image-zoom.html
new file mode 100644
index 0000000..e074556
--- /dev/null
+++ b/layouts/_partials/scripts/image-zoom.html
@@ -0,0 +1,13 @@
+{{/* Optional minimal image zoom assets */}}
+{{- $js := resources.Get "js/image-zoom.js" -}}
+{{- $css := resources.Get "css/components/image-zoom.css" -}}
+
+{{- if hugo.IsProduction -}}
+ {{- $js = $js | minify | fingerprint -}}
+ {{- $css = $css | minify | fingerprint -}}
+{{- end -}}
+
+
+
+
+