Previous approach (CSS transition + reflow trick) is unreliable in
Firefox. New approach: PhotoSwipe emits 'change' synchronously before
painting; we add a direction-aware CSS keyframe animation to the
incoming slide element, with animation-fill-mode:both so there is no
flash before the animation starts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
PhotoSwipe's goTo() moves slides instantly (no spring animation unlike
swipe). Intercepts keydown in capture phase, sets a CSS transition and
forces a reflow before PhotoSwipe moves the container, so the browser
animates from the old position to the new one. Cleans up on close.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
Creates partials/entry-journal.html.twig and partials/entry-story.html.twig
so trip, dailies, and home all use the same up-to-date markup. Home page
gains PhotoSwipe, blurred fill, adaptive aspect ratio, and hash-based
marker scroll. Future changes only need to happen in one place.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
Removed redundant margin-bottom on .journal-post (feed gap already
separates items). Reduced padding-bottom and gap from 3rem to 2rem,
cutting the between-entry whitespace roughly in half.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
Portrait entries (first image taller than wide) get a 4:5 container —
Instagram's proven cap that prevents single photos dominating the screen.
Landscape entries keep 4:3. Aspect-ratio moved from slide to wrap so the
strip inherits it via height:100%.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
- object-fit: contain + ::before blurred background fill on all slides
- dots moved inside photo-wrap, overlaid at bottom with shadow for contrast
- arrows hidden on touch devices via @media (hover: none)
- margin-bottom increased for breathing room below photo block
- trip.html.twig brought up to parity with dailies: same structure,
same PhotoSwipe init, same expand button, same dot overlay
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
Move PhotoSwipe CSS from per-entry assets.addCss() (runs after head is
committed) to a single <link> tag at block start. Restore the expand
button as the reliable mobile tap target — it dispatches a synthetic
click on the visible <a> slide, which bubbles to PhotoSwipe's gallery
handler. Merge dot sync into the single module script.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
Drops ~80 lines of fragile custom lightbox JS/HTML/CSS in favour of
PhotoSwipe v5 loaded from CDN. Slides are now <a> tags (the pswp-gallery
children) which always fire click events on iOS even inside scroll
containers — the root cause of the mobile tap issue. Pinch-to-zoom and
swipe-to-dismiss come for free. Dot sync kept as a separate vanilla JS
block.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
- Aspect ratio 3:2 → 4:3 (less aggressive crop; closer to phone native)
- Slides become <button> elements with data-full pointing to original image
- Tap/click any photo in the feed opens a full-screen lightbox showing the
uncropped original; prev/next browses all feed photos; Esc/arrows/backdrop
click close
- Dot indicator now syncs with scroll via IntersectionObserver
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
Replaces the boolean toggle with a select field offering:
on — connect all consecutive entries (chronological line)
manual — force_connect entries only (user-controlled connections)
intelligent_gpx — suppress connectors where GPX covers both endpoints;
force_connect overrides (original smart logic, restored)
off — no connectors at all; force_connect also ignored
buildJourneySegments gains an optional trackpointsPerFile param used
only by intelligent_gpx mode. renderGpxJourney extracts trackpoints
only when connectMode is intelligent_gpx. dailies.html.twig falls
back from intelligent_gpx → on (mini-map has no GPX tracks to
suppress against).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
autoconnect:true now connects every consecutive entry pair in
chronological order; the old proximity check (suppress where GPX
covers the route) is removed entirely.
- buildJourneySegments: drops allTrackpoints/thresholdKm params;
logic is now force_connect || autoconnect (binary, no GPX math)
- renderGpxJourney: no longer extracts trackpoints; just renders
visual GPX layers then calls buildJourneySegments
- dailies.html.twig: removes GPX URL collection, toGeoJSON CDN load,
and the Promise.all — connectors are now synchronous
- extractTrackpoints/isNearTrack/haversineKm removed (dead code)
- blueprint help text updated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
Adds two configurable toggles to the trip blueprint (Admin2 Trip tab):
- use_gpx: show/hide GPX tracks on all maps (default: enabled)
- autoconnect: draw connector lines between markers (default: enabled)
When use_gpx is off, GPX files are not fetched or rendered on any map
(home, map, trip, dailies). The stats panel in trip.html.twig still
reads GPX_URLS directly and is unaffected.
When autoconnect is off, buildJourneySegments suppresses all
auto-connectors; only entries with force_connect:true still draw a
line — making force_connect behaviour independent of both settings.
Also refactors the inline Promise.all in trip.html.twig to use the
shared renderGpxJourney utility (reducing duplication).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
The home map was drawing an initial addJourneyLine, then trying to remove
layer 'home-journey' in the Promise.all callback — but addJourneyLine names
the layer 'home-journey-line', so removeLayer was a no-op and removeSource
failed (layer still referencing the source), leaving a ghost line on top of
the GPX tracks.
Extract the Promise.all → GPX tracks → buildJourneySegments → addJourneySegments
pattern into MapUtils.renderGpxJourney() and replace both map.html.twig and
home.html.twig with the shared call. No upfront journey line is drawn — the
function handles the no-GPX case correctly via Promise.all([]).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
user/config/media.yaml defines only 'gpx', which replaces the system
media.types instead of merging (blueprint-unaware key replacement). This
causes page.media[hero_image] to return undefined for jpg/png files.
Fallback constructs the hero URL directly from page.url + filename,
matching what shortcode plugins already do. The page.media path is still
tried first so it works correctly if the config is ever fixed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr