Compare commits

..

255 Commits

Author SHA1 Message Date
m038 21c1d22859 fix: PhotoSwipe bg doesn't cover bottom — add !important and pin bg to viewport 2026-06-22 09:11:52 +02:00
m038 68b328dabc feat: enrich Slovenia 2024 Piran entry with coords and weather 2026-06-22 09:07:17 +02:00
m038 817bd17959 feat: split Slovenia 2024 into its own trip
Move Piran entry out of us-canada-mex-2024 into a new slovenia-2024 trip.
Rename entry folder to match the post title convention.
Fix us-canada-mex-2024 date_start to 2024-07-21 (first actual US entry).
2026-06-22 09:06:00 +02:00
m038 77dd99ee2b feat(stories): add mini-map via shared partial, add story card IDs 2026-06-22 01:39:29 +02:00
m038 857f33be54 refactor(dailies): use shared feed-map partial 2026-06-22 01:37:33 +02:00
m038 320a98893a feat: add shared feed-map partial (dailies + stories) 2026-06-22 01:33:26 +02:00
m038 e07fb3a72a feat(map): exit fullscreen on marker click, then scroll to entry
When fullscreen is active, clicking a marker now triggers fsBtn.click()
to exit cleanly (handles class, body overflow, tripMap.resize + icon),
then waits 450ms for the exit animation before scrolling to the entry
and firing the highlight. Also fixes missing icon-swap CSS for
.home-map-col.is-fullscreen (was only targeting .feed-map-wrap).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-22 01:12:52 +02:00
m038 1bb588d1d2 fix(trip): switch panel animation to max-height (grid-template-rows broken)
grid-template-rows: 0fr fails to fully collapse when the direct grid
child has overflow:hidden (creates a BFC that prevents 0-height).
max-height: 0 → 600px with overflow:hidden is simpler and reliable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-22 01:09:38 +02:00
m038 aa1cb7411c fix(map): collapse attribution on load; darken fullscreen button
Attribution: MapLibre v4 uses <details> and may open it after load
regardless of compact:true — remove the open attribute in the load
handler to guarantee collapsed state.

Button: switch from teal to --color-canvas (#22201B) so it sits quietly
against the dark map; icon reads in --color-ink (warm cream).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-22 01:05:12 +02:00
m038 5fe8c015f1 fix(map): theme fullscreen button with accent colour
Replace plain white with --color-accent/--color-accent-on so the button
reads as a site control rather than a stray MapLibre element.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-22 01:00:50 +02:00
m038 6f9538053c feat(map): mobile fullscreen button on trip page map
Button in bottom-right of #trip-map (z-index:1000), hidden ≥769px.
Attribution moved to bottom-left to free the corner. Clicking toggles
.is-fullscreen on .home-map-col (position:fixed, 100dvh), locks body
scroll, and calls tripMap.resize() for MapLibre to re-render.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-22 00:56:57 +02:00
m038 5e503cf3a5 fix(map): fullscreen btn inside map div, attribution moved to bottom-left
Button is back inside #feed-map with z-index:1000 to clear all MapLibre
layers. Attribution control disabled in constructor and re-added to
bottom-left so bottom-right is free for the fullscreen button.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-22 00:20:23 +02:00
m038 ce860cfef9 fix(map): move fullscreen button outside feed-map div, top-right corner
MapLibre's attribution button occupies bottom-right of the container.
Moving our button out of the map div avoids MapLibre's DOM entirely,
and top-right is clear of all default MapLibre controls.
Position anchor moves to feed-map-wrap (position:relative).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 23:54:13 +02:00
m038 989755d33c feat(map): mobile fullscreen button for feed mini-map
Button in the bottom-right corner of the map, hidden ≥769px. Clicking
it toggles .is-fullscreen on .feed-map-wrap (position:fixed, full
viewport), locks body scroll, and calls feedMap.resize() so MapLibre
re-renders at the new size. Icon swaps between expand SVG and ✕.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 23:44:09 +02:00
m038 9ddf52c635 fix(trip): raise stat-value clamp floor to 2rem (32px)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 23:35:32 +02:00
m038 9b62f79301 fix(trip): raise stat-value clamp floor to --text-xl (1.75rem)
22px floor was too close to the preferred at 375px (6vw=22.5px), so
values were pinned near the minimum. 28px floor makes values pop more
on small screens while long values like ~12,366 still wrap gracefully.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 23:33:06 +02:00
m038 64aa9ec023 revert(trip): restore stat-label to --text-xs
Small label is intentional — the contrast with the larger value is the
visual hierarchy. Revert the sm bump from the previous commit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 23:30:24 +02:00
m038 9bfd96af2c fix(trip): bump stat-label to --text-sm, widen stat-value fluid range
Label: xs (12px) → sm (14px) for clearer hierarchy below the value.
Value preferred: 5.5vw → 6vw so short values stay bold on mid phones.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 23:28:42 +02:00
m038 000af6934f fix(trip): raise stat-value clamp floor to --text-lg for visual hierarchy
14px floor was too close to the 12px label size. 1.375rem keeps the
value visually dominant over the label even at minimum size.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 23:23:39 +02:00
m038 c94e36a861 fix(trip): fluid stat-value font size with clamp()
Replaces fixed 3rem with clamp(--text-sm, 5.5vw, --text-3xl) so long
values like "4:32:15" scale down on mobile instead of overflowing.
Desktop (≥870px viewport) is unchanged at 3rem.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 23:21:58 +02:00
m038 2c831628b2 fix(trip): fix cycling stats mobile grid — span lone last card full width
7 cycling stat blocks in a 2-col mobile grid leaves a lone card in the
last row's left column with empty space on the right. Using
:last-child:nth-child(odd) + grid-column: 1/-1 spans that card across
both columns. Also minmax(0,1fr) on both grids for strictly equal widths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 23:18:09 +02:00
m038 02fc666661 feat(trip): pill radius on panel toggles, slide animation, mobile close button
- Radius: trip-panel-toggle now uses --radius-full, consistent with filter pills
- Animation: stats/cycling blocks use CSS grid-template-rows 0fr→1fr transition
  (inner trip-panel-inner div carries decoration so border/padding don't peek
  out when collapsed; display:none removed)
- Close button: ↑ Close stats / ↑ Close cycling at bottom of each panel,
  hidden ≥769px, triggers the header toggle via data-toggle attribute

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 23:09:52 +02:00
m038 c3cb224402 style(trip): give Stats/Cycling panel toggles a square bordered style
Border + 4px radius instead of borderless text, matching the visual
weight of the filter pills without the full pill roundness.
Active state gets teal border + accent-light background like other active controls.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 23:03:56 +02:00
m038 2b8ea1963b refactor(trip): declutter filter bar — move Stats/Cycling to panel toggles
Filter bar now has one job: content type (All/Journal/Stories) + sort icon (↑/↓).
Stats and Cycling move to a lean text-button row below the bar with a
rotating ▾ caret — CSS handles expand/collapse state via .is-active,
no JS changes needed for the caret animation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 22:58:28 +02:00
m038 f94880e758 feat: add sort toggle to dailies and stories pages
dailies: reverse Twig output to ascending (matching trip default),
add feed-sort-bar above feed, add sort JS using [data-type] + appendChild.

stories: wrap heading in flex header row with sort button inline,
add sort JS targeting .story-card children of .stories-grid.

CSS: feed-sort-bar (right-aligned button above feed),
stories-listing__header (flex row, baseline-aligned), heading margin moved to header.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 22:27:11 +02:00
m038 b6142cee44 feat(trip): add ascending/descending sort toggle button
Button sits in the right filter group alongside Stats/Cycling.
Default state: ascending (↑ Oldest first, no highlight).
Toggled state: descending (↓ Newest first, is-active pill style).
DOM reversal uses insertBefore against the anchored #feed-filter-empty
so the empty-state message stays last regardless of sort direction.
Interacts safely with the type filter (show/hide by data-type).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 22:21:55 +02:00
m038 53bfe5955d fix(photoswipe): target currSlide.container not currSlide.el
pswp.currSlide is a Slide instance whose DOM element is stored as
.container (.pswp__zoom-wrap). The .el property belongs to the
itemHolder wrapper, not the Slide — so currSlide.el was always
undefined, the null-guard exited early, and no animation played.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 21:45:22 +02:00
m038 9f503c011d fix(photoswipe): keyboard arrow animation via CSS keyframes
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
2026-06-21 21:36:48 +02:00
m038 415d95ed47 feat(photoswipe): animate keyboard arrow navigation in lightbox
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
2026-06-21 21:29:06 +02:00
m038 e787544a2b feat(strip): smooth scroll animation on arrow button clicks
scroll-behavior: smooth on the strip element ensures programmatic
scrollBy calls animate consistently, cooperating with scroll-snap.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 21:22:42 +02:00
m038 9f94164c61 fix(arrows): insert strip-controls after wrap, not inside it
Dots moved inside journal-photo-wrap (overflow:hidden) earlier, so
controls were being clipped. Now inserts after the wrap element.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 21:13:43 +02:00
m038 608ccfdecd fix(partial): restore data-slides on photo strip
Missing data-slides caused base.html.twig arrow script to read
slideCount as 1 and bail before creating prev/next controls.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 21:10:48 +02:00
m038 933652fd57 refactor(templates): extract entry markup into shared partials
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
2026-06-21 21:07:12 +02:00
m038 fdaed1033a fix(ios): use 100dvh for PhotoSwipe to fix dynamic viewport
iOS Safari freezes 100vh at the initial viewport height (address bar
visible). 100dvh tracks the live viewport as browser chrome shows/hides.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 20:48:13 +02:00
m038 bc77baca2e fix(scroll): clear URL hash when back-to-top is clicked
Uses history.pushState to strip the stale #entry-slug without
triggering a page jump, then smooth-scrolls to the top.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 20:41:41 +02:00
m038 7c9a55224a fix(scroll): use hash navigation for marker clicks
Browser handles scroll natively via window.location.hash, respecting
scroll-margin-top. Updates URL for shareability and screen reader
compatibility. Added html { scroll-behavior: smooth } for smooth
hash navigation globally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 20:40:15 +02:00
m038 85ba3747b1 fix(scroll): use block:start so scroll-margin-top is respected
block:center ignores scroll-margin-top. block:start positions the
entry's top edge at the margin offset, clearing the sticky header.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 20:36:46 +02:00
m038 71f8629d18 fix(scroll): add scroll-margin-top to all feed entry types
Offsets scroll targets by header height + 1rem so marker clicks land
below the sticky nav, not behind it. Applies to both .journal-post
and .entry-card (story cards).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 20:33:57 +02:00
m038 b1492918d5 feat(trip): add back-to-top button
Reuses .story-totop styles. Appears after scrolling past 80% of
viewport height, smooth-scrolls to top on click.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 20:29:07 +02:00
m038 95ea38d250 fix(feed): reduce excess spacing between entries
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
2026-06-21 20:25:02 +02:00
m038 81be69f08d fix(photos): make PhotoSwipe background fully opaque
Prevents page dots from leaking through the semi-transparent overlay.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 20:21:33 +02:00
m038 71eaa3e788 feat(photos): adaptive aspect ratio per entry (portrait 4:5, landscape 4:3)
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
2026-06-21 20:18:05 +02:00
m038 5c75f1416f feat(photos): blurred ambient fill, dots overlay, PhotoSwipe on trip + dailies
- 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
2026-06-21 20:09:24 +02:00
m038 3379e50503 fix(dailies): fix PhotoSwipe CSS loading + restore expand button
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
2026-06-21 19:39:33 +02:00
m038 30c8937566 feat(dailies): replace custom lightbox with PhotoSwipe v5
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
2026-06-21 19:31:27 +02:00
m038 770a96b099 feat(dailies): 4:3 photo strip, lightbox, and dot sync
- 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
2026-06-21 19:06:54 +02:00
m038 604ba00c70 content: set transport_mode on all central-asia-2023 entries; add plane option to blueprint
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 18:28:44 +02:00
m038 b6c9d0b2ac fix: centripetal Catmull-Rom spline to prevent overshooting near close markers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 18:20:26 +02:00
m038 51ab99b839 content: move Farewell Vodka entry between Khorog and Bukhara
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 18:10:08 +02:00
m038 2f733e5ffc chore: ignore ui-test post entries from gitignore
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 18:02:54 +02:00
m038 d7e3162f55 (Grav GitSync) Automatic Commit from m038 2026-06-21 15:51:01 +00:00
m038 8e127e7e3a (Grav GitSync) Automatic Commit from m038 2026-06-21 15:45:54 +00:00
m038 e853cb543a (Grav GitSync) Automatic Commit from m038 2026-06-21 15:40:39 +00:00
m038 e29953ab90 (Grav GitSync) Automatic Commit from m038 2026-06-21 15:38:11 +00:00
m038 366974475f (Grav GitSync) Automatic Commit from m038 2026-06-21 15:37:57 +00:00
m038 fa29888578 chore: ignore git-sync.yaml and security.yaml
Both files are server-specific: git-sync.yaml contains an encrypted
token, security.yaml holds Grav nonces/salts. Neither should roam
across environments.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 17:25:22 +02:00
m038 31f3c6fb2f fix: resolve AX6/AX7 a11y violations
- gpx-manager: raise th color #666→#999 (6.9:1 contrast on dark bg)
- gpx-manager: raise .gpx-delete text #c0392b→#e07070 (6.2:1 contrast)
- gpx-manager: add visible label text 'Choose GPX file' to file input
- snap-gallery: add tabindex=0 to .pgallery__frame for keyboard scrollability

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPJztrVGbwic2xTG7G9fjM
2026-06-21 17:24:29 +02:00
m038 936662e35c chore: update admin password
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 16:17:08 +02:00
m038 a440583691 content: add home page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 15:10:51 +02:00
m038 6486d377b2 chore: commit config drift from CMS usage
accounts/mischa.yaml: pagesViewMode set to tree via Admin UI
config/site.yaml: active_trip updated from demo to us-canada-mex-2024

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 15:10:49 +02:00
m038 6c842ebe7f content: apply enrichment data to central-asia-2023 entries
Fills in lat, lng, weather_temp_c, and weather_desc for all 23 entries.
Completes the enrichment committed in 3f53bf5 which only renamed folders.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 15:10:45 +02:00
m038 89c9771a84 chore: untrack third-party plugins from user repo
All plugins except cache-on-save and story-blocks are managed by GPM
and server-install.sh — they should not be in version control.
Fixes .gitignore to plugins/* with explicit allows for custom plugins only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 15:10:33 +02:00
m038 89ae41d9ec Ignore italy-2026-demo in user repo
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 15:01:52 +02:00
m038 3983615c99 Remove demo content from user repo
Demo trip italy-2026-demo was accidentally committed; it should only
exist in docs/demo/ and be loaded locally via make demo-load.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 15:01:39 +02:00
m038 8412d1540c content: enrich us-canada-mex-2024 entries with location, weather, and rename folders to title slugs
Applied location_city, location_country, lat, lng, weather_temp_c, weather_desc to all
11 entries (pixelfed-2 through pixelfed-12). Renamed folders from pixelfed-N format to
date-title-slug format. pixelfed-1 (Piran/Slovenia) left untouched.
2026-06-21 14:24:01 +02:00
m038 3f53bf5b85 content: enrich central-asia-2023 entries with location, weather, and rename folders to title slugs
Applied location (city, country, lat, lng) and weather (temp_c, desc) enrichment to all 22
journal entries. Renamed entry folders from pixelfed-N numbering to date-title-slug format.
Bukhara entry (pixelfed-15) had its date corrected from 2023-09-23 to 2023-10-02.
2026-06-21 14:18:59 +02:00
m038 3c77d6cdad docs: remove stale docs/ content migrated to main repo docs/ on 2026-06-19 2026-06-21 14:16:33 +02:00
m038 e44105b330 content: enrich italy-2025 entries with location and weather 2026-06-21 13:17:47 +02:00
m038 f6a8657de2 chore: move Off to first position in connect markers select
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 12:12:45 +02:00
m038 89e2708b1e docs: switch blueprint help text line breaks from \n to <br>
Admin2 renders help as HTML so \n collapses; <br> is required.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 12:07:13 +02:00
m038 f78ab147af docs: format blueprint help text with newlines
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 12:04:50 +02:00
m038 886ed21e5d docs: add descriptive help text to use_gpx and autoconnect blueprint fields
Explains each connect markers mode and the dependency between
Intelligent GPX and Show GPX being enabled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 11:53:02 +02:00
m038 eafc431e0e feat: expand connect markers to 4-mode select
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
2026-06-21 11:42:03 +02:00
m038 9809950347 refactor: simplify connector logic — remove GPX proximity suppression
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
2026-06-21 11:38:50 +02:00
m038 21b572677e feat: add per-trip use_gpx and autoconnect toggles
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
2026-06-21 11:20:25 +02:00
m038 7c2303c4e8 fix: sort trip page entries ascending (oldest first) 2026-06-21 11:03:49 +02:00
m038 a4b3e526fb content: update home page intro copy 2026-06-21 10:53:04 +02:00
m038 3018ae16ff feat: use page content field for home page description instead of subtitle header 2026-06-21 10:49:54 +02:00
m038 ff9ea3a0a7 fix: correct home blueprint field names to header.title/header.subtitle 2026-06-21 10:45:55 +02:00
m038 913e4bf19a feat: pull home page title/subtitle from page content instead of hardcoding 2026-06-21 10:43:50 +02:00
m038 6eaa00d612 fix: share GPX+journey rendering via MapUtils.renderGpxJourney
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
2026-06-21 10:37:56 +02:00
m038 04e4fa3dcd feat: add between-trips highlights mode with grid and map markers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-21 01:48:39 +02:00
m038 8edbfd2dd3 feat: add travelling branch and GPX to home map (active trip mode) 2026-06-21 01:43:24 +02:00
m038 b1cffca953 chore: mark demo entries as featured for homepage highlight testing 2026-06-21 01:36:05 +02:00
m038 cf364bc298 feat: add blueprints for active_trip/travelling config, tagline, featured fields 2026-06-21 01:36:00 +02:00
m038 5a6c00eaa4 demo: add Montalcino story showcasing full-bleed and image-caption shortcodes
Demonstrates all three image-caption width variants (column, full, bleed via
full-bleed shortcode) in a real story context with narrative prose.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPJztrVGbwic2xTG7G9fjM
2026-06-20 23:51:51 +02:00
m038 512f1ce9b2 feat(story-blocks): add full-bleed and image-caption shortcodes
Two new shortcodes for immersive storytelling:
- [full-bleed image="" caption="" credit=""] — viewport-wide image, max 80vh
- [image-caption image="" caption="" credit="" width="column|full|bleed"] — photo at configurable width with caption

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPJztrVGbwic2xTG7G9fjM
2026-06-20 23:25:16 +02:00
m038 ee107eebdf fix: add collection config to demo dailies.md; photo strip keyboard access
- demo dailies.md: add content.items collection config (required for page.collection())
- base.html.twig: add tabindex="0" to journal-photo-strip for WCAG scrollable-region-focusable

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-20 22:12:46 +02:00
m038 63a826fc8e chore: switch active trip to italy-2026-demo, remove japan-korea-2026
- active_trip: italy-2026-demo in site.yaml (was japan-korea-2026)
- post-form.md parent updated to /trips/italy-2026-demo/dailies
- Remove japan-korea-2026 trip pages (no real trip exists)
- Remove stale old italy-2026-demo entries/stories/GPX from git tracking
  (these were leftover from before the demo-source approach)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-20 22:00:37 +02:00
m038 a9ce9a257c feat(demo): add journal entries days 5–8 with photos
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-20 21:30:17 +02:00
m038 5ee0b8510f feat(demo): add journal entries days 1–4 with photos 2026-06-20 21:27:12 +02:00
m038 19d34622ca feat(demo): add story 4 — Florence Without a Map 2026-06-20 21:24:49 +02:00
m038 dd764c8726 feat(demo): add story 3 — One Evening in Siena 2026-06-20 21:23:18 +02:00
m038 dacda6fca0 feat(demo): add story 2 — Val d'Orcia at Dawn 2026-06-20 21:21:52 +02:00
m038 8f87155c1d feat(demo): add story 1 — Sorano: Rock and Time 2026-06-20 21:19:57 +02:00
m038 42ed59a6b3 chore(demo): cleanup old demo data, rename GPX files, update trip.md
- Remove japan-korea-2026 demo folder entirely
- Remove italy-2025 and italy-2026-demo dailies and stories (replaced in later tasks)
- Rename 4 long GPX filenames to short slugs (day-1 through day-4)
- Update trip.md title to 'Tuscany 2026'
- Add dailies/dailies.md index page
- Recreate empty dailies and 04.stories directories
2026-06-20 21:14:57 +02:00
m038 c403ea9593 fix(a11y): fix active filter button contrast (color-ink on accent-light bg) 2026-06-20 20:49:08 +02:00
m038 a2cdbd7506 feat(a11y): add unique aria-label to GPX delete buttons 2026-06-20 20:36:46 +02:00
m038 f463eadbef feat(a11y): add keyboard prev/next to photo strip and region landmark 2026-06-20 20:32:12 +02:00
m038 ce5d520817 feat(a11y): add aria-pressed to filter buttons and aria-expanded to stats/cycling toggles 2026-06-20 20:27:45 +02:00
m038 b1e1a5cb9a feat(a11y): fix --color-ink-muted and --color-accent contrast ratios 2026-06-20 20:23:03 +02:00
m038 a7786f263f feat(a11y): add skip-to-main link and main landmark id 2026-06-20 20:19:27 +02:00
m038 ffcf156289 feat: add story markers to trip map (white diamond); extend flash highlight to story cards 2026-06-20 17:53:56 +00:00
m038 d923f3eb46 feat: generate AI titles for 36 Pixelfed-imported entries 2026-06-20 16:38:28 +00:00
m038 075a8fa9d4 fix: restore system media types in media.yaml; gpx was silently overriding all built-in types 2026-06-20 18:31:48 +02:00
m038 20212fee25 perf: skip hero media lookup for journal entries — only story cards use it 2026-06-20 18:27:45 +02:00
m038 229532ab8b fix(story): fall back to direct URL when page.media fails due to media.types config override
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
2026-06-20 16:31:26 +02:00
m038 138649c8e5 docs: clarify intentional sort omission in dailies feed 2026-06-20 15:47:37 +02:00
m038 728a43c4c3 fix: add missing 2024-05-28-pixelfed-1 Northern America entry 2026-06-20 13:41:49 +00:00
m038 850d2f5c50 feat: replace journal entry card with inline journal-post on home page 2026-06-20 13:34:22 +00:00
m038 6283c840ff feat: import 36 Pixelfed posts into central-asia-2023, us-canada-mex-2024, italy-2025
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-20 13:33:35 +00:00
m038 7a9cd0f269 feat: add central-asia-2023 and us-canada-mex-2024 trip page trees 2026-06-20 13:25:57 +00:00
m038 cf5e1ecb2d chore: move italy demo to italy-2026-demo; clean japan and italy-2025 demo content 2026-06-20 13:18:31 +00:00
m038 da7fbaf5b1 feat: replace journal entry card with inline journal-post in trip feed 2026-06-20 13:04:09 +00:00
m038 e7482e5bdd feat: replace journal entry card with inline journal-post in dailies feed 2026-06-20 12:50:14 +00:00
m038 f829da10ec feat: add journal-post CSS component and dot-sync JS; remove stale journal-card-only rules 2026-06-20 14:32:04 +02:00
m038 fb5ae6732c fix: remove redundant background declaration from .trip-card:hover 2026-06-20 12:53:31 +02:00
m038 a398bcb737 feat: add map-to-card flash highlight on marker click 2026-06-20 12:47:51 +02:00
m038 9365f46440 fix: apply flat entry-card structure to home.html.twig 2026-06-20 12:43:58 +02:00
m038 246fbfde76 fix: add missing data-type attributes to entry cards in dailies.html.twig 2026-06-20 12:40:58 +02:00
m038 2a151b710c refactor: collapse entry card article+a to flat <a>, unify hover targets across card types
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPJztrVGbwic2xTG7G9fjM
2026-06-20 12:38:28 +02:00
m038 ca283d621a fix(story): restore datetime type, use date-only format; strip times from demo stories
Reverts text→datetime change. Uses format:'Y-m-d' (date-only) so the
datepicker omits the time component. Strips HH:MM from all Italy demo
story dates for consistency with the new date-only format.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-20 12:36:52 +02:00
m038 ca920a9fe8 feat: add fixed top and footer back pills to entry page 2026-06-20 12:31:09 +02:00
m038 26182ec363 feat: apply back-pill class to story footer back link
Apply the .back-pill class to the story footer back link and add
:not(.back-pill) guard to .story-footer a rule to prevent the accent
color override. This ensures the back pill maintains its design system
styling (border, background, text color) in story footers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPJztrVGbwic2xTG7G9fjM
2026-06-20 12:23:53 +02:00
m038 d0c821588e fix(story): replace datetime pickers with text fields for date inputs
Admin2's datetime widget ignores the blueprint format parameter and renders
dates in US MM/DD/YYYY locale format regardless. Switching both Start Date
and End Date to type:text eliminates the datepicker entirely — existing ISO
values (2025-09-06 20:00) display as typed, no locale interference.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-20 12:21:50 +02:00
m038 3edc18fe28 feat: add back-pill class, card hover lift, flash keyframe; remove duplicate story-escape 2026-06-20 12:20:14 +02:00
m038 5bc8d008df fix(story): end_date format Y-m-d H:i; fix guard comparison; remove test data
Blueprint: end_date format changed to Y-m-d H:i (same as start date) so
Admin2 uses the identical datepicker — avoids ambiguous d-m-Y input being
misread by PHP as m-d-Y.

Template guard: was comparing end_date string against page.date|date('Y-m-d')
which can never match. Now compares date-only parts of both fields:
page.header.end_date|date('Y-m-d') != page.date|date('Y-m-d')

Montalcino live page: removed test end_date '12-09-2026 00:00'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-20 12:10:21 +02:00
m038 5eca310bd8 fix(story): remove spurious end_date from Montalcino; guard start==end range
Montalcino demo story had end_date: 2025-09-06 matching its start date,
causing a '06 – 06 Sep' range display. Removed from both the live page
and the demo source.

Template: added guard so end_date equal to the start date never renders
as a range, even if it appears in frontmatter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-20 11:54:31 +02:00
m038 13d6576a2c fix(story): smart date range formatting + blueprint end_date format fix
Blueprint: end_date format changed from 'Y-m-d H:i' to 'Y-m-d' — the
demo frontmatter stores dates without a time component so Admin was
failing to parse it and showing the field empty.

Template: three-case smart range formatting:
- Same month & year  → 01 – 03 Sep 2025
- Same year only     → 01 Jan – 03 Sep 2025
- Different years    → 01 Jan 2025 – 03 Feb 2026

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-20 11:50:44 +02:00
m038 bc67a0ee88 fix(story): add end_date blueprint field; fix date range display
Blueprint: renamed 'Date' to 'Start Date', added optional 'End Date'
field (header.end_date) so it's editable in Admin.

Template: single date → 'd M Y'; range → 'd M Y – d M Y' (full year
on both sides — the old format silently dropped the start year).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-20 11:45:38 +02:00
m038 46c8a76633 fix(story): scrolly step cards — canvas surface + teal left accent bar
Card background was rgba(26,24,20,0.92) — identical to the page background
(--color-paper #1A1814), making cards invisible. Changed to --color-canvas
(#22201B) for a lifted surface. Added border-left: 3px solid --color-accent
as an editorial marker; other three sides keep the subtle --color-border.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-20 11:36:24 +02:00
m038 cc341cc944 fix(story): nav title cross-fades scroll-driven as hero content exits viewport
Replaced IntersectionObserver (discrete threshold) with a scroll RAF loop
using getBoundingClientRect. Opacity is computed from the fraction of
.story-hero__content still visible above the viewport top — so the nav title
fades in gradually as the hero title slides off the top edge, reaching full
opacity only when the element is completely gone.

Removed CSS transition (no longer needed; per-frame JS update is smooth).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-20 11:28:12 +02:00
m038 f4ee63282b fix(story): nav title hidden on load, DM Serif Display typography
Was visible on load because story-hero__content starts below the
viewport fold (bottom:18% of 140vh hero) — IO fired immediately
as 'not intersecting'. Fixed with hasBeenVisible flag: nav title
only appears after the element has entered then exited the viewport.

Typography changed from DM Sans/500 to DM Serif Display/400 at
--text-lg (1.375rem) with -0.01em tracking — same face as the
hero title, scaled down for the 60px nav bar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-20 11:24:36 +02:00
m038 326f28e4ac feat(story): sticky nav title + floating back-to-top pill
Nav title: absolutely centered in the site-header, fades in via
IntersectionObserver when .story-hero__content scrolls above the fold.
Hidden on mobile (< 640px) where there is no room.

Back-to-top: fixed bottom-right pill, appears after 80% of the hero
viewport is scrolled past, smooth-scrolls to top on click.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-20 11:15:06 +02:00
m038 6e5caf33ad fix(story): soft pill for scrolly caption — rounded edges with faded halo
border-radius: 9999px for a true pill shape.
box-shadow matching the background alpha creates the feathered edge fade
all the way around without any extra elements.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-20 11:09:37 +02:00
m038 49c4ab0341 fix(story): smooth hero overlay fade-out and add scrolly caption background
Overlay previously hard-hid with display:none when scrollY = 100vh, while
the title was still visible (it exits at ~115vh). Now fades in 0→0.65 over
the first 70vh then fades back out to 0 by 140vh (full hero height).

Scrolly caption changed from full-width to centered pill with
rgba(0,0,0,0.45) background — readable against any image regardless of tone.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-20 10:32:22 +02:00
m038 7dcaa703e0 ux: merge journey fields into entry/location tabs; unstack lat/lng and weather fields 2026-06-20 10:15:02 +02:00
m038 a3565677a5 demo: add hero images to all daily entries and Japan story; simplify Japan story image refs 2026-06-20 09:53:14 +02:00
m038 37c38e925a fix: add transport_mode to entry JSON serialisation in all three map templates; note bbox approach in isNearTrack 2026-06-20 00:54:04 +02:00
m038 3301f049cc feat: apply GPX connector algorithm to dailies feed mini-map 2026-06-20 00:47:39 +02:00
m038 b1665dad80 feat: use buildJourneySegments in trip.html.twig mini-map 2026-06-20 00:45:01 +02:00
m038 d9fd5eb74c feat: use buildJourneySegments in map.html.twig — suppress connectors covered by GPX 2026-06-20 00:42:34 +02:00
m038 dfca8ef6e2 feat: add GPX proximity algorithm to MapUtils (buildJourneySegments, extractTrackpoints)
Adds haversineKm, extractTrackpoints, isNearTrack, buildJourneySegments, and
addJourneySegments to the shared MapLibre GL IIFE. Updates MapUtils export to
expose the new functions. ES5-only; no arrow functions, const/let, or modules.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPJztrVGbwic2xTG7G9fjM
2026-06-20 00:39:39 +02:00
m038 6ce77d7be7 fix: restore entry.yaml original structure, keep only Journey tab addition 2026-06-20 00:36:48 +02:00
m038 2adf06831c feat: add force_connect and transport_mode fields to entry and story blueprints 2026-06-20 00:33:07 +02:00
m038 3772a64a0e fix: story back button uses history.back(); add demo images; fix story dates for chronological interleaving 2026-06-20 00:05:53 +02:00
m038 3bd1e61817 docs: add three Tuscany demo stories (gallery-led, scrollytelling-led, mood-fragment) 2026-06-19 23:41:48 +02:00
m038 14e386a122 fix: remove 1m per-step elevation threshold — Komoot data is pre-smoothed, threshold filtered nearly all gain/loss 2026-06-19 23:34:39 +02:00
m038 8152fe79b6 fix: compute GPX stats per-file to avoid spurious inter-track segments
Both stats.html.twig and trip.html.twig previously flattened all GPX
trackpoints into a single masterPts array before computing haversine
distance, elevation, and moving time. This caused the junction between
file N's last point and file N+1's first point to be treated as a real
segment — e.g. Florence→coast (~79 km, ~42 h) for Italy's 3-file demo
data, overstating distance and moving time significantly.

Fix: compute all metrics within each file independently and sum the
results. fileResults collection and callback consumption are unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPJztrVGbwic2xTG7G9fjM
2026-06-19 23:13:08 +02:00
m038 1a247e1889 fix: story template-story class, datetime attr, imageName escaping, raw content comments
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-19 23:12:47 +02:00
m038 103ceb62b9 fix: deterministic GPX ordering in parseGpxFiles (trip.html.twig) 2026-06-19 23:06:28 +02:00
m038 3845d1b5e4 docs: add demo story content (The Thousand Gates, all four shortcode blocks) 2026-06-19 23:04:11 +02:00
m038 c123a035ce feat: expand trip inline stats to 6 stats + add cycling panel with GPX parsing
- Expanded stats block from 4 to 6 stats (days, entries, countries, cities, distance, temp range)
- Added date_end-aware days calculation (uses header.date_end when available)
- Added cities dedup logic (seen_city_lower) matching Task 1 pattern
- Added temperature range computation (temp_min / temp_max)
- Added has_gpx boolean flag
- Distance label is conditional: km cycled (GPX) vs km roamed (no GPX)
- Stats note text is conditional to match distance mode
- Cycling button added to filter bar (only rendered when has_gpx)
- Cycling panel (7 stat blocks) added after stats block (hidden by default, toggled independently)
- Replaced old haversine IIFE with unified haversineKm + parseGpxFiles + IIFE
- GPX Mode A: fetches GPX files, sums trackpoint distances, populates cycling panel
- GPX Mode B: haversine between entry GPS points (no GPX)
- Updated .trip-stats-grid from repeat(4) to repeat(3) columns
- Added .trip-cycling-block, .trip-cycling-header, .trip-cycling-icon, .trip-cycling-title, .trip-cycling-grid CSS

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPJztrVGbwic2xTG7G9fjM
2026-06-19 23:01:42 +02:00
m038 dfd1c38396 feat: add stories listing page and all story/shortcode CSS 2026-06-19 22:59:17 +02:00
m038 48b877c439 fix: deterministic multi-GPX trackpoint ordering and catch-path completion
Pre-allocate fileResults[idx] slots so GPX files always concatenate in URL
order regardless of fetch arrival order (Bug 1). Both .then and .catch now
call computeDistance() after decrementing pending so a failed last fetch no
longer leaves the distance element permanently blank (Bug 2).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPJztrVGbwic2xTG7G9fjM
2026-06-19 22:56:38 +02:00
m038 0dc9095b4b feat: add story.html.twig with hero scroll effect and shortcode JS 2026-06-19 22:56:00 +02:00
m038 fcdb3de387 feat: add pull-quote and snap-gallery shortcodes 2026-06-19 22:50:27 +02:00
m038 3b5dc18ec6 feat: expand stats page to 6 stats — cities, temp range, distance mode detection 2026-06-19 22:50:12 +02:00
m038 a06f744ec1 feat: add scrolly-section shortcode (Scrollama-driven sticky image steps) 2026-06-19 22:47:23 +02:00
m038 c514bfd4a9 feat: add story-blocks plugin with chapter-break shortcode 2026-06-19 22:43:54 +02:00
m038 916969c96f feat: journey line — Catmull-Rom spline curve, dotted subordinate style under GPX tracks 2026-06-19 22:33:48 +02:00
m038 3ef8d48ee2 feat: add entry url to map_entries data and as data-url attribute on all markers 2026-06-19 22:18:04 +02:00
m038 997baf4cc3 fix: marker click scrolls to card on home/trip pages instead of navigating (no url field) 2026-06-19 22:14:58 +02:00
m038 456fc94c8e fix: bump MapLibre CSS specificity to 020 — CDN loads after style.css so same-specificity rules lost 2026-06-19 22:11:21 +02:00
m038 044e74f5d3 feat: hover-only title tooltip on map markers; click navigates to entry 2026-06-19 22:05:52 +02:00
m038 f7df6ef37e fix: remove cooperativeGestures, increase fitBounds padding, add popups to embedded maps 2026-06-19 22:01:54 +02:00
m038 a363052f5f feat: migrate trip overview map to MapLibre GL (removes last Leaflet reference) 2026-06-19 21:57:12 +02:00
m038 b431cfc0ac feat: migrate mini-map and home map to MapLibre GL 2026-06-19 21:49:52 +02:00
m038 87a782ae12 feat: migrate full map page to MapLibre GL with animated journey line 2026-06-19 21:46:23 +02:00
m038 12c5b2c4a1 feat: add shared MapLibre GL utilities (journey line, markers) 2026-06-19 21:43:45 +02:00
m038 0d1688c6c4 Revert "revert: remove out-of-scope stats block (belongs in separate task)"
This reverts commit a9043f711e.
2026-06-19 21:40:09 +02:00
m038 a9043f711e revert: remove out-of-scope stats block (belongs in separate task) 2026-06-19 21:39:42 +02:00
m038 93005bd7cd fix: replace raw rgba with color-mix token in MapLibre attribution style 2026-06-19 21:39:21 +02:00
m038 fe0aa669bc style: swap Leaflet CSS override for MapLibre design-token styles 2026-06-19 21:36:35 +02:00
m038 897da36a21 feat: add inline stats block with toggle to trip page
Adds Twig computation for days on road, countries visited, and GPS
points; an expandable stats panel (hidden by default) with haversine
distance calculation; and toggle JS that activates the Stats button.
2026-06-19 21:35:54 +02:00
m038 eb739d80ab feat: wire up feed filter — All content / Journal / Stories
Added JavaScript to the trip.html.twig template that:
- Adds event listeners to filter buttons (.trip-filter-btn)
- Shows/hides article cards based on data-type attribute (journal/story)
- Manages active state of filter buttons
- Displays empty state message when no results match the filter
- Uses ES5 syntax (no arrow functions, const/let, or template literals)

Also added hidden feed-filter-empty element to display appropriate
empty messages for each filter type.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RB86BaJBG3eGiMdfhmHRrQ
2026-06-19 21:32:13 +02:00
m038 0478a18fa8 feat: add filter bar markup and pill button styles to trip page
Replace the old trip-nav links with a new filter bar component featuring:
- Three pill buttons for filtering (All content, Journal, Stories)
- "All content" button active by default with teal accent styling
- Separate Stats button with matching pill styling
- CSS for buttons with hover and active states
- Responsive flexbox layout that wraps on narrow screens

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RB86BaJBG3eGiMdfhmHRrQ
2026-06-19 21:29:33 +02:00
m038 2508936928 feat: add data-type attributes to feed cards; restyle story card with full border 2026-06-19 21:26:50 +02:00
m038 650e97883b demo: add placeholder hero images to Tuscany Gravel 2025 entries (QA)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RB86BaJBG3eGiMdfhmHRrQ
2026-06-19 20:57:36 +02:00
m038 2eef8fbf9a fix: Leaflet void background corrected to actual CartoDB ocean color (#282828)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RB86BaJBG3eGiMdfhmHRrQ
2026-06-19 20:54:22 +02:00
m038 11224289de fix: Leaflet void background matches CartoDB ocean color (#0d0d17)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RB86BaJBG3eGiMdfhmHRrQ
2026-06-19 20:50:58 +02:00
m038 69c9f4f939 feat: trip page matches home layout — sticky map + feed, GPX route, no sidebar
- Same home-layout (45% sticky map / 55% scrollable feed) on every trip page
- GPX route overlay loaded from trip page media
- Marker click scrolls to entry card (same as home page)
- Map sub-nav link removed (map is now embedded)
- Separate /map page remains accessible by URL but has no nav link

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RB86BaJBG3eGiMdfhmHRrQ
2026-06-19 20:42:41 +02:00
m038 010478b3fa fix: sort past trips descending by date (newest first)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RB86BaJBG3eGiMdfhmHRrQ
2026-06-19 17:32:47 +02:00
m038 49d10f4816 fix: home map visible on mobile, invalidateSize on both maps
- Explicit height: 40vh on .home-map (not just 100% of parent) so Leaflet
  can measure the container reliably before CSS inheritance is resolved
- align-self: stretch on .home-map-col so it spans full width in flex column
- setTimeout invalidateSize(100ms) on home and dailies maps as safety net

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RB86BaJBG3eGiMdfhmHRrQ
2026-06-19 17:29:52 +02:00
m038 a9eda558c0 fix: nav slash, back button context, home page max-width
- Past Trips nav link: add missing / (base_url_absolute has no trailing slash)
- Entry back link: history.back() with journal fallback, label → "← Back"
- Home page: max-width 1400px instead of none — narrows layout on wide screens

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RB86BaJBG3eGiMdfhmHRrQ
2026-06-19 17:01:45 +02:00
m038 16b44513f2 fix: stories escape link goes to trip page not archive 2026-06-19 15:51:51 +02:00
m038 ab8a5138dd feat: dailies merges stories, id attrs for map sync; stories escape link 2026-06-19 15:47:42 +02:00
m038 b66f1cdb2d feat: trip page — entry counts, merged feed, sticky sidebar index 2026-06-19 15:45:06 +02:00
m038 a78236bf3b feat: home page template — sticky map + merged feed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RB86BaJBG3eGiMdfhmHRrQ
2026-06-19 15:42:43 +02:00
m038 a9843a0a2d feat: past trips archive with trip cards and entry counts 2026-06-19 15:40:15 +02:00
m038 5c98bf239a fix: add missing .trip-feed and .trip-sidebar-section CSS classes 2026-06-19 15:37:58 +02:00
m038 86b2778a47 feat: CSS for home layout, story cards, trip sidebar, escape link 2026-06-19 15:36:38 +02:00
m038 035c92f293 feat: home page routing — real / route, new nav (Home + Past Trips) 2026-06-19 15:34:12 +02:00
m038 fbc4fc195b fix: slice File to Blob before append so 3rd-arg filename is always used 2026-06-19 15:33:13 +02:00
m038 597add6c1d fix: use fd.append 3rd arg to set slugified filename in multipart upload 2026-06-19 15:13:00 +02:00
m038 1c9a6711b3 fix: slugify uploaded GPX filename before sending to API 2026-06-19 15:11:29 +02:00
m038 537f443cf1 feat: gpx-manager list, upload, delete via Grav API session auth 2026-06-19 14:58:25 +02:00
m038 e4451857c2 feat: gpx-manager template layout with trip sections 2026-06-19 14:57:59 +02:00
m038 feeef865aa feat: add gpx-manager page definition (access-protected) 2026-06-19 14:57:24 +02:00
m038 5c02432ce0 fix: use !important to override Leaflet default grey background 2026-06-19 13:22:53 +02:00
m038 d3ef42f04f fix: set leaflet-container background to match dark tile color, prevent grey flash 2026-06-19 13:21:50 +02:00
m038 bae9d68943 fix: switch map tiles to CartoDB dark (no API key required) 2026-06-19 13:18:36 +02:00
m038 dc162ff58c feat: switch to Stadia Alidade Smooth Dark map tiles 2026-06-19 13:11:42 +02:00
m038 3d5e29e26c feat: add paper grain texture, fix hardcoded colors, improve typography 2026-06-19 13:11:36 +02:00
m038 ba3a2ea9e7 feat: switch to warm-dark color tokens 2026-06-19 13:11:32 +02:00
m038 64b7fcc166 feat: Grav 2.0 compat — flex accounts/pages, api.super permission
- accounts.type: flex (required by admin2 API)
- pages.type: flex (required for admin2 pages API)
- Add access.api.super + api.access to mischa account (admin2 uses api.* permissions, not admin.*)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 13:02:25 +02:00
m038 0bb3b3bcce chore: consolidate all docs/plans/specs into main repo docs/
Moved from user/ repo: milestone specs, design spec, QA docs, research,
posting pipeline, bugs log, UI redesign plan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 10:36:47 +02:00
m038 a1acabbf17 chore: move docs/plans/specs to main repo 2026-06-19 10:36:39 +02:00
m038 70b4e1ca7a fix: use trip-relative URL for entry back-link, add active_trip sync comment
- entry.html.twig: replace hardcoded /tracker href with page.parent().url
- post-form.md: add comment to keep pageconfig.parent in sync with active_trip in site.yaml
2026-06-19 02:01:06 +02:00
m038 24f3c14d77 feat: add Admin blueprint for trip page type with date range, cover image, and album URL fields 2026-06-19 01:54:59 +02:00
m038 d1066d7eb3 chore: remove stale docs/demo/tracker (moved to docs/demo/trips/) 2026-06-19 01:52:36 +02:00
m038 ffda4568ab fix: update post form parent and add Italy 2025 demo trip with GPX routes
- Change pageconfig.parent from '/tracker' to '/trips/japan-korea-2026/dailies'
- Move japan-korea-2026 demo entries to docs/demo/trips/japan-korea-2026/dailies/
- Add Italy 2025 (Tuscany Gravel) demo trip: 5 entries with real Tuscany
  coordinates, plus trip.md, map/stats/stories stubs, and 3 GPX routes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 01:50:02 +02:00
m038 86997cb878 feat: add GPX route rendering to trip map via leaflet-gpx
Adds leaflet-gpx@2.1.2 CDN script to map template, collects *.gpx
media files from the trip page, and renders them as teal polylines
beneath entry pins. Also fixes user/config/media.yaml to use the
required types: key so Grav's Media class correctly discovers .gpx
files. Map remains functional when no GPX files are present.
2026-06-19 01:38:36 +02:00
m038 50a5f2d178 feat: add trip/trips/stories templates, update nav and map/stats to use trip-relative paths
- Rename tracker.html.twig to dailies.html.twig; update dailies.md template key
- Fix map.html.twig and stats.html.twig: find dailies via page.parent().route
- Update base.html.twig nav to use config.site.active_trip for all hrefs
- Fix dailies.html.twig mini-map link to use page.parent().url/map
- Create trip.html.twig, trips.html.twig, stories.html.twig

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 01:27:39 +02:00
m038 2a32917568 refactor: rename tracker to dailies (URL slug), keep nav label as Journal 2026-06-19 01:24:43 +02:00
m038 24acae2a85 feat: restructure pages under trips/japan-korea-2026 entity
- Create trips/japan-korea-2026/{tracker,map,stats,stories} hierarchy
- Move 8 entry folders from 01.tracker into trips/.../01.tracker/
- Add active_trip: japan-korea-2026 to site.yaml
- Whitelist GPX file type in media.yaml
2026-06-19 01:19:41 +02:00
m038 534b9a96f1 feat: add demo entries with images + fix form upload action
- New demo entry: Arashiyama with single hero image (bamboo.jpg)
- New demo entry: Gyeongbokgung with four gallery images (palace-gate,
  throne-hall, hanok-rooftops, bugaksan)
- post-form.md: add upload: true to process block so filepond photo
  uploads are handled after page creation; simplify list-of-maps to
  flat map (Symfony YAML preserves insertion order)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 00:12:55 +02:00
m038 3a79fe2cc7 fix: use ISO date format for page date storage
Changes dateformat.default from 'd M Y' to 'Y-m-d H:i' so dates
submitted via the post form are stored as '2026-06-19 10:30' rather
than '18 Jun 2026'. This ensures add-page-by-form generates slugs in
YYYY-MM-DD-HHmm-title order, preserving chronological sort.

All templates use explicit |date() filters so display is unaffected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 00:10:49 +02:00
m038 9a9220e066 docs: add posting pipeline reference and Admin entry blueprint
- posting-pipeline.md: full frontmatter reference, frontend form flow,
  Admin backend flow, page folder structure
- blueprints/entry.yaml: Admin form fields for city, country, lat/lng,
  weather condition dropdown, temperature, hero image

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 23:24:37 +02:00
m038 c05b9e3400 feat: add Grav 2.0 compat flag for cache-on-save plugin and switch GPM to testing channel
- Add blueprints.yaml for cache-on-save plugin with Grav 2.0 support
- Update system.yaml GPM setting from stable to testing channel
- Update .gitignore to allow cache-on-save plugin tracking

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 23:19:27 +02:00
m038 4fcd74df8a Merge pull request 'Tracker ordering fix + March–April fixture entries' (#1) from experimental-polar-steps into main
Reviewed-on: #1
2026-06-18 22:41:53 +02:00
m038 14791ab69e feat: add March–April trip fixture entries, remove stale test entry
Seven fixture entries (March 25 Narita through April 1 Seoul) used as
Playwright test fixtures for tracker ordering and entry-page tests.
Removes the leftover June 18 test entry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 22:34:29 +02:00
m038 c63378740c fix: use page.collection() for tracker descending date order
page.children ignores the order.by/dir frontmatter config; page.collection()
applies it, so entries now render newest-first as intended.

Also wire Grav asset pipeline into base template (assets.css/js tags).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 22:34:19 +02:00
m038 f0768c297b fix: switch photos to filepond for AJAX upload + increase limit to 100MB 2026-06-18 20:26:22 +02:00
m038 4558f94c3f fix: button color, replace native validation with custom inline errors 2026-06-18 20:10:05 +02:00
m038 682ba00bea fix: make Post Daily button full-width and mobile-friendly 2026-06-18 20:02:20 +02:00
m038 6b24215190 fix: rewire add-page-by-form so posts actually get created
Two root-cause bugs:
1. Wrong action name: 'add-page-by-form' is not handled by the plugin;
   the plugin only matches 'addpage' or 'add_page'. Using the wrong name
   meant the action silently no-oped while 'message' still fired, showing
   'Entry posted successfully!' for a post that was never written.
2. Config in wrong place: parent/slug/template must be in 'pageconfig' and
   'pagefrontmatter' frontmatter blocks on the form page — the plugin reads
   from page->header(), not from the process block params.

Fix: move config to pageconfig/pagefrontmatter, change action to 'add_page'.
Slug is built from date+title fields (e.g. 2026-06-18-1430-my-title).
Photos destination changed to '@self' so the plugin copies from flash to
the new entry folder correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 19:51:58 +02:00
m038 50ab4f522b fix: redirect back to /post after login instead of always going to /tracker 2026-06-18 19:42:33 +02:00
m038 f631ca3cfd fix: raise Grav upload_limit to 25MB to match PHP config 2026-06-18 19:40:54 +02:00
m038 8cc141d7d2 fix: correct HTML rendering in entry body and feed excerpts
- entry.html.twig: add |raw to page.content (autoescape: true in
  system.yaml was escaping all HTML output including rendered markdown)
- tracker.html.twig: use |striptags|slice(0,250) for clean plain-text
  excerpts instead of raw HTML summary
- Both templates: fix location display whitespace (Tokyo , Japan → Tokyo, Japan)
  using parts array pattern with Twig whitespace control

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 19:30:47 +02:00
m038 0eb5e8c6a5 docs: update demo instructions to use make commands 2026-06-18 17:52:38 +02:00
m038 b37f46de55 feat: add demo content (7 Japan/Korea entries) and update summary
Demo covers: Tokyo × 2, Mt. Fuji (snow), Kyoto, Nara, Osaka, Seoul.
All entries have GPS — shows full map route, 2-country stats, weather
variety including snow. Deploy/reset instructions in docs/demo/README.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 14:33:56 +02:00
m038 bbcea0094c docs: add visual design QA checklist to test plan 2026-06-18 14:25:20 +02:00
m038 1addbc294f feat: add reduced-motion support and :focus-visible ring 2026-06-18 14:24:37 +02:00
m038 5a3f2bc0c5 feat: apply design tokens to stats, map, mini-map; teal markers 2026-06-18 14:24:16 +02:00
m038 c60f6726df feat: redesign post form — hide GPS fields via :has(), teal CTA, status feedback 2026-06-18 14:23:08 +02:00
m038 c5d9c92968 feat: redesign single entry page — hero image, display typography, rule separator 2026-06-18 14:22:05 +02:00
m038 521c678f1c feat: redesign entry feed cards with full-bleed photo + gradient overlay 2026-06-18 14:21:00 +02:00
m038 c33dc2ca56 feat: redesign site header — accent bar, DM Serif title, sticky, active nav 2026-06-18 14:19:58 +02:00
m038 49f60a873a feat: update global styles and login form to use design tokens 2026-06-18 14:19:23 +02:00
m038 573ac00a8d feat: add design tokens and DM font loading 2026-06-18 14:18:42 +02:00
m038 d190094e80 docs: add UI redesign spec and implementation plan
Design direction: Field Notes — DM Serif Display + DM Sans typography,
deep teal (#1F6B5A) accent, full-bleed entry card photos with overlay.
9-task implementation plan covering tokens, header, feed, entry page,
post form, stats/map, mobile polish, and visual QA checklist.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 14:15:27 +02:00
m038 6cd5ed0100 Update docs to reflect date+time+title slug format
- qa-test-plan: update expected folder, URL, and automation greps in
  TC-P.5, TC-P.6, TC-P.7; add slug explanation block to test data;
  rewrite TC-P.10 to cover two posts on the same day (now valid)
- bugs-and-fixes: add BUG-003 documenting the silent duplicate-date
  failure and the slug fix

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 13:50:47 +02:00
m038 c928fff92e Improve post slug: date + time + title
Slug format: 2026-06-18-1430-arrived-in-tokyo
- Eliminates one-post-per-day collision (BUG TC-P.10)
- URL is stable after creation (time baked in, title change doesn't affect it)
- regex_replace strips non-alphanumeric chars; trim('-') cleans edges

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 13:45:35 +02:00
m038 8ddc1af5c0 Fix cache-on-save plugin: use deleteAll() not clear()
Grav\Common\Cache has no clear() method in this version; the correct
method is deleteAll().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 13:30:01 +02:00
m038 fdb423d2c7 Fix BUG-001 and BUG-002; add post-submission QA test plan and QA entry
BUG-001: cache-on-save plugin clears page cache on onFormProcessed so
new entries appear in the tracker feed immediately after submission.

BUG-002: disabled Twig template cache (twig.cache: false) so theme
file changes take effect without a manual cache flush.

Also adds bugs-and-fixes.md, corrects TC-P test URLs (.entry suffix),
fixes TC-P.1 expectation (inline login form, not a redirect), and
creates the QA test entry for automated scenario verification.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 13:21:26 +02:00
m038 03ebdd6acb Phase 5: Summary document for morning review 2026-06-18 01:17:26 +02:00
m038 94e833273d Phase 5: QA test plan and automated test results 2026-06-18 01:16:42 +02:00
m038 1251086b69 Phase 4 M4: Mini-map on tracker feed with route line and entry navigation 2026-06-18 01:13:47 +02:00
m038 df18b9cd5a Phase 4 M3: Statistics page with days, entries, countries, distance 2026-06-18 01:13:13 +02:00
m038 82efc6450f Phase 4 M2: Interactive Leaflet map with route polyline and entry markers 2026-06-18 01:11:43 +02:00
m038 d3fcde9b0b Phase 4 M1: Entry enrichment — location, weather, gallery, hero image 2026-06-18 01:10:41 +02:00
m038 f1181a07b4 Phase 3: Product specs for Milestones 1-4 2026-06-18 01:07:21 +02:00
m038 c61f673511 Phase 2: PM analysis and milestone plan 2026-06-18 01:03:38 +02:00
m038 d1f905f196 Phase 1: Research docs for Polarsteps and FindPenguins 2026-06-18 01:02:20 +02:00
480 changed files with 990149 additions and 262 deletions
+8 -1
View File
@@ -1,2 +1,9 @@
/plugins/ /plugins/*
!/plugins/.gitkeep
!/plugins/cache-on-save/
!/plugins/story-blocks/
/data/ /data/
/pages/01.trips/italy-2026-demo/
/pages/02.post/*ui-test*/
/config/plugins/git-sync.yaml
/config/security.yaml
+1
View File
@@ -0,0 +1 @@
/* @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved. */
+10
View File
@@ -0,0 +1,10 @@
# Deny all direct web access to this folder and everything beneath it.
# Grav reads these files server-side; they must never be served over HTTP.
# This is a defense-in-depth backup for the rules in the site root .htaccess.
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order allow,deny
Deny from all
</IfModule>
+20 -6
View File
@@ -1,13 +1,27 @@
login: mischa login: mischa
state: enabled
title: ''
email: mischa@gorinskat.nl
fullname: Mischa
hashed_password: $2y$10$xyV7bAUWEo75K6LbatUuYe/6x2Tj9nT6YnIjaDvESAhU2hJ7tjG2.
language: en
modified: 1782051328
admin_next:
preferences:
pluginsViewMode: cards
colorMode: dark
accentHue: 271
accentSaturation: 91
fontFamily: inter
pagesViewMode: tree
content_editor: ''
groups: { }
access: access:
admin: admin:
login: true login: true
super: true super: true
site: site:
login: true login: true
state: enabled api:
title: Mischa super: true
email: mischa@gorinskat.nl access: true
fullname: Mischa
hashed_password: '$2y$10$dUEYTopGEDouFoAa/Wxw6.vsOA71yr3gSStfDvr10aKm4ih9ObQ7m'
language: en
+20
View File
@@ -0,0 +1,20 @@
form:
validation: loose
fields:
active_trip:
type: pages
label: Active Trip
start_route: '/trips'
show_root: false
show_slug: true
travelling:
type: toggle
label: Currently Travelling
highlight: 1
default: false
options:
1: 'Yes'
0: 'No'
validate:
type: bool
-84
View File
@@ -1,84 +0,0 @@
name: Daily Entry
extends@: default
form:
fields:
tabs:
type: tabs
active: 1
fields:
content:
type: tab
title: Entry
fields:
header.title:
type: text
label: Title
validate:
required: true
header.date:
type: datetime
label: Date
format: 'Y-m-d H:i'
validate:
required: true
content:
type: markdown
label: Content
validate:
required: true
header.hero_image:
type: text
label: 'Hero Image Filename'
help: 'Filename of the main photo for this entry (e.g. photo.jpg). Upload photos via the Media tab.'
location:
type: tab
title: Location
fields:
header.lat:
type: number
label: Latitude
help: 'GPS latitude (for map, Milestone 2)'
placeholder: '48.8566'
step: any
header.lng:
type: number
label: Longitude
help: 'GPS longitude (for map, Milestone 2)'
placeholder: '2.3522'
step: any
publishing:
type: tab
title: Publishing
fields:
header.published:
type: toggle
label: Published
highlight: 1
default: 1
options:
1: 'Yes'
0: 'No'
validate:
type: bool
header.publish_date:
type: datetime
label: 'Publish Date'
help: 'Schedule future publication (leave blank to publish immediately)'
format: 'Y-m-d H:i'
header.unpublish_date:
type: datetime
label: 'Unpublish Date'
help: 'Automatically unpublish at this date/time'
format: 'Y-m-d H:i'
+10
View File
@@ -0,0 +1,10 @@
# Deny all direct web access to this folder and everything beneath it.
# Grav reads these files server-side; they must never be served over HTTP.
# This is a defense-in-depth backup for the rules in the site root .htaccess.
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order allow,deny
Deny from all
</IfModule>
+23
View File
@@ -0,0 +1,23 @@
ui:
defaults:
colorMode: ''
accentHue: 271
accentSaturation: 91
fontFamily: inter
fontSize: normal
editorMode: normal
editorStickyToolbar: true
editorFixedHeight: 0
adminLanguage: en-US
pagesPerPage: 20
pagesViewMode: tree
usersViewMode: cards
groupsViewMode: cards
pluginsViewMode: cards
themesViewMode: cards
settings:
autoSaveEnabled: false
autoSaveToolbarUndo: true
autoSaveBatchWindowMs: 0
collabEnabled: false
menubarLinks: { }
+225
View File
@@ -0,0 +1,225 @@
types:
defaults:
type: file
thumb: media/thumb.png
mime: application/octet-stream
image:
filters:
default:
- enableProgressive
jpg:
type: image
thumb: media/thumb-jpg.png
mime: image/jpeg
jpe:
type: image
thumb: media/thumb-jpg.png
mime: image/jpeg
jpeg:
type: image
thumb: media/thumb-jpg.png
mime: image/jpeg
png:
type: image
thumb: media/thumb-png.png
mime: image/png
webp:
type: image
thumb: media/thumb-webp.png
mime: image/webp
avif:
type: image
thumb: media/thumb.png
mime: image/avif
gif:
type: animated
thumb: media/thumb-gif.png
mime: image/gif
svg:
type: vector
thumb: media/thumb-svg.png
mime: image/svg+xml
mp4:
type: video
thumb: media/thumb-mp4.png
mime: video/mp4
mov:
type: video
thumb: media/thumb-mov.png
mime: video/quicktime
m4v:
type: video
thumb: media/thumb-m4v.png
mime: video/x-m4v
swf:
type: video
thumb: media/thumb-swf.png
mime: video/x-flv
flv:
type: video
thumb: media/thumb-flv.png
mime: video/x-flv
webm:
type: video
thumb: media/thumb-webm.png
mime: video/webm
ogv:
type: video
thumb: media/thumb-ogg.png
mime: video/ogg
mp3:
type: audio
thumb: media/thumb-mp3.png
mime: audio/mp3
ogg:
type: audio
thumb: media/thumb-ogg.png
mime: audio/ogg
wma:
type: audio
thumb: media/thumb-wma.png
mime: audio/wma
m4a:
type: audio
thumb: media/thumb-m4a.png
mime: audio/m4a
wav:
type: audio
thumb: media/thumb-wav.png
mime: audio/wav
aiff:
type: audio
thumb: media/thumb-aif.png
mime: audio/aiff
aif:
type: audio
thumb: media/thumb-aif.png
mime: audio/aiff
txt:
type: file
thumb: media/thumb-txt.png
mime: text/plain
xml:
type: file
thumb: media/thumb-xml.png
mime: application/xml
doc:
type: file
thumb: media/thumb-doc.png
mime: application/msword
docx:
type: file
thumb: media/thumb-docx.png
mime: application/vnd.openxmlformats-officedocument.wordprocessingml.document
xls:
type: file
thumb: media/thumb-xls.png
mime: application/vnd.ms-excel
xlsx:
type: file
thumb: media/thumb-xlsx.png
mime: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
ppt:
type: file
thumb: media/thumb-ppt.png
mime: application/vnd.ms-powerpoint
pptx:
type: file
thumb: media/thumb-pptx.png
mime: application/vnd.openxmlformats-officedocument.presentationml.presentation
pps:
type: file
thumb: media/thumb-pps.png
mime: application/vnd.ms-powerpoint
rtf:
type: file
thumb: media/thumb-rtf.png
mime: application/rtf
bmp:
type: file
thumb: media/thumb-bmp.png
mime: image/bmp
tiff:
type: file
thumb: media/thumb-tiff.png
mime: image/tiff
mpeg:
type: file
thumb: media/thumb-mpg.png
mime: video/mpeg
mpg:
type: file
thumb: media/thumb-mpg.png
mime: video/mpeg
mpe:
type: file
thumb: media/thumb-mpe.png
mime: video/mpeg
avi:
type: file
thumb: media/thumb-avi.png
mime: video/msvideo
wmv:
type: file
thumb: media/thumb-wmv.png
mime: video/x-ms-wmv
html:
type: file
thumb: media/thumb-html.png
mime: text/html
htm:
type: file
thumb: media/thumb-html.png
mime: text/html
ics:
type: iCal
thumb: media/thumb-ics.png
mime: text/calendar
pdf:
type: file
thumb: media/thumb-pdf.png
mime: application/pdf
ai:
type: file
thumb: media/thumb-ai.png
mime: image/ai
psd:
type: file
thumb: media/thumb-psd.png
mime: image/psd
zip:
type: file
thumb: media/thumb-zip.png
mime: application/zip
7z:
type: file
thumb: media/thumb-7z.png
mime: application/x-7z-compressed
gz:
type: file
thumb: media/thumb-gz.png
mime: application/x-gzip
tar:
type: file
thumb: media/thumb-tar.png
mime: application/x-tar
css:
type: file
thumb: media/thumb-css.png
mime: text/css
js:
type: file
thumb: media/thumb-js.png
mime: text/javascript
json:
type: file
thumb: media/thumb-json.png
mime: application/json
vcf:
type: file
thumb: media/thumb-vcf.png
mime: text/x-vcard
gpx:
type: file
mime: application/gpx+xml
+4
View File
@@ -0,0 +1,4 @@
popularity:
salt: 671ae9ab4f792c7dc860dbe8be288f2fdebdb3b4615f3c0f43211ecb95aaeeb3
auth:
jwt_secret: 61a84160bdd430768c82c4fe153e151a7a6f68f993c3779a5f36d32ee9293653
+1 -1
View File
@@ -8,5 +8,5 @@ user_registration:
route: /login route: /login
built_in_css: true built_in_css: true
built_in_js: true built_in_js: true
redirect_after_login: /tracker redirect_after_login: ''
site_host: https://intotheeast.com site_host: https://intotheeast.com
+7
View File
@@ -0,0 +1,7 @@
<?php
// Auto-generated private secret. Do NOT commit to version control.
// Used for CSRF nonce signing and admin rate-limit hashing. Regenerate by
// deleting this file; the next request will write a new value.
return 'lsUHWFkCwvGZrL';
-1
View File
@@ -1 +0,0 @@
salt: lsUHWFkCwvGZrL
+3 -2
View File
@@ -1,8 +1,9 @@
title: 'Into the East' title: 'Into the East'
description: 'A travel blog by Mischa'
author: author:
name: Mischa name: Mischa
email: mischa@gorinskat.nl email: mischa@gorinskat.nl
taxonomies: [category, tag]
metadata: metadata:
description: 'Into the East — travel journal' description: 'Into the East — travel journal'
description: 'A travel blog by Mischa'
active_trip: /trips/us-canada-mex-2024
travelling: false
+9 -9
View File
@@ -5,7 +5,7 @@ wrapped_site: false
reverse_proxy_setup: false reverse_proxy_setup: false
force_ssl: false force_ssl: false
force_lowercase_urls: true force_lowercase_urls: true
custom_base_url: 'http://100.96.115.96:8081' custom_base_url: ''
username_regex: '^[a-z0-9_-]{3,16}$' username_regex: '^[a-z0-9_-]{3,16}$'
pwd_regex: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}' pwd_regex: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}'
intl_enabled: true intl_enabled: true
@@ -28,10 +28,10 @@ languages:
pages_fallback_only: false pages_fallback_only: false
debug: false debug: false
home: home:
alias: /tracker alias: /home
hide_in_urls: false hide_in_urls: false
pages: pages:
type: regular type: flex
dirs: dirs:
- 'page://' - 'page://'
theme: intotheeast theme: intotheeast
@@ -41,7 +41,7 @@ pages:
list: list:
count: 20 count: 20
dateformat: dateformat:
default: 'd M Y' default: 'Y-m-d H:i'
short: 'D, d M Y G:i:s' short: 'D, d M Y G:i:s'
long: 'D, d M Y G:i:s' long: 'D, d M Y G:i:s'
publish_dates: true publish_dates: true
@@ -125,10 +125,10 @@ cache:
server: null server: null
port: null port: null
twig: twig:
cache: true cache: false
debug: true debug: true
auto_reload: true auto_reload: true
autoescape: true autoescape: false
undefined_functions: true undefined_functions: true
undefined_filters: true undefined_filters: true
safe_functions: { } safe_functions: { }
@@ -195,7 +195,7 @@ media:
unsupported_inline_types: null unsupported_inline_types: null
allowed_fallback_types: null allowed_fallback_types: null
auto_metadata_exif: false auto_metadata_exif: false
upload_limit: 2097152 upload_limit: 104857600
session: session:
enabled: true enabled: true
initialize: true initialize: true
@@ -210,7 +210,7 @@ session:
domain: null domain: null
path: null path: null
gpm: gpm:
releases: stable releases: testing
official_gpm_only: true official_gpm_only: true
http: http:
method: curl method: curl
@@ -221,7 +221,7 @@ http:
verify_peer: true verify_peer: true
verify_host: true verify_host: true
accounts: accounts:
type: regular type: flex
storage: file storage: file
avatar: gravatar avatar: gravatar
flex: flex:
+68
View File
@@ -0,0 +1,68 @@
# Demo Content
Seven sample journal entries for a fictional Japan + South Korea trip (MarchApril 2026). Used to showcase a populated site during development, design review, and QA — without touching real trip content.
---
## What's included
| Entry | Date | Location | Weather | GPS |
|---|---|---|---|---|
| Wheels Down at Narita | 25 Mar · 15:40 | Tokyo, Japan | Sunny 16°C | 35.772, 140.393 |
| Sakura in Ueno Park | 26 Mar · 10:00 | Tokyo, Japan | Partly cloudy 14°C | 35.716, 139.775 |
| Summit Clouds and Snow | 27 Mar · 07:15 | Kawaguchiko, Japan | **Snow 1°C** | 35.510, 138.765 |
| A Thousand Torii Gates | 28 Mar · 11:30 | Kyoto, Japan | Sunny 18°C | 34.967, 135.773 |
| The Deer of Nara | 29 Mar · 14:00 | Nara, Japan | Partly cloudy 17°C | 34.685, 135.805 |
| Dotonbori After Dark | 30 Mar · 18:00 | Osaka, Japan | Cloudy 19°C | 34.669, 135.502 |
| Seoul Calling | 01 Apr · 09:00 | Seoul, South Korea | Rain 10°C | 37.564, 126.985 |
**Features demonstrated:**
- Feed: 7 entry cards (all text-only; add photos to entries to test the photo card variant)
- Map: full route from Tokyo → Osaka → Seoul, with visible polyline
- Stats: 7 entries, 2 countries, ~1,300 km traveled (Tokyo → Seoul straight-line sum)
- Weather variety: Sunny, Partly cloudy, Snow, Rain, Cloudy
- Multi-day, multi-city, multi-country trip structure
---
## How to deploy demo content
```bash
make demo-load
```
Open `http://localhost:8081/tracker` — all 7 entries should appear.
---
## How to reset (remove demo content)
Removes all demo entries. Real entries with different slugs are left untouched.
```bash
make demo-reset
```
---
## Adding photos to demo entries
The demo entries have no photos (binary files aren't tracked in git). To test the photo card layout, drop one or more JPEG/PNG files into an entry folder:
```bash
user/docs/demo/tracker/2026-03-27-0715-summit-clouds-and-snow.entry/
entry.md
fuji-lake.jpg ← add any image here
```
Then copy the folder to the pages tree as above. The first image found will be used as the hero on the feed card and the entry hero.
---
## Notes
- Entries use the same frontmatter format as real posts created via `/post`
- Slug format matches the current system: `YYYY-MM-DD-HHmm-title-slug`
- GPS coordinates are real and will show on the map
- The snow entry (Kawaguchiko, 27 Mar) demonstrates snow weather badge rendering
- Seoul entry crosses into a second country — verifies the Stats country count logic
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+4
View File
@@ -0,0 +1,4 @@
---
title: 'Trip Map'
template: map
---
+4
View File
@@ -0,0 +1,4 @@
---
title: 'Trip Stats'
template: stats
---
+5
View File
@@ -0,0 +1,5 @@
---
title: Stories
template: stories
published: true
---
+8
View File
@@ -0,0 +1,8 @@
---
title: 'Tuscany Gravel 2025'
template: trip
date: '2025-09-01'
date_start: '2025-09-01'
date_end: '2025-09-08'
cover_image: ''
---
Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

@@ -0,0 +1,43 @@
---
title: 'Sorano: Rock and Time'
date: '2026-09-03'
location_name: Sorano
location_country: Italy
lat: 42.683
lng: 11.715
hero_image: hero.jpg
hero_alt: Medieval town of Sorano clinging to pale tufa cliffs at dusk
published: true
---
The road from Orbetello climbs inland through scrubland and heat. For most of the afternoon there is nothing on the horizon except sky and the occasional electricity pylon. Then, at the top of a ridge, Sorano appears — and the word "appears" does not quite cover it. The town has been carved from a cliff of tufa, a pale volcanic rock so soft you can score it with a fingernail. The buildings are the cliff and the cliff is the buildings.
[scrolly-section image="hero.jpg" alt="Medieval town of Sorano seen from the approach road, perched on pale tufa cliffs" caption="Sorano — tufa cliff town, Grosseto province"]
The approach by bike gives you an unusually long time to study it. The descent into the valley and the climb back up take perhaps forty minutes, and the town is visible for most of that time, doing nothing, requiring nothing.
---
Close up the rock is extraordinary. Hundreds of tomb niches cut into the cliff face — Etruscan graves, most of them open to the sky now, their contents long removed. The people who built this town chose to live surrounded by the evidence of their own mortality. This seems either very brave or very sensible.
---
The gate into the old town is fifteenth century and narrow enough that loaded bikes don't fit without turning sideways. Inside, the air is noticeably cooler and the alleys are steep, paved with the same pale tufa, worn smooth by centuries of feet.
[/scrolly-section]
We found a wall to lean the bikes against and sat looking south over the valley we had come from. The light was going amber. Below us, the road we had ridden was already in shadow.
[chapter-break image="photo-1.jpg" title="After Dark" number="II" alt="Narrow medieval alley in Sorano at dusk, pale stone walls glowing warm" /]
[pull-quote image="photo-1.jpg" alt="Stone alley in Sorano lit by a single lantern at night"]
A town built on rock, carved from rock, returning slowly to rock. Two thousand years of human effort and the cliff remains indifferent.
[/pull-quote]
[scrolly-section image="photo-2.jpg" alt="View south from the tufa cliff walls of Sorano at dusk" caption="Val di Fiora, from the old walls"]
One restaurant was open. The menu was four items. We had the pasta with wild boar and the pasta with truffles and a carafe of local wine that cost six euros and was excellent.
---
The owner sat at the next table watching a football match on his phone without headphones. Nobody minded. The town outside was completely quiet.
[/scrolly-section]
We were in bed before nine. Sorano at night is absolutely silent. It has been this quiet, in approximately this configuration, for a very long time.
Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

@@ -0,0 +1,30 @@
---
title: "Val d'Orcia at Dawn"
date: '2026-09-05'
location_name: Val d'Orcia
location_country: Italy
lat: 43.078
lng: 11.676
hero_image: hero.jpg
hero_alt: Wide Tuscan valley at dawn, long cypress shadows across pale gravel road
published: true
featured: true
---
We left before the heat arrived. The alarm was five-thirty and the sky outside the tent was still more grey than blue. The valley was invisible in the dark except as an absence — a vast silence below us where the shapes of hills ought to be. By six the light had changed. The Val d'Orcia is one of those landscapes that photographers wait years to shoot at this hour, and you can see why: the light arrives at an angle that makes everything look like something from a different century.
[snap-gallery images="hero.jpg,photo-1.jpg,photo-2.jpg" captions="Six in the morning: the valley belongs entirely to the light,The Cypress Road — every photograph of Tuscany was taken here or somewhere like it,A farmhouse that has been sitting on this hill for four hundred years" alts="Wide misty Tuscan valley at dawn with long shadows,Straight road lined by tall cypress trees in morning light,Stone farmhouse on a hilltop with rolling landscape behind" /]
The roads down here are white gravel — strade bianche — and the tyres make a particular sound on them that you don't get anywhere else. We rode for two hours without seeing a car. The only other people were two elderly men walking a dog in the opposite direction. They waved.
[chapter-break image="photo-1.jpg" title="The Hour Before Heat" alt="Cypress road vanishing into a hazy summer morning" /]
By nine the temperature had already shifted. The quality of the light changed — softer, more diffuse, the sky turning white at the edges. The windows of the farmhouses began to open. Dogs that had been invisible in the dark became visible on walls and in doorways, watching us with professional detachment.
[snap-gallery images="photo-2.jpg,hero.jpg" captions="The road changes from asphalt to gravel to packed earth and back again without warning,The valley floor at nine: the shadows have shortened, the colours have flattened" alts="Farmhouse detail with terracotta roof and single cypress tree,Tuscan valley road in mid-morning haze" /]
[pull-quote]
The best hours of a cycling day are the ones nobody else sees. Before the heat arrives, before the cafes open, before the traffic comes. Everything belongs to you then.
[/pull-quote]
We reached Pienza at eleven-thirty. The ice-cream queue was eight deep and entirely justified.
Binary file not shown.

After

Width:  |  Height:  |  Size: 419 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

@@ -0,0 +1,33 @@
---
title: 'One Evening in Siena'
date: '2026-09-05'
location_name: Siena
location_country: Italy
lat: 43.318
lng: 11.330
hero_image: hero.jpg
hero_alt: Piazza del Campo at dusk, terracotta paving fading from gold to shadow
published: true
---
[pull-quote image="hero.jpg" alt="Piazza del Campo seen from the upper rim at golden hour"]
Siena is not a city that tries to impress you. It has been here for a thousand years and intends to be here for a thousand more. You fit around it, not the other way.
[/pull-quote]
We rolled in at half past six, legs finished, panniers heavier than they started. The Campo appeared without warning at the end of a narrow street and we both stopped pedalling at exactly the same moment. That particular square does something to people. It is partly the shape — a shallow bowl, a scallop shell, the way it holds you — and partly the light at that hour, which turns the terracotta pavement the colour of old copper.
[chapter-break image="photo-1.jpg" title="The Campo" number="I" alt="Detail of Siena's herringbone brick pavement catching the last light" /]
[scrolly-section image="hero.jpg" alt="Piazza del Campo filling with people as evening comes" caption="Campo, 19:00 — the square fills from the edges inward"]
The locals arrive first. They know which spot faces west and which benches stay in the shade longest. Then the tourists, then the pigeons, then the long shadows.
---
A busker with an accordion near the Fonte Gaia. A group of students lying on the slope reading. Three children running in a circle for reasons nobody questioned.
---
We sat on the pavement with our backs against the warm brickwork of the Palazzo Pubblico and did not move for forty minutes. The relief of sitting still after eight hours on a bike is a specific physical sensation. It travels upward from your legs and settles somewhere just behind the sternum.
[/scrolly-section]
We found a place for dinner three streets away, down a flight of steps with no sign outside. The pasta was handmade, the wine was local, the bill was reasonable. We were in bed by ten. Tomorrow: Florence.
Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

@@ -0,0 +1,35 @@
---
title: 'Florence Without a Map'
date: '2026-09-07'
location_name: Florence
location_country: Italy
lat: 43.769
lng: 11.255
hero_image: hero.jpg
hero_alt: Arno river at midday with Ponte Vecchio, ochre buildings reflected in still water
published: true
---
No route today. No GPS, no distance target, no reason to be anywhere by any particular time. After six days of forward motion this felt almost wrong — the instinct to check the elevation profile arriving at nothing. We put the bikes in the hotel basement and walked out into Florence on foot.
[chapter-break image="hero.jpg" title="Day Seven" number="VII" alt="Arno river and Ponte Vecchio from Ponte Santa Trinita at midday" /]
[snap-gallery images="hero.jpg,photo-1.jpg" captions="The Arno at noon — greener than expected, the bridges older than you remember,Via dei Servi: washing lines, shutters, a cat on a warm stone ledge that had been warm since morning" alts="Arno river with Ponte Vecchio reflected in still water at midday,Narrow Florence street with laundry strung between buildings" /]
[pull-quote]
Cycling makes you earn every city you arrive at. Florence, we got for free. It felt like a gift and a debt simultaneously.
[/pull-quote]
[scrolly-section image="photo-1.jpg" alt="Narrow Oltrarno street in afternoon light" caption="Oltrarno, 14:00"]
The Uffizi had a queue that stretched around two corners and disappeared into a side street. We looked at it for a moment and went to find coffee instead. This felt correct.
---
A covered market in the Oltrarno that nobody had told us about. A man selling leather goods from a table he clearly reassembled each morning from identical components. A small dog sleeping under a fruit stall in a precisely calculated patch of shade.
---
We crossed the Ponte Vecchio at two in the afternoon, which is exactly the wrong time to cross the Ponte Vecchio, and it was still worth it. The light off the Arno at that hour is genuinely extraordinary and all the photographs in the world do not prepare you for it.
[/scrolly-section]
Dinner near the apartment, early. Feet sore in a different way from legs sore — a smaller, more concentrated complaint. Tomorrow: the last day. The coast road home.
Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

@@ -0,0 +1,16 @@
---
title: 'Setting Off from Campiglia'
date: '2026-09-01 07:00'
template: entry
published: true
featured: true
hero_image: ''
lat: 43.024
lng: 10.603
location_city: Campiglia Marittima
location_country: Italy
weather_temp_c: 27
weather_desc: Sunny
---
Seven in the morning and the coast road is still cool. We loaded the bikes in the car park below the old town, the panniers heavier than they should be and the weather forecast saying nine consecutive days of sun. The route heads south first — down into the Maremma, then east, then a long loop back. Eight days. Nobody goes this way in September except cyclists and people who have got lost.
Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

@@ -0,0 +1,15 @@
---
title: 'Maremma in Full Sun'
date: '2026-09-02 11:30'
template: entry
published: true
hero_image: ''
lat: 42.612
lng: 11.171
location_city: Maremma
location_country: Italy
weather_temp_c: 29
weather_desc: Sunny
---
Eleven-thirty and already thirty degrees. The Maremma is agricultural land and scrubland and very little else, and in September it has the quality of a landscape that has given up trying. The road is straight, the sun is direct, the shadows are almost vertical. We stopped at a petrol station and drank two cans of something cold each. The man at the counter looked at us like people who had made a series of questionable decisions.
Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

@@ -0,0 +1,15 @@
---
title: 'The Lagoon at Dusk'
date: '2026-09-02 19:00'
template: entry
published: true
hero_image: ''
lat: 42.442
lng: 11.218
location_city: Orbetello
location_country: Italy
weather_temp_c: 24
weather_desc: Partly cloudy
---
Orbetello sits on a causeway between two lagoons and at dusk the light does something remarkable to the water. Pink flamingos — real ones, not ornamental — were standing in the shallows on the western side, perfectly still. We ate at a table outside overlooking the eastern lagoon. The sky turned orange and then purple and then a deep blue that was almost indistinguishable from the water. The wine was cold and the pasta had clams.
Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

@@ -0,0 +1,15 @@
---
title: 'Orbetello Morning'
date: '2026-09-03 08:00'
template: entry
published: true
hero_image: ''
lat: 42.442
lng: 11.217
location_city: Orbetello
location_country: Italy
weather_temp_c: 22
weather_desc: Sunny
---
The lagoon at eight in the morning is a different thing from the lagoon at eight in the evening. Flat, silver, nearly silent. A single fisherman in a small boat about two hundred metres out, not appearing to fish. We left before the town had properly woken up, heading northeast on roads that climbed immediately and steeply into a landscape of oak and limestone that felt nothing like the coast we had left behind twenty minutes before.
Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

@@ -0,0 +1,15 @@
---
title: 'Tufa and Towers'
date: '2026-09-03 17:00'
template: entry
published: true
hero_image: ''
lat: 42.683
lng: 11.715
location_city: Sorano
location_country: Italy
weather_temp_c: 26
weather_desc: Sunny
---
Sorano appears on the horizon an hour before you reach it: a cluster of towers and walls on a pale cliff, floating above the valley. The closer you get the stranger it becomes. The town is not built on rock — the town is rock, volcanic tufa carved and inhabited over two thousand years. The Etruscans started it. Everyone since has just kept adding floors. We are staying the night and it already feels like somewhere that requires more time than we have.
Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

@@ -0,0 +1,15 @@
---
title: 'The Long Climb North'
date: '2026-09-04 15:00'
template: entry
published: true
hero_image: ''
lat: 43.077
lng: 11.678
location_city: "Val d'Orcia"
location_country: Italy
weather_temp_c: 23
weather_desc: Partly cloudy
---
Today was the hardest day. The route from Sorano to the Val d'Orcia crosses the eastern slope of Monte Amiata, which sounds manageable on a map and is not manageable at all. By noon we had climbed eleven hundred metres. By two we were somewhere above Seggiano in thin cloud, the views long gone, legs complaining in a language that had become very specific. Then the cloud lifted and the Val d'Orcia was simply there below us: pale roads, dark cypress, the whole thing exactly as advertised. Sometimes the landscapes that have been photographed to death are still worth arriving at.
Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

@@ -0,0 +1,15 @@
---
title: 'Before the Heat Arrives'
date: '2026-09-05 08:30'
template: entry
published: true
hero_image: ''
lat: 43.078
lng: 11.676
location_city: Pienza
location_country: Italy
weather_temp_c: 21
weather_desc: Sunny
---
Six o'clock and the valley below Pienza is still in shadow. We left camp early on purpose — the route to Siena is long and September sun waits for no one. On the strade bianche the tyres make a sound like distant applause. No cars for the first two hours. Just the road and the light doing things to the cypress trees that would be embarrassing to describe in any other context.
Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

@@ -0,0 +1,15 @@
---
title: 'Into Siena'
date: '2026-09-05 18:00'
template: entry
published: true
hero_image: ''
lat: 43.318
lng: 11.335
location_city: Siena
location_country: Italy
weather_temp_c: 25
weather_desc: Sunny
---
The approach to Siena by bike is through streets that get progressively older and steeper until suddenly the Campo is there. We had both seen it in photographs and the photographs are accurate in every way except one: they do not tell you how the square smells — stone and frying onions and the particular warm stillness of a Sienese summer evening. We sat on the pavement with our backs against the Palazzo Pubblico for forty minutes and did not want to be anywhere else.
Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

@@ -0,0 +1,15 @@
---
title: 'Florence by Nightfall'
date: '2026-09-06 20:00'
template: entry
published: true
hero_image: ''
lat: 43.767
lng: 11.253
location_city: Florence
location_country: Italy
weather_temp_c: 21
weather_desc: Cloudy
---
A long day. Siena to Florence is ninety kilometres and involves two significant climbs before you reach the Chianti hills, after which it becomes more manageable but you have already used the legs you needed. We came in from the south as the light was going, the city materialising from a distance as a density of rooftops and towers. The Arno appeared between buildings and we crossed it and then we were in, which is always a slightly surprising moment after a long day.
Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

@@ -0,0 +1,15 @@
---
title: 'One Rest Day'
date: '2026-09-07 14:00'
template: entry
published: true
hero_image: ''
lat: 43.769
lng: 11.255
location_city: Florence
location_country: Italy
weather_temp_c: 22
weather_desc: Partly cloudy
---
The bikes stayed in the basement. We walked instead, which after six days of cycling felt simultaneously easier and harder — easier on the legs, harder on the feet, which are used to being passive. Florence does not require a plan. Every street contains something. We crossed the Arno four times from different bridges, each one giving a slightly different version of the same view, all of them good.
Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

@@ -0,0 +1,15 @@
---
title: 'Dawn on the Cecina Coast'
date: '2026-09-08 07:30'
template: entry
published: true
hero_image: ''
lat: 43.553
lng: 10.313
location_city: Cecina
location_country: Italy
weather_temp_c: 20
weather_desc: Sunny
---
The last day starts on the coast road south of Cecina, the sea visible between the pine trees. We have been inland for most of the week and the smell of salt water is a surprise. The road is flat, which after eight days of Tuscan hills feels almost suspicious. We rode in silence for the first hour. There was nothing that needed saying.
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

@@ -0,0 +1,15 @@
---
title: 'Home'
date: '2026-09-08 16:30'
template: entry
published: true
hero_image: ''
lat: 43.017
lng: 10.587
location_city: Campiglia Marittima
location_country: Italy
weather_temp_c: 26
weather_desc: Sunny
---
The old town of Campiglia was visible on its hill for the last twenty kilometres, appearing and disappearing between the trees the way it had appeared on the horizon eight days ago when we left. The loop is complete: same car park, same view across the coast, different legs. The bikes went back in the car and we sat on a wall and counted the countries and the kilometres and the pasta dishes. Eight days, one loop, Tuscany in September. It was exactly what it was supposed to be.
@@ -0,0 +1,11 @@
---
title: Journal
template: dailies
content:
items: '@self.children'
order:
by: date
dir: desc
filter:
published: true
---
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+4
View File
@@ -0,0 +1,4 @@
---
title: 'Trip Map'
template: map
---
+4
View File
@@ -0,0 +1,4 @@
---
title: 'Trip Stats'
template: stats
---
@@ -0,0 +1,5 @@
---
title: Stories
template: stories
published: true
---
+8
View File
@@ -0,0 +1,8 @@
---
title: 'Tuscany 2026'
template: trip
date: '2026-09-01'
date_start: '2026-09-01'
date_end: '2026-09-08'
cover_image: ''
---
+7
View File
@@ -0,0 +1,7 @@
---
title: Into the East
visible: false
routable: true
---
Dispatches from foreign lands and places closer by. A few moments worth remembering, collected here while I'm between trips.
@@ -1,13 +0,0 @@
---
title: 'The Journey Begins'
date: '2026-06-17 10:00'
template: entry
published: true
hero_image: ''
lat: ''
lng: ''
---
First entry. Bags are packed, passport is ready, the adventure starts here.
This is a test entry to confirm the site is working correctly before departure.
@@ -0,0 +1,17 @@
---
title: 'Welcome to My Central Asian Picture Diary'
date: '2023-08-28 07:49'
template: entry
published: true
hero_image: 'photo-1.png'
lat: '52.5200'
lng: '13.4050'
location_city: 'Berlin'
location_country: 'Germany'
weather_temp_c: '24'
weather_desc: 'sunny'
---
Welcome to my picture diary of my 2023 adventure through Central Asia! Get ready for a visual journey through breathtaking landscapes and unforgettable moments! Join me as I explore the winding roads of Kazakhstan, Kyrgyzstan, Tajikistan, and Uzbekistan. Follow along as I share one picture per day, capturing unique experiences and adding stories to bring them to life. Get inspired and come along on this exciting adventure with me!
- Mischa
PS This is an AI generated image of the Kazakh Steppe!
@@ -0,0 +1,18 @@
---
title: 'Last Beer Before the Foreign Land'
date: '2023-08-29 10:29'
template: entry
transport_mode: train
published: true
hero_image: 'photo-1.jpg'
lat: '52.36402'
lng: '13.50745'
location_city: 'Berlin'
location_country: 'Germany'
weather_temp_c: '24'
weather_desc: 'sunny'
---
Today the trip has become a reality, passed through security and waiting at the gate... A strange feeling, the months of anticipation are changing into a reality that so far only has lived in fantasy and bank statements... A last German beer to sooth that anxious feeling of knowing that for the next few months everything will be different!
With a bretzel in my hand, I will fly to the foreign land!
Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

@@ -0,0 +1,16 @@
---
title: 'The UAZ Buchanka Counter Begins'
date: '2023-08-30 18:06'
template: entry
transport_mode: plane
published: true
hero_image: 'photo-1.jpg'
lat: '51.140108'
lng: '71.429747'
location_city: 'Astana'
location_country: 'Kazakhstan'
weather_temp_c: '15'
weather_desc: 'rainy'
---
A simple van, quite boring even if I'd say to myself after all I've seen today. Though this is not just some van, it's a UAZ "Buchanka". I do have a special and weird relationship with these cars, the little UAZ 469 brother as well. Within first 50 meters of my Hostel i saw this one and i couldn't resist capturing it on my camera... so far this is Number 2 i spotted during my trip! I'll keep the counter running...
Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

@@ -0,0 +1,16 @@
---
title: 'Baiterek: Bird of Happiness in Astana'
date: '2023-08-31 16:45'
template: entry
transport_mode: walking
published: true
hero_image: 'photo-1.jpg'
lat: '51.128246'
lng: '71.430466'
location_city: 'Astana'
location_country: 'Kazakhstan'
weather_temp_c: '15'
weather_desc: 'cloudy'
---
After finishing my Beshbarmak I was thinking it would be today's picture. Even though it's Kazakhstans main dish, it doesn't represent my day which was about Kazakhstans history and culture ... So.... Бәйтерек (Baïterek) a monument built to support the moving of the capital from Almaty to Astana. It symbolizes the mythical tree of live in which Samruk, the bird of happiness, has laid it's egg. A new beginning for Kazakhstan after its independence.
Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Some files were not shown because too many files have changed in this diff Show More