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
This commit is contained in:
2026-07-07 23:39:04 +02:00
co-authored by Claude Opus 4.8
parent 21f0c8fdd5
commit 536ca2085f
5 changed files with 13 additions and 62 deletions
@@ -20,8 +20,17 @@
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).
Retina (R6 / R14): two explicit cropResize derivatives (1x at w×h, 2x at
2w×2h) as an explicit `srcset` with w-descriptors; `alt` is the trip title.
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 -%}
@@ -38,8 +47,8 @@
{%- endif -%}
{%- if cover -%}
<div class="{{ wrapper_class }}">
<img src="{{ cover.cropResize(w, h).url }}"
srcset="{{ cover.cropResize(w, h).url }} {{ w }}w, {{ cover.cropResize(w * 2, h * 2).url }} {{ (w * 2) }}w"
<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">