- {# Serve the source image at full resolution — Bootstrap-3 only
- constrains via `max-width: 100%`, so on viewports wider than
- the natural image width the slide would otherwise sit short
- of the column edge. Live does the same (no resize). #}
- {{ image::render(src=slide.image, alt=slide.alt) }}
+ {# Bootstrap-3 only constrains hero images via `max-width: 100%`,
+ so the natural image width has to cover the widest viewport
+ we care about. We hand the browser a `srcset` ladder up to
+ 5345 px (the source's native width) and let it pick — phones
+ grab ~480-800 px, desktops 1600-2560 px, 5K screens the full
+ file. The first slide is the LCP element, so it loads eager. #}
+ {{ image::render(
+ src=slide.image,
+ alt=slide.alt,
+ srcset_widths="480,768,1200,1600,2560,3840,5345",
+ sizes="100vw",
+ lazy=not loop.first) }}
diff --git a/templates/macros/image.html b/templates/macros/image.html
index d2bb5b6..fda448c 100644
--- a/templates/macros/image.html
+++ b/templates/macros/image.html
@@ -1,22 +1,37 @@
{# Image-rendering macro, used both by templates (directly via import)
and indirectly by the `img` shortcode (which forwards to it).
- With `width > 0`: resizes via Zola's image pipeline (file size benefit)
- but emits no width/height attributes — the theme CSS sizes the image,
- matching the live Grav site's pattern. Avoiding the HTML width attribute
- sidesteps a stretch artefact triggered by Bootstrap-3's
- `img { height: auto !important }` interacting with `.float-right`
- padding.
+ Modes:
+ - `srcset_widths="480,768,…"` non-empty: emit a responsive `srcset`
+ over those widths. `sizes` should describe the layout (e.g.
+ "100vw" for the full-width hero). The `src` fallback uses the
+ largest width. CSV form because Tera macros only allow scalar
+ defaults, not arrays.
+ - `width > 0`: single resized variant via Zola's image pipeline.
+ - `width = 0` (default): raw
![]()
straight from /static/images/.
- With `width = 0` (default): emits the raw
![]()
straight from
- /static/images/ — same shape, no resize. #}
+ In all modes we omit the HTML width/height attributes — the theme CSS
+ sizes the image, matching the live Grav site's pattern. Avoiding the
+ HTML width attribute sidesteps a stretch artefact triggered by
+ Bootstrap-3's `img { height: auto !important }` interacting with
+ `.float-right` padding.
-{% macro render(src, width=0, alt="", class="") %}
+ `lazy=false` switches off `loading="lazy"`. Use it for above-the-fold
+ images (LCP candidates) like the first hero slide. #}
+
+{% macro render(src, width=0, alt="", class="", srcset_widths="", sizes="", lazy=true) %}
{%- set width = width | int -%}
- {%- if width > 0 -%}
+ {%- set loading_attr = "" -%}
+ {%- if lazy -%}{%- set loading_attr = ' loading="lazy"' -%}{%- endif -%}
+ {%- if srcset_widths -%}
+ {%- set widths = srcset_widths | split(pat=",") -%}
+ {%- set largest = widths | last | trim | int -%}
+ {%- set fallback = resize_image(path="static/images/" ~ src, width=largest, op="fit_width", quality=82) -%}
+

+ {%- elif width > 0 -%}
{%- set resized = resize_image(path="static/images/" ~ src, width=width, op="fit_width", quality=85) -%}
-

+

{%- else -%}
-

+

{%- endif -%}
{% endmacro render %}