Give trips an optional one-liner (header.tagline) and description (markdown content), surface them where they help, and fix the soft cover image — all editable from admin. - U1: header.cover_image blueprint field → pagemediaselect media picker. - U2: new macros/cover.html.twig — single source for cover resolution (author-selected → first journal image → none; missing file falls back) and retina rendering (1x/2x cropResize + srcset). Merged resolve+render into one macro since Twig macros can't return a Medium object. - U3: trip-list card renders the one-liner (when set) and the retina cover. - U4: trip-page in-column header gains the one-liner, an expandable description, and a thin banner strip — gated behind a trip_header_extras partial flag (default off) so the shared home active-trip view is unchanged (R12/KTD4). - U5: styles for the card/header one-liner, collapsible description (max-height preview, not line-clamp, so it holds across paragraphs) and the banner, with a mobile banner-height reduction. Covers R1–R15. Verified with new Playwright specs + full trip/home/maps regression against an isolated worktree dev server. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RDS6t8wcpbwKvvrxykVQ5K
46 lines
2.0 KiB
Twig
46 lines
2.0 KiB
Twig
{#
|
||
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).
|
||
|
||
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).
|
||
|
||
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.
|
||
#}
|
||
{% macro render(trip_page, alt, w, h, wrapper_class, sizes) %}
|
||
{%- set cover = null -%}
|
||
{%- if trip_page.header.cover_image and trip_page.media[trip_page.header.cover_image] is defined -%}
|
||
{%- set cover = trip_page.media[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.cropResize(w, h).url }}"
|
||
srcset="{{ cover.cropResize(w, h).url }} {{ w }}w, {{ cover.cropResize(w * 2, h * 2).url }} {{ (w * 2) }}w"
|
||
sizes="{{ sizes|default('100vw') }}"
|
||
alt="{{ alt }}"
|
||
loading="lazy">
|
||
</div>
|
||
{%- endif -%}
|
||
{% endmacro %}
|