Compare commits

...
3 Commits
Author SHA1 Message Date
m038andClaude Opus 5 1d4bf4e929 Fix route name spelling: Vestkustryten -> Vestkystruten
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 23:18:16 +02:00
m038andClaude Opus 5 1b9e51baf7 Turn off travelling so home shows the between-trips state everywhere
travelling was only set to false in user/env/localhost/config/site.yaml,
which Grav reads only when the request hostname is literally "localhost",
and which is git-ignored. So the home page still rendered Denmark 2026 as
an active trip on every other hostname, and the setting could never reach
production. Set it in the tracked config instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 23:12:53 +02:00
m038andClaude Opus 5 e81c73a9ba Fix story scrolly text rendering; story/trip blueprint media upload
Scrolly-section text never rendered: the inline Scrollama block ran before
the main.js bundle published window.scrollama, so it always hit its guard
and the panel text stayed display:none, leaving a blank column beside the
pinned image. Defer to DOMContentLoaded, and build the step panels before
the scrollama check so a missing bundle costs the animation, not the text.

Also fixes an invalid calc(-(...)) that dropped the mobile scrolly offset,
and drops the duplicate inline back-to-top in favour of main.js's version
(which pushes a history entry).

Blueprint changes carried over from an earlier session: story.yaml and
trip.yaml now extend the default page form so both can upload media.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 23:12:45 +02:00
7 changed files with 107 additions and 52 deletions
+1 -1
View File
@@ -6,5 +6,5 @@ metadata:
description: 'Into the East — travel journal'
description: 'A travel blog by Mischa'
active_trip: /trips/denmark-2026
travelling: true
travelling: false
owner_username: mischa
@@ -2,7 +2,7 @@
force_connect: false
published: true
featured: true
title: 'Vestkustryten - a reflection'
title: 'Vestkystruten - a reflection'
date: '29-07-2026 19:51'
end_date: '29-07-2026 19:51'
transport_mode: bicycle
+1 -1
View File
@@ -1,5 +1,5 @@
---
title: 'Vestkustryten - Denmark 2026'
title: 'Vestkystruten - Denmark 2026'
template: trip
date: '2026-07-10'
date_start: '2026-07-10'
+64 -12
View File
@@ -1,5 +1,12 @@
title: 'Story'
# Inherits Grav's default page form — this is what supplies the page-media
# uploader, plus the standard Options and Advanced tabs. Without it the form is
# standalone and has no way to upload images (see entry.yaml, same pattern).
'@extends':
type: default
context: blueprints://pages
form:
fields:
tabs:
@@ -8,6 +15,7 @@ form:
fields:
content:
ordering@: 0
type: tab
title: Content
fields:
@@ -34,7 +42,7 @@ form:
type: text
label: Hero Image
placeholder: 'hero.jpg'
help: 'Filename of the hero image (upload via Media tab)'
help: 'Filename of an image uploaded in the Images field below'
header.hero_alt:
type: text
@@ -44,10 +52,53 @@ form:
content:
type: markdown
label: Content
help: 'Prose renders as a normal reading column. For layout blocks (scrolly sections, galleries, pull quotes) use the shortcodes listed on the Blocks tab.'
validate:
required: true
header.media_order:
type: pagemedia
label: Images
help: 'Upload every image the story needs here first — shortcodes refer to them by bare filename (photo-1.jpg), never by path.'
blocks:
ordering@: 1
type: tab
title: Blocks
fields:
blocks_intro:
type: spacer
text: "<p>Paste these into the <strong>Content</strong> field. Image parameters take a <strong>bare filename</strong> of an image uploaded in the <strong>Images</strong> field on the Content tab. The hero image is not a block \u2014 it has its own field on the Content tab.</p>"
blocks_scrolly:
type: spacer
text: "<p style=\"margin:1.4em 0 0.4em\"><strong>Scrolly section \u2014 text panels scroll over a pinned image</strong></p><pre style=\"white-space:pre-wrap;word-break:break-word;padding:0.6em 0.8em;border-radius:6px;background:rgba(127,127,127,0.12)\">[scrolly-section image=\"hero.jpg\" alt=\"What the image shows\" caption=\"Optional caption\"]\nFirst panel.\n\n---\n\nSecond panel. A --- starts a new panel.\n[/scrolly-section]</pre>"
blocks_pullquote:
type: spacer
text: "<p style=\"margin:1.4em 0 0.4em\"><strong>Pull quote \u2014 large extracted quote, image optional</strong></p><pre style=\"white-space:pre-wrap;word-break:break-word;padding:0.6em 0.8em;border-radius:6px;background:rgba(127,127,127,0.12)\">[pull-quote image=\"photo-1.jpg\" alt=\"What the image shows\"]\nThe quote itself. Markdown works in here.\n[/pull-quote]</pre><p>Drop <em>image</em> and <em>alt</em> for the plain no-image variant.</p>"
blocks_chapter:
type: spacer
text: "<p style=\"margin:1.4em 0 0.4em\"><strong>Chapter break \u2014 section transition over a background image</strong></p><pre style=\"white-space:pre-wrap;word-break:break-word;padding:0.6em 0.8em;border-radius:6px;background:rgba(127,127,127,0.12)\">[chapter-break image=\"photo-1.jpg\" title=\"After Dark\" number=\"II\" alt=\"What the image shows\" /]</pre><p><em>number</em> is optional.</p>"
blocks_gallery:
type: spacer
text: "<p style=\"margin:1.4em 0 0.4em\"><strong>Snap gallery \u2014 swipeable carousel</strong></p><pre style=\"white-space:pre-wrap;word-break:break-word;padding:0.6em 0.8em;border-radius:6px;background:rgba(127,127,127,0.12)\">[snap-gallery images=\"hero.jpg,photo-1.jpg\" captions=\"First caption,Second caption\" alts=\"First alt,Second alt\" /]</pre><p><strong>No commas inside captions.</strong> The three lists are split on commas and zipped by position, so one comma in a caption shifts every caption after it. Use em dashes or semicolons.</p>"
blocks_fullbleed:
type: spacer
text: "<p style=\"margin:1.4em 0 0.4em\"><strong>Full bleed \u2014 single edge-to-edge image as a visual pause</strong></p><pre style=\"white-space:pre-wrap;word-break:break-word;padding:0.6em 0.8em;border-radius:6px;background:rgba(127,127,127,0.12)\">[full-bleed image=\"photo-2.jpg\" alt=\"What the image shows\" caption=\"Optional\" credit=\"Optional\" /]</pre>"
blocks_imagecaption:
type: spacer
text: "<p style=\"margin:1.4em 0 0.4em\"><strong>Image with caption \u2014 photo at a chosen width</strong></p><pre style=\"white-space:pre-wrap;word-break:break-word;padding:0.6em 0.8em;border-radius:6px;background:rgba(127,127,127,0.12)\">[image-caption image=\"photo-2.jpg\" alt=\"What the image shows\" caption=\"Optional\" credit=\"Optional\" width=\"column\" /]</pre><p><em>width</em>: column (default), full, or bleed.</p>"
blocks_gotchas:
type: spacer
text: "<p style=\"margin:1.4em 0 0.4em\"><strong>Two things that bite</strong></p><p>Self-closing tags need the space before <code>/]</code>. And a mistyped shortcode fails silently \u2014 it renders as literal text or vanishes, with no warning. Preview the page after adding each block.</p>"
location:
ordering@: 2
type: tab
title: Location
fields:
@@ -97,21 +148,13 @@ form:
validate:
type: bool
# Published now comes from the inherited Options tab — defining it here
# too would render two toggles bound to the same header key.
publishing:
ordering@: 3
type: tab
title: Publishing
fields:
header.published:
type: toggle
label: Published
highlight: 1
default: 1
options:
1: 'Yes'
0: 'No'
validate:
type: bool
header.featured:
type: toggle
label: Featured highlight
@@ -123,3 +166,12 @@ form:
0: 'No'
validate:
type: bool
# Drop the inherited Date field — Story defines its own required
# "Start Date" on the Content tab, and both bind to header.date.
options:
fields:
publishing:
fields:
header.date:
unset@: true
+20 -14
View File
@@ -1,5 +1,13 @@
title: 'Trip'
# Inherits Grav's default page form — this is what supplies the page-media
# uploader (used for cover images and GPX files), plus the standard Options and
# Advanced tabs. Without it the form is standalone and can only *select*
# existing media, never upload any.
'@extends':
type: default
context: blueprints://pages
form:
fields:
tabs:
@@ -8,6 +16,7 @@ form:
fields:
content:
ordering@: 0
type: tab
title: Content
fields:
@@ -21,7 +30,15 @@ form:
type: markdown
label: Description
# Inherited from the default blueprint; relabelled because this is
# also where GPX files and cover images get uploaded.
header.media_order:
type: pagemedia
label: Images and GPX files
help: 'Upload cover images and .gpx route files here. GPX tracks are auto-detected on the map — no manual linking. The Cover Image field below picks from what is uploaded here.'
trip:
ordering@: 1
type: tab
title: Trip
fields:
@@ -78,17 +95,6 @@ form:
'manual': 'Manual — force connect only'
'intelligent_gpx': 'Intelligent GPX — suppress where route is covered'
publishing:
type: tab
title: Publishing
fields:
header.published:
type: toggle
label: Published
highlight: 1
default: 1
options:
1: 'Yes'
0: 'No'
validate:
type: bool
# No Publishing tab — it held only the Published toggle, which now comes
# from the inherited Options tab. Defining it here too would render two
# toggles bound to the same header key.
+1 -1
View File
@@ -2086,7 +2086,7 @@ body::after {
.scrolly-step:last-child { padding-bottom: 50vh; }
@media (max-width: 768px), (pointer: coarse) {
.scrolly { display: block; }
.scrolly__steps { margin-top: calc(-(100vh - var(--site-header-height))); position: relative; z-index: 1; }
.scrolly__steps { margin-top: calc(-1 * (100vh - var(--site-header-height))); position: relative; z-index: 1; }
.scrolly-step { min-height: 80vh; padding: var(--space-8) var(--space-6); align-items: center; justify-content: center; }
.scrolly-step:last-child { padding-bottom: 50vh; }
}
+19 -22
View File
@@ -132,23 +132,11 @@
update();
})();
/* ── Back to top button ─────────────────────────────────── */
(function () {
var btn = document.getElementById('story-totop');
if (!btn) return;
var threshold = window.innerHeight * 0.8;
var shown = false;
btn.addEventListener('click', function () {
window.scrollTo({ top: 0, behavior: 'smooth' });
});
window.addEventListener('scroll', function () {
var shouldShow = window.scrollY > threshold;
if (shouldShow !== shown) {
shown = shouldShow;
btn.classList.toggle('is-visible', shown);
}
}, { passive: true });
})();
/* ── Back to top button ───────────────────────────────────
Owned by initBackToTop('story-totop') in js/src/main.js, called from its
DOMContentLoaded boot. An inline copy used to live here and double-bound
both the click and scroll handlers; it also lacked the history.pushState
that makes Back return to the reading position. Do not re-add it. */
/* ── ChapterBreak scroll-reveal ─────────────────────────── */
(function () {
@@ -216,11 +204,17 @@
update();
})();
/* ── ScrollySection (Scrollama) ──────────────────────────── */
(function () {
/* ── ScrollySection (Scrollama) ────────────────────────────
Deferred to DOMContentLoaded on purpose: `scrollama` is published by the
js/main.js bundle, which Grav emits via assets.js('bottom') *after* this
inline script (base.html.twig renders block content first). Running inline
would hit the scrollama guard every time, leaving .scrolly__steps-content
hidden and the text column blank. DOMContentLoaded fires only after all
classic scripts have executed, so scrollama is available by then. */
document.addEventListener('DOMContentLoaded', function () {
var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var sections = document.querySelectorAll('.scrolly');
if (!sections.length || typeof scrollama === 'undefined') return;
if (!sections.length) return;
var panOffsets = ['50% 40%', '50% 50%', '50% 60%', '50% 45%', '50% 55%'];
@@ -252,7 +246,10 @@
stepsWrap.appendChild(step);
});
if (reduced) {
/* Step-building above must never be gated on scrollama: the slot content is
display:none in CSS, so bailing before this point loses the text entirely.
Degrade to plain visible panels instead — no pinning, but readable. */
if (reduced || typeof scrollama === 'undefined') {
section.querySelectorAll('.scrolly-step').forEach(function (s) { s.classList.add('is-active'); });
return;
}
@@ -281,6 +278,6 @@
if (d.direction === 'up') d.element.classList.remove('is-active');
});
});
})();
});
</script>
{% endblock %}