Files
intotheeast-com-content/themes/intotheeast/templates/macros/cover.html.twig
T
m038andClaude Opus 4.8 536ca2085f fix(trip-cover): crop-to-fill cover strip + all-or-nothing retina
Banners/cards used cropResize (fit-inside), so a portrait fallback
source was handed back as a narrow sliver that object-fit:cover then
upscaled into a blur (reported on us-canada-mex-2024). Switch to
cropZoom (crop-to-fill) so the derivative is a real w×h cover strip.

Emit the 2x srcset descriptor only when the source is genuinely >=2w
wide (cover.width >= 2w), else 1x-only — no upscaling, no odd
intermediate widths. Imported pixelfed photos cap at ~1440px wide, so
auto-picked covers are usually 1x-only (see backlog: full-res reimport).

Also drop the no-photos-demo test fixture — it surfaced as stray demo
content in the trip list; AE4 (no cover + no images -> no banner) is a
trivial else-branch of the shared macro's cover guard, covered by
construction alongside the R7/AE3 fallback tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDS6t8wcpbwKvvrxykVQ5K
2026-07-07 23:39:04 +02:00

58 lines
2.9 KiB
Twig
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{#
Shared trip cover: one source of truth for cover resolution + retina
rendering, used by both the trip-list card (trips.html.twig) and the
trip-page banner strip (trip-feed-col.html.twig) so the two surfaces
cannot drift (KTD2).
Resolution (R7 / R11):
1. author-selected header.cover_image, when it resolves to page media;
2. else the first published journal entry's first image;
3. else nothing.
A set-but-missing cover_image (deleted/moved file) is `is not defined` in
page media, so it falls through to the auto-pick rather than rendering a
broken image (R11). Resolution matches against `media.images` (not all
media), so a non-image selection — e.g. a `.gpx` from the trip page's own
media, which the picker used to offer — also falls through instead of
routing a non-image Medium into cropResize.
Twig macros can only emit strings — they cannot return a Medium object —
so resolution and rendering live together in one macro: it emits the
wrapper + retina <img> when a cover exists, and nothing at all when none
does (which gives R9/AE4 a clean text-only header for free).
Rendering (R6 / R14): cropZoom crops-to-fill (cover), so the 1x derivative is
an actual w×h strip matching the banner/card aspect — NOT cropResize, which
fits-inside and would hand a portrait source back as a narrow sliver that the
wrapper's object-fit:cover then upscales into a blur. `alt` is the trip title.
Retina, all-or-nothing: a 2x derivative (2w×2h) is added to the `srcset` only
when the source is genuinely wide enough to supply it without upscaling
(`cover.width >= 2w`); otherwise the srcset is 1x only — no blurry, re-encoded
upscale, and no odd intermediate widths. Note most imported phone photos are
≤1440px wide, so an auto-picked fallback cover is usually 1x-only; the 2x
kicks in mainly for an explicitly-set wide landscape `cover_image`.
#}
{% macro render(trip_page, alt, w, h, wrapper_class, sizes) %}
{%- set cover = null -%}
{%- if trip_page.header.cover_image and trip_page.media.images[trip_page.header.cover_image] is defined -%}
{%- set cover = trip_page.media.images[trip_page.header.cover_image] -%}
{%- else -%}
{%- set dailies_page = grav.pages.find(trip_page.route ~ '/dailies') -%}
{%- if dailies_page -%}
{%- set first_entry = dailies_page.children.published()|first -%}
{%- if first_entry and first_entry.media.images|length > 0 -%}
{%- set cover = first_entry.media.images|first -%}
{%- endif -%}
{%- endif -%}
{%- endif -%}
{%- if cover -%}
<div class="{{ wrapper_class }}">
<img src="{{ cover.cropZoom(w, h).url }}"
srcset="{{ cover.cropZoom(w, h).url }} {{ w }}w{% if cover.width >= (w * 2) %}, {{ cover.cropZoom(w * 2, h * 2).url }} {{ (w * 2) }}w{% endif %}"
sizes="{{ sizes|default('100vw') }}"
alt="{{ alt }}"
loading="lazy">
</div>
{%- endif -%}
{% endmacro %}