Merge branch 'main' into feat/post-location-override
Brings the branch up to user/main, which was ahead on both content and
theme fixes. Notably e17a5dc ("block submit on unfinished photo uploads;
un-squeeze EXIF portraits in lightbox") — the branch point predated it, and
its absence is why UG1, UG2 and LD1 failed here: the upload gate and the
oriented-derivative slide dims those specs assert simply were not on this
branch. Also picks up denmark-2026 `published: true`, so the local testing
flip is no longer needed.
js/src/post-form.js auto-merged cleanly, keeping both main's upload gate and
this branch's location-override panel + lazy maplibre CSS link. The only
conflict was js/post/post-form.js, a generated bundle — resolved by
rebuilding via make build-assets rather than hand-merging minified output.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
After Width: | Height: | Size: 4.4 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
@@ -0,0 +1,29 @@
|
||||
---
|
||||
photos:
|
||||
IMG_1959.jpg:
|
||||
name: IMG_1959.jpg
|
||||
type: image/jpeg
|
||||
size: 4104001
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-10-08-20-the-start/IMG_1959.jpg
|
||||
IMG_1958.jpg:
|
||||
name: IMG_1958.jpg
|
||||
type: image/jpeg
|
||||
size: 4602898
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-10-08-20-the-start/IMG_1958.jpg
|
||||
title: 'The start…. '
|
||||
date: '2026-07-10 08:20'
|
||||
lat: null
|
||||
lng: null
|
||||
location_city: Berlin
|
||||
location_country: Germany
|
||||
weather_desc: Cloudy
|
||||
weather_temp_c: 20
|
||||
transport_mode: train
|
||||
edit_path: null
|
||||
published: true
|
||||
force_connect: false
|
||||
featured: false
|
||||
---
|
||||
|
||||
The start was surprisingly smooth - the real wake-up was the crowd of odd characters hanging around S Gesundbrunnen at 6am. Deutsche Bahn did surprisingly well: just a 10-minute delay and a platform change.
|
||||
The adventure has started. Denmark, here I come!
|
||||
|
After Width: | Height: | Size: 4.2 MiB |
|
After Width: | Height: | Size: 2.5 MiB |
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 4.5 MiB |
|
After Width: | Height: | Size: 4.7 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
@@ -0,0 +1,49 @@
|
||||
---
|
||||
photos:
|
||||
IMG_1972.jpg:
|
||||
name: IMG_1972.jpg
|
||||
type: image/jpeg
|
||||
size: 2638016
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-10-22-34-a-day-full-of-firsts/IMG_1972.jpg
|
||||
IMG_1969.jpg:
|
||||
name: IMG_1969.jpg
|
||||
type: image/jpeg
|
||||
size: 4379241
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-10-22-34-a-day-full-of-firsts/IMG_1969.jpg
|
||||
IMG_1977.jpg:
|
||||
name: IMG_1977.jpg
|
||||
type: image/jpeg
|
||||
size: 3095975
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-10-22-34-a-day-full-of-firsts/IMG_1977.jpg
|
||||
IMG_1983.jpg:
|
||||
name: IMG_1983.jpg
|
||||
type: image/jpeg
|
||||
size: 4693570
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-10-22-34-a-day-full-of-firsts/IMG_1983.jpg
|
||||
IMG_1999.jpg:
|
||||
name: IMG_1999.jpg
|
||||
type: image/jpeg
|
||||
size: 4133238
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-10-22-34-a-day-full-of-firsts/IMG_1999.jpg
|
||||
IMG_1994.jpg:
|
||||
name: IMG_1994.jpg
|
||||
type: image/jpeg
|
||||
size: 4979702
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-10-22-34-a-day-full-of-firsts/IMG_1994.jpg
|
||||
title: 'A day full of firsts'
|
||||
date: '2026-07-10 22:34'
|
||||
lat: '55.394911'
|
||||
lng: '8.712669'
|
||||
location_city: Esbjerg
|
||||
location_country: Denmark
|
||||
weather_desc: Sunny
|
||||
weather_temp_c: 18
|
||||
transport_mode: bicycle
|
||||
edit_path: null
|
||||
published: true
|
||||
force_connect: false
|
||||
featured: false
|
||||
---
|
||||
|
||||
So what did I actually do? I visited the northernmost point of the German mainland, then cycled across a border on an international trip—last year I took the ferry, lame huh? I saw Ribe, the oldest town in Denmark, and tried the Danish shelter system for the first time. It’s awesome.
|
||||
But the most important part: no daily destination. Just a rough idea and a shelter spot to sleep in when I’m tired. No anxiety, no hotel needed—though probably no showers in the upcoming days either. :D
|
||||
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 2.9 MiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 4.7 MiB |
@@ -0,0 +1,24 @@
|
||||
---
|
||||
photos: ''
|
||||
title: 'Internet too slow in Denmark…?'
|
||||
date: '2026-07-11 20:35'
|
||||
lat: '55.682921'
|
||||
lng: '8.183712'
|
||||
location_city: Varde
|
||||
location_country: Denmark
|
||||
weather_desc: Cloudy
|
||||
weather_temp_c: 19
|
||||
transport_mode: bicycle
|
||||
edit_path: /trips/denmark-2026/dailies/2026-07-11-20-35-internet-too-slow-in-denmark/entry.md
|
||||
published: true
|
||||
force_connect: false
|
||||
featured: false
|
||||
---
|
||||
|
||||
Sometimes yes, I’m writing this while lying in my tent: 98km done and I’m knackered. Uploading pictures keeps failing...
|
||||
|
||||
A quick coffee break at a hip specialty coffee place in Esbjerg made the morning complete and injected the fuel for a special detour today l. Namely, to reach a special geographical spot—the most western point of Denmark. My goal is to visit all the corners, and this trip should complete that.
|
||||
|
||||
I’d actually wanted to stop at 70km, since I’d already hit my goal for the day. But a quick food stop got me some fuel - a hot dog and a Starbucks triple espresso - and with a bit of wind at my back, voilà, here we are. Funny enough, some of the same people from last night’s shelter are here too.
|
||||
|
||||
Note to self: that starbucks triple espresso is great fuel, don’t be ashamed.
|
||||
|
After Width: | Height: | Size: 3.8 MiB |
|
After Width: | Height: | Size: 4.2 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 5.2 MiB |
@@ -0,0 +1,43 @@
|
||||
---
|
||||
photos:
|
||||
IMG_2035.jpg:
|
||||
name: IMG_2035.jpg
|
||||
type: image/jpeg
|
||||
size: 4025475
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-12-20-28-fairytale-landscapes-and-the-financial-reality-check/IMG_2035.jpg
|
||||
IMG_2037.jpg:
|
||||
name: IMG_2037.jpg
|
||||
type: image/jpeg
|
||||
size: 4355388
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-12-20-28-fairytale-landscapes-and-the-financial-reality-check/IMG_2037.jpg
|
||||
IMG_2041.jpg:
|
||||
name: IMG_2041.jpg
|
||||
type: image/jpeg
|
||||
size: 3848089
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-12-20-28-fairytale-landscapes-and-the-financial-reality-check/IMG_2041.jpg
|
||||
IMG_2042.jpg:
|
||||
name: IMG_2042.jpg
|
||||
type: image/jpeg
|
||||
size: 5405714
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-12-20-28-fairytale-landscapes-and-the-financial-reality-check/IMG_2042.jpg
|
||||
title: 'Fairytale landscapes and the financial reality check'
|
||||
date: '2026-07-12 20:28'
|
||||
lat: '56.143163'
|
||||
lng: '8.176442'
|
||||
location_city: 'Ringkobing-Skjern Kommune'
|
||||
location_country: Denmark
|
||||
weather_desc: Sunny
|
||||
weather_temp_c: 24
|
||||
transport_mode: bicycle
|
||||
edit_path: null
|
||||
published: true
|
||||
force_connect: false
|
||||
featured: false
|
||||
---
|
||||
|
||||
A day through the dunes: more offroad gravel than the gorgeous paved cycling paths Denmark is famous for - those are saved for Route 475. Route 1 is hardcore. Well, okay, it’s not all that crazy, but I was happy with my new 45mm tires. Smooth riding, and enough grip.
|
||||
|
||||
The route went through a kind of dunish desert, all grass and sand. Scattered all over were little houses - holiday homes for the Danes, I assume. Even though some of them
|
||||
looked legit.
|
||||
|
||||
The addiction to gentrified coffee was too great, so I stopped at a hip place in a little surf town. I’m glad my in-head currency conversion isn’t very fast: €13.60 for a coffee and a cake. Denmark makes you feel poor - which makes the tent feel quite fitting.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
photos: ''
|
||||
title: 'Cold Hawaii…'
|
||||
date: '2026-07-14 22:16'
|
||||
lat: '57.033053'
|
||||
lng: '8.479737'
|
||||
location_city: Klittmøller
|
||||
location_country: Denmark
|
||||
weather_desc: Sunny
|
||||
weather_temp_c: 22
|
||||
transport_mode: bicycle
|
||||
edit_path: /trips/denmark-2026/dailies/2026-07-14-22-16-cold-hawaii/entry.md
|
||||
published: true
|
||||
force_connect: false
|
||||
featured: false
|
||||
---
|
||||
|
||||
Today was a mandatory take-it-easy day. I’ve reached 350km of the 620km in four days — pretty powerful, maybe also too much. I’ve noticed that when you’re on the bike, you’re in bike mode: I take a picture here and there, but you can’t stop every 10km either. Too many breaks can be devastating.
|
||||
So I arrived in Klitmøller, aka Cold Hawaii, because — guess what — you can surf here. I assumed it would just be downwind stuff, but apparently there can be some decent waves. That’s the vibe of this town, same as Hvide Sande: laid back, calm, and lots of people with surfboard carriers on their bikes. Danes on bikes can be as impressive as the Dutch — we’ve found our counterpart!
|
||||
With just 38km today I planned a relaxing afternoon: laundry in a proper machine, a warm shower (first since I left home), and some bike maintenance and cleaning. But… my anti-mosquito spray had a surprise for me. All the shaking had worked the cap loose, and the DEET leaked slowly into a plastic bag — which it promptly dissolved, leaving gooey, sticky plastic all over my stuff. A challenge too great for a relaxing afternoon. I did minimal damage control, then had dinner and a beer with a view of the sea.
|
||||
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.8 MiB |
|
After Width: | Height: | Size: 4.3 MiB |
|
After Width: | Height: | Size: 696 KiB |
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 3.4 MiB |
|
After Width: | Height: | Size: 4.5 MiB |
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 4.1 MiB |
|
After Width: | Height: | Size: 2.7 MiB |
@@ -0,0 +1,51 @@
|
||||
---
|
||||
photos:
|
||||
IMG_2123.jpg:
|
||||
name: IMG_2123.jpg
|
||||
type: image/jpeg
|
||||
size: 2867530
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-15-20-35-a-fresh-feeling/IMG_2123.jpg
|
||||
IMG_2098.jpg:
|
||||
name: IMG_2098.jpg
|
||||
type: image/jpeg
|
||||
size: 3141614
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-15-20-35-a-fresh-feeling/IMG_2098.jpg
|
||||
IMG_2099.jpg:
|
||||
name: IMG_2099.jpg
|
||||
type: image/jpeg
|
||||
size: 3609643
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-15-20-35-a-fresh-feeling/IMG_2099.jpg
|
||||
IMG_2103.jpg:
|
||||
name: IMG_2103.jpg
|
||||
type: image/jpeg
|
||||
size: 4732533
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-15-20-35-a-fresh-feeling/IMG_2103.jpg
|
||||
IMG_2107.jpg:
|
||||
name: IMG_2107.jpg
|
||||
type: image/jpeg
|
||||
size: 3166448
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-15-20-35-a-fresh-feeling/IMG_2107.jpg
|
||||
IMG_2108.jpg:
|
||||
name: IMG_2108.jpg
|
||||
type: image/jpeg
|
||||
size: 4289987
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-15-20-35-a-fresh-feeling/IMG_2108.jpg
|
||||
title: 'A fresh feeling'
|
||||
date: '2026-07-15 20:35'
|
||||
lat: '57.136715'
|
||||
lng: '9.327441'
|
||||
location_city: Abybro
|
||||
location_country: Denmark
|
||||
weather_desc: Sunny
|
||||
weather_temp_c: 22
|
||||
transport_mode: bicycle
|
||||
edit_path: null
|
||||
published: true
|
||||
force_connect: false
|
||||
featured: false
|
||||
---
|
||||
|
||||
Do you think showering twice in one day counts as pre-showering for the days ahead? I’ll let you know in a couple of days. But the morning started clean — freshness and mist in the air, and my body fully recovered after being pushed hard. Another 80km, which leaves about 140km to go.
|
||||
Goal change: finish the route early and carry on down the east coast. My train back leaves from Aalborg (not the far north anyway), and I don’t mind more of Denmark.
|
||||
The legs felt rested and just kept plowing through. I glanced at the navigation and 50km was already done — I’d even cycled past Bulbjerg. Too far to turn back, alas. I powered up with another triple-shot canned espresso (I feel genuinely bad for liking this stuff as a coffee aficionado, but it works), and before I knew it I was at my home for the night.
|
||||
Then: my first dinner ever cooked on my little camp stove. Tonight’s menu — first course, pan-fried tomatoes with herbes de Provence and smoky salt: honestly delish, well worth bringing back for a dinner party at home. Main was a tuna pasta with herbs and smoky salt — that one was fine. And for dessert, gas-grilled marshmallows: my poor, not-so-outdoorsy, lazy man’s version of a campfire roast. Three courses on one tiny burner. Now I’m satisfied and heading to bed to charge up for tomorrow.
|
||||
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 5.6 MiB |
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 2.9 MiB |
@@ -0,0 +1,46 @@
|
||||
---
|
||||
photos:
|
||||
IMG_2153.jpg:
|
||||
name: IMG_2153.jpg
|
||||
type: image/jpeg
|
||||
size: 2723140
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-17-15-30-the-lies-the-north-and-the-reward/IMG_2153.jpg
|
||||
IMG_2155.jpg:
|
||||
name: IMG_2155.jpg
|
||||
type: image/jpeg
|
||||
size: 3159543
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-17-15-30-the-lies-the-north-and-the-reward/IMG_2155.jpg
|
||||
IMG_2159.jpg:
|
||||
name: IMG_2159.jpg
|
||||
type: image/jpeg
|
||||
size: 3047815
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-17-15-30-the-lies-the-north-and-the-reward/IMG_2159.jpg
|
||||
IMG_2157.jpg:
|
||||
name: IMG_2157.jpg
|
||||
type: image/jpeg
|
||||
size: 5889404
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-17-15-30-the-lies-the-north-and-the-reward/IMG_2157.jpg
|
||||
IMG_2158.jpg:
|
||||
name: IMG_2158.jpg
|
||||
type: image/jpeg
|
||||
size: 2970861
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-17-15-30-the-lies-the-north-and-the-reward/IMG_2158.jpg
|
||||
title: 'The lies, the north and the reward '
|
||||
date: '2026-07-17 15:30'
|
||||
lat: '57.725018'
|
||||
lng: '10.596427'
|
||||
location_city: Skagen
|
||||
location_country: Denmark
|
||||
weather_desc: 'Partly cloudy'
|
||||
weather_temp_c: 26
|
||||
transport_mode: bicycle
|
||||
edit_path: null
|
||||
published: true
|
||||
force_connect: false
|
||||
featured: false
|
||||
---
|
||||
|
||||
Time flies when you’ve got a routine and you’re just riding. Yesterday’s post inspiration got lost at the Stranden shelter — two minutes from the beach, zero connectivity. Today was all about the goal. Must… reach… the north….
|
||||
Yesterday I was pushing hard, telling myself each extra kilometer meant one less tomorrow. Then reality hit. My trusty Wahoo — which has navigated me without a single hiccup (knock on wood) — wasn’t showing the true countdown. Turns out I had an extra 11km to reach the top. Whatever that means, Mr Wahoo.
|
||||
Once I could smell success in the air, another painful decision: push my bike 1km through the sand, or leave it behind? I’ll say nothing and let the pictures speak for themselves.
|
||||
Now, a well-earned reward — an iskaffe and a lille citrontærte — and I start all over again? Not really. I’ll continue down Route 1, then branch off onto number 5, the east coast route. Two days left before I have to catch my train, and cycling is cheaper than hovering around a Danish town for fun. Onwards to Aalborg!
|
||||
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 2.5 MiB |
|
After Width: | Height: | Size: 5.8 MiB |
@@ -0,0 +1,40 @@
|
||||
---
|
||||
photos:
|
||||
IMG_2168.jpg:
|
||||
name: IMG_2168.jpg
|
||||
type: image/jpeg
|
||||
size: 2125806
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-18-09-08-the-dunes-of-jerup/IMG_2168.jpg
|
||||
IMG_2173.jpg:
|
||||
name: IMG_2173.jpg
|
||||
type: image/jpeg
|
||||
size: 3497959
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-18-09-08-the-dunes-of-jerup/IMG_2173.jpg
|
||||
IMG_2175.jpg:
|
||||
name: IMG_2175.jpg
|
||||
type: image/jpeg
|
||||
size: 2672034
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-18-09-08-the-dunes-of-jerup/IMG_2175.jpg
|
||||
IMG_2178.jpg:
|
||||
name: IMG_2178.jpg
|
||||
type: image/jpeg
|
||||
size: 6060397
|
||||
path: user/pages/01.trips/denmark-2026/01.dailies/2026-07-18-09-08-the-dunes-of-jerup/IMG_2178.jpg
|
||||
title: 'The dunes of Jerup'
|
||||
date: '2026-07-18 09:08'
|
||||
lat: 57.54421
|
||||
lng: 10.43837
|
||||
location_city: Jerup
|
||||
location_country: Denmark
|
||||
weather_desc: Cloudy
|
||||
weather_temp_c: 18
|
||||
transport_mode: bicycle
|
||||
published: true
|
||||
force_connect: false
|
||||
featured: false
|
||||
---
|
||||
|
||||
After my reward I continued the route on the east coast, and while looking for a shelter I stumbled upon Jerup Shelterplads — the closest one to the beach I’d seen on the whole trip. The east coast probably isn’t as wild as the west. Hidden in the dunes, I saw the wooden structures pop up, the outlines of half a dozen camper vans, and the lively sound of people chatting and children playing. No silent retreat in nature this night, and I was happy about it. The weather looked slightly dramatic — rain and lots of lightning — though fortunately, as is common in Denmark, it settled into light showers and some thunder around 02:00.
|
||||
My neighbours were a jolly bunch of Danish-Indonesians, delighted to learn more about Indonesian people living in the Netherlands. They were from Aceh, practically the opposite end of the country from where most Indonesian-Dutch families come from: the Maluku islands. One of them also knew about the Javanese diaspora in Suriname, and we had a little conversation about that too. Unfortunately their limited English and my practically non-existent Bahasa Indonesia wouldn’t let us go further into such an interesting subject.
|
||||
Jerup beach was my favourite shelter so far — each tent could hide in its own little spot in the dunes, barely 20 meters from the water. A last coffee on the beach, and the forecast looked very rainy. It started as I was packing up the last bits of the tent.
|
||||
From Tolne I took the train to Aalborg, where I’ll slowly assimilate back into the urban setting after eight days away from it.
|
||||
@@ -5,8 +5,8 @@ date: '2026-07-10'
|
||||
date_start: '2026-07-10'
|
||||
date_end: '2026-07-20'
|
||||
cover_image: bikepacking_denmark.jpeg
|
||||
tagline: '560 km up Denmark''s wild North Sea coast to where two seas meet'
|
||||
published: false
|
||||
tagline: "560 km up Denmark's wild North Sea coast to where two seas meet"
|
||||
published: true
|
||||
autoconnect: intelligent_gpx
|
||||
---
|
||||
|
||||
|
||||
@@ -791,8 +791,65 @@ body::after {
|
||||
|
||||
.feed-map-link:hover { color: var(--color-accent-hover); }
|
||||
|
||||
/* ── Feedback / notification messages ────────────────────────────────────────── */
|
||||
|
||||
/* Grav renders feedback two ways and the theme styled neither globally:
|
||||
- Form/flash messages as <div class="notices success|error|warning green|red|yellow">
|
||||
(Form plugin form-messages.html.twig; also markdown-notices output).
|
||||
- Plain flash messages as #messages .toast .toast-error|success|warning (quark
|
||||
partials/messages.html.twig, resolved since this theme has none of its own).
|
||||
Without these rules they fell back to light admonition boxes that clash with the
|
||||
dark theme (first noticed on /login). Kept on-theme with a status-coloured
|
||||
left border. */
|
||||
.notices,
|
||||
#messages .toast {
|
||||
padding: 1rem 1.1rem;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: var(--text-base);
|
||||
margin: 0 0 var(--space-5);
|
||||
background: var(--color-canvas);
|
||||
color: var(--color-ink);
|
||||
border: 1px solid var(--color-border);
|
||||
border-left: 4px solid var(--color-accent);
|
||||
}
|
||||
.notices p,
|
||||
#messages .toast p { margin: 0; }
|
||||
|
||||
/* markdown-notices sets bright per-colour backgrounds on .notices.red / .yellow /
|
||||
.green / .blue at (0,2,0). Override each variant at equal specificity (this
|
||||
sheet loads later) so the dark canvas + cream text always win — only the
|
||||
left-border colour signals status. */
|
||||
.notices.error, .notices.red,
|
||||
.notices.success, .notices.green,
|
||||
.notices.warning, .notices.yellow,
|
||||
.notices.blue,
|
||||
#messages .toast-error,
|
||||
#messages .toast-success,
|
||||
#messages .toast-warning {
|
||||
background: var(--color-canvas);
|
||||
color: var(--color-ink);
|
||||
border-left-width: 4px;
|
||||
border-left-style: solid;
|
||||
}
|
||||
.notices.error, .notices.red,
|
||||
#messages .toast-error { border-left-color: var(--color-error); }
|
||||
.notices.success, .notices.green,
|
||||
#messages .toast-success { border-left-color: var(--color-accent); }
|
||||
.notices.warning, .notices.yellow,
|
||||
#messages .toast-warning { border-left-color: #c98a2e; }
|
||||
.notices.blue { border-left-color: var(--color-accent); }
|
||||
|
||||
/* ── Login form ─────────────────────────────────────────────────────────────── */
|
||||
|
||||
/* Neutralise the Login plugin's built-in white box (#grav-login in
|
||||
plugin://login/css/login.css) so the wrapper matches the dark theme. */
|
||||
#grav-login {
|
||||
background: var(--color-canvas);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.login-form { max-width: 400px; margin: var(--space-8) auto; padding: 0 var(--space-4); }
|
||||
.login-form .form-field { margin-bottom: var(--space-5); }
|
||||
.login-form .form-label label { display: block; font-size: var(--text-sm); font-weight: 600; margin-bottom: var(--space-2); }
|
||||
|
||||
@@ -243,6 +243,38 @@ function initPhotoConversion() {
|
||||
}
|
||||
}, true);
|
||||
|
||||
// Complete upload gate (BUG 2026-07-09: fast create submit lost the photo).
|
||||
// The form plugin's own submit guard (filepond-handler.js) only blocks the
|
||||
// PROCESSING / PROCESSING_QUEUED states. A file still being read (LOADING —
|
||||
// the "too quick" click) or one whose upload FAILED (PROCESSING_ERROR)
|
||||
// slips through it and the entry is saved without the photo — silently,
|
||||
// because an errored thumbnail still satisfies the ≥1-photo count. Block
|
||||
// submit unless every FilePond item reached PROCESSING_COMPLETE, and say
|
||||
// which case blocked it.
|
||||
form.addEventListener('submit', function (e) {
|
||||
if (!orderPond) return;
|
||||
var files = orderPond.getFiles();
|
||||
if (!files.length) return; // the ≥1-photo rule is initValidation's job
|
||||
var statuses = (window.FilePond && window.FilePond.FileStatus) || {};
|
||||
var pending = 0;
|
||||
var failed = 0;
|
||||
files.forEach(function (f) {
|
||||
if (f.status === statuses.PROCESSING_COMPLETE) return;
|
||||
if (f.status === statuses.PROCESSING_ERROR || f.status === statuses.LOAD_ERROR) failed++;
|
||||
else pending++; // LOADING, INIT, IDLE, QUEUED, PROCESSING, …
|
||||
});
|
||||
if (!pending && !failed) return;
|
||||
e.preventDefault();
|
||||
if (collapse) collapse.details.open = true; // reveal the item states
|
||||
setStatus(failed
|
||||
? 'A photo failed to upload — remove it (tap its ✕) and re-add it before posting.'
|
||||
: 'Photos are still uploading — hang on a moment.', 'err');
|
||||
var el = photoStatusEl();
|
||||
if (el && typeof el.scrollIntoView === 'function') {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
}, true);
|
||||
|
||||
// FilePond hook: reject a HEIC item, convert it to JPEG via the lazy heic-to
|
||||
// chunk (KTD4), then re-add the JPEG through pond.addFile() so FilePond
|
||||
// uploads and page-attaches it via its own (correct) contract.
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
{% if grav.user.authenticated %}
|
||||
<a href="{{ base_url_absolute }}/post"{% if page.template == 'post-form' %} aria-current="page"{% endif %}>New Post</a>
|
||||
<a href="{{ base_url_absolute }}/gpx-manager"{% if page.template == 'gpx-manager' %} aria-current="page"{% endif %}>GPX Manager</a>
|
||||
<a href="{{ url(uri.addNonce((uri.path)|trim('/') ~ '/task' ~ config.system.param_sep ~ 'login.logout', 'logout-form', 'logout-nonce'))|e }}">Logout</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
{% endblock %}
|
||||
|
||||
@@ -38,15 +38,32 @@
|
||||
|
||||
{% set images = entry.media.images %}
|
||||
{% if images|length > 0 %}
|
||||
{% set firstImg = images|first %}
|
||||
{% set wrapRatio = firstImg.height > firstImg.width ? '4 / 5' : '4 / 3' %}
|
||||
{#
|
||||
Lightbox slides link to a 2000px fit-within DERIVATIVE, and the
|
||||
data-pswp-* dims are measured from that derivative's saved file — never
|
||||
from `img.width`/`img.height`, which are the ORIGINAL's raw stored pixels
|
||||
with EXIF orientation ignored. Originals from iPhones are often stored
|
||||
rotated (landscape pixels + EXIF "rotate 90°"), so raw dims disagree with
|
||||
what the browser renders and PhotoSwipe squeezes portraits into landscape
|
||||
boxes (BUG 2026-07-09, covered by tests/ui/post/lightbox-dims.spec.js).
|
||||
The derivative is re-encoded (EXIF stripped, orientation baked in when
|
||||
php-exif is available), so its measured dims always match its rendering.
|
||||
Note: .path()/.url each finalize+reset the medium's operation queue,
|
||||
hence the repeated cropResize() calls — the derivative file itself is
|
||||
cached. path() needs explicit parens: Medium is ArrayAccess and carries a
|
||||
'path' ITEM (the page folder), which shadows the method in Twig's
|
||||
attribute resolution and hands getimagesize a directory.
|
||||
#}
|
||||
{% set firstDims = (images|first).cropResize(2000, 2000).path()|getimagesize %}
|
||||
{% set wrapRatio = firstDims[1] > firstDims[0] ? '4 / 5' : '4 / 3' %}
|
||||
<div class="journal-photo-wrap" style="aspect-ratio: {{ wrapRatio }}">
|
||||
<div class="journal-photo-strip pswp-gallery" id="gallery-{{ entry.slug }}" data-slides="{{ images|length }}">
|
||||
{% for img in images %}
|
||||
{% set slideDims = img.cropResize(2000, 2000).path()|getimagesize %}
|
||||
<a class="journal-photo-slide"
|
||||
href="{{ img.url }}"
|
||||
data-pswp-width="{{ img.width }}"
|
||||
data-pswp-height="{{ img.height }}"
|
||||
href="{{ img.cropResize(2000, 2000).url }}"
|
||||
data-pswp-width="{{ slideDims[0] }}"
|
||||
data-pswp-height="{{ slideDims[1] }}"
|
||||
style="--thumb: url('{{ img.cropResize(900, 675).url }}')"
|
||||
target="_blank">
|
||||
<img src="{{ img.cropResize(900, 675).url }}" alt="{{ entry.title }}" loading="lazy">
|
||||
|
||||
@@ -23,12 +23,18 @@
|
||||
#}
|
||||
<div class="home-map-col">
|
||||
<div class="home-map" id="{{ map_id }}">
|
||||
{# Same gate as the init script below: without entries the map never
|
||||
initialises and nothing wires the button's click handler, so on
|
||||
mobile (the only viewport where it shows) it would float as a dead
|
||||
control over an empty map area (home pre-departure state). #}
|
||||
{% if entries|length > 0 %}
|
||||
<button class="feed-map-fullscreen-btn" id="{{ map_id }}-fullscreen" aria-label="Expand map">
|
||||
<svg class="feed-map-fs-open" aria-hidden="true" width="14" height="14" viewBox="0 0 14 14" fill="currentColor">
|
||||
<path d="M0 0v4h1.5V1.5H4V0z M14 0H10v1.5h2.5V4H14z M0 14v-4h1.5v2.5H4V14z M14 14H10v-1.5h2.5V10H14z"/>
|
||||
</svg>
|
||||
<span class="feed-map-fs-close" aria-hidden="true">✕</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||