From e81c73a9ba73c45da471effdcfaffb916329351e Mon Sep 17 00:00:00 2001 From: Mischa Date: Wed, 29 Jul 2026 23:12:45 +0200 Subject: [PATCH] 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) --- themes/intotheeast/blueprints/story.yaml | 76 ++++++++++++++++---- themes/intotheeast/blueprints/trip.yaml | 34 +++++---- themes/intotheeast/css/style.css | 2 +- themes/intotheeast/templates/story.html.twig | 41 +++++------ 4 files changed, 104 insertions(+), 49 deletions(-) diff --git a/themes/intotheeast/blueprints/story.yaml b/themes/intotheeast/blueprints/story.yaml index 5d60bfc..7b3e287 100644 --- a/themes/intotheeast/blueprints/story.yaml +++ b/themes/intotheeast/blueprints/story.yaml @@ -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: "

Paste these into the Content field. Image parameters take a bare filename of an image uploaded in the Images field on the Content tab. The hero image is not a block \u2014 it has its own field on the Content tab.

" + + blocks_scrolly: + type: spacer + text: "

Scrolly section \u2014 text panels scroll over a pinned image

[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]
" + + blocks_pullquote: + type: spacer + text: "

Pull quote \u2014 large extracted quote, image optional

[pull-quote image=\"photo-1.jpg\" alt=\"What the image shows\"]\nThe quote itself. Markdown works in here.\n[/pull-quote]

Drop image and alt for the plain no-image variant.

" + + blocks_chapter: + type: spacer + text: "

Chapter break \u2014 section transition over a background image

[chapter-break image=\"photo-1.jpg\" title=\"After Dark\" number=\"II\" alt=\"What the image shows\" /]

number is optional.

" + + blocks_gallery: + type: spacer + text: "

Snap gallery \u2014 swipeable carousel

[snap-gallery images=\"hero.jpg,photo-1.jpg\" captions=\"First caption,Second caption\" alts=\"First alt,Second alt\" /]

No commas inside captions. 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.

" + + blocks_fullbleed: + type: spacer + text: "

Full bleed \u2014 single edge-to-edge image as a visual pause

[full-bleed image=\"photo-2.jpg\" alt=\"What the image shows\" caption=\"Optional\" credit=\"Optional\" /]
" + + blocks_imagecaption: + type: spacer + text: "

Image with caption \u2014 photo at a chosen width

[image-caption image=\"photo-2.jpg\" alt=\"What the image shows\" caption=\"Optional\" credit=\"Optional\" width=\"column\" /]

width: column (default), full, or bleed.

" + + blocks_gotchas: + type: spacer + text: "

Two things that bite

Self-closing tags need the space before /]. And a mistyped shortcode fails silently \u2014 it renders as literal text or vanishes, with no warning. Preview the page after adding each block.

" 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 diff --git a/themes/intotheeast/blueprints/trip.yaml b/themes/intotheeast/blueprints/trip.yaml index f913581..9ce51b6 100644 --- a/themes/intotheeast/blueprints/trip.yaml +++ b/themes/intotheeast/blueprints/trip.yaml @@ -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. diff --git a/themes/intotheeast/css/style.css b/themes/intotheeast/css/style.css index 180099d..94c3822 100644 --- a/themes/intotheeast/css/style.css +++ b/themes/intotheeast/css/style.css @@ -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; } } diff --git a/themes/intotheeast/templates/story.html.twig b/themes/intotheeast/templates/story.html.twig index 2f27eb3..7d9cc5a 100644 --- a/themes/intotheeast/templates/story.html.twig +++ b/themes/intotheeast/templates/story.html.twig @@ -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'); }); }); -})(); +}); {% endblock %}