diff --git a/README.md b/README.md index c7f9c87..4a6fe02 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Two git repos: | `tests/` | Playwright suite — see [`docs/reference/testing.md`](docs/reference/testing.md) | | `php/` | Local PHP ini overrides | | `docs/` | All project documentation — start at [`docs/README.md`](docs/README.md) | -| `docs/guides/` | Operational how-tos (posting, GPX, trip switching, setup, deploy cycle) | +| `docs/guides/` | Operational how-tos (posting, story authoring, GPX, trip switching, setup, deploy cycle) | | `docs/reference/` | Stable facts: architecture, design system, testing | | `docs/solutions/` | Write-ups of bugs and workflow traps already hit, with YAML frontmatter (`module`, `tags`, `problem_type`) | | `docs/working/` | Specs, plans, backlog, QA — work in flight | diff --git a/docs/guides/story-authoring.md b/docs/guides/story-authoring.md new file mode 100644 index 0000000..8a65e88 --- /dev/null +++ b/docs/guides/story-authoring.md @@ -0,0 +1,228 @@ +# Writing a Story + +A Story is a long-form, hand-crafted piece with an immersive layout — distinct from an Entry, which is a quick dated post from the road (see [`CONCEPTS.md`](../../CONCEPTS.md)). Stories get a Ken Burns hero, scroll-driven sections, galleries and pull quotes. + +The admin editor gives you a **plain markdown textarea** for the body. There is no block picker — the layout vocabulary is a set of shortcodes you type by hand. This guide is that vocabulary; the story edit form also carries a condensed copy of it on its **Blocks** tab, so you don't need this file open while writing. + +Admin lives at **`/admin`** (the plugin slug is `admin2`, but the route is `/admin`). A story's edit URL looks like `/admin/pages/edit/trips//stories/`. + +Stories live at: + +``` +user/pages/01.trips//04.stories//story.md +``` + +`04.stories/stories.md` is a `routable: false` container — its children are aggregated onto the trip page. There is no standalone `/stories` view; don't create one. + +--- + +## 1. Create the page + +1. Admin → **Pages** → add a page under the trip's **Stories** folder +2. Set page template to **story** — this loads [`user/themes/intotheeast/blueprints/story.yaml`](../../user/themes/intotheeast/blueprints/story.yaml). You get the story-specific **Content / Blocks / Location / Publishing** tabs plus the inherited **Options / Advanced / Security** tabs +3. Fill in Title and Start Date (both required) + +--- + +## 2. Upload the images first + +Upload every image the story needs via the **Images** field at the bottom of the Content tab, before writing the body. + +> There is no separate *Media* tab — the uploader is a field on the Content tab, labelled **Images**. It arrives via inheritance: `story.yaml` declares `'@extends': {type: default, context: blueprints://pages}`, which merges in Grav's default page form. `story.yaml` and `trip.yaml` did not originally extend it, so neither form could upload anything, which is why the demo story images had to be placed on the filesystem. Both now inherit, matching [`entry.yaml`](../../user/themes/intotheeast/blueprints/entry.yaml). `home.yaml` is still standalone, deliberately — the home page has no per-page media. + +Every shortcode refers to images by **bare filename** — the `story-blocks` plugin prefixes the page URL at render time ([`story-blocks.php:22`](../../user/plugins/story-blocks/story-blocks.php)), so you write: + +``` +image="photo-1.jpg" ✅ +image="/images/photo-1.jpg" ❌ don't path it +``` + +The demo stories use a `hero.jpg` / `photo-1.jpg` / `photo-2.jpg` naming convention. Worth copying — it keeps the shortcodes readable. + +--- + +## 3. Frontmatter fields + +All of these come from the form tabs, so you rarely type them by hand. Listed here because the body shortcodes are *not* the whole story — the hero in particular is frontmatter, not a tag. + +| Field | Tab | Notes | +|---|---|---| +| `title` | Content | Required | +| `date` | Content | Required. Start date | +| `end_date` | Content | Optional — leave blank for a single-day story | +| `hero_image` | Content | **The hero. Filename only**, from the Images field. Missing → grey placeholder, story still renders | +| `hero_alt` | Content | Falls back to the title if empty | +| `location_name`, `location_country` | Location | Shown in the hero meta line and the opener | +| `lat`, `lng` | Location | Decimal degrees — places the story marker on the trip map | +| `transport_mode` | Location | walking / bicycle / bus / train / car | +| `force_connect` | Location | Always draw a connector line from the previous marker | +| `published` | Options | Grav's standard toggle, from the inherited form | +| `featured` | Publishing | Show as a homepage highlight when not travelling | + +The Ken Burns pan on the hero is automatic — no parameter for it. + +If you hand-write frontmatter, use `date: '2026-09-03'`. Admin2 saves its own serialization (`29-07-2026 19:51`); both parse fine. + +--- + +## 4. The body: six shortcodes + +Defined in [`user/plugins/story-blocks/shortcodes/`](../../user/plugins/story-blocks/shortcodes/). Plain prose between them renders as a normal reading column — you don't need a shortcode to write paragraphs. + +### Wrapping tags + +**`scrolly-section`** — text panels scroll over a pinned, slowly panning image. The centrepiece block. + +``` +[scrolly-section image="hero.jpg" alt="Description of the image" caption="Optional caption"] +The first panel. Scrolls into view over the image. + +--- + +The second panel. A markdown `---` starts a new panel. +[/scrolly-section] +``` + +Panels are split on the `
` that `---` produces ([`story.html.twig:234`](../../user/themes/intotheeast/templates/story.html.twig)). `caption` is optional. Under `prefers-reduced-motion` all panels render active with no pinning. + +**`pull-quote`** — large extracted quote, optionally over a background image. + +``` +[pull-quote image="photo-1.jpg" alt="Description of the image"] +The quote itself. Markdown works in here. +[/pull-quote] +``` + +Drop `image`/`alt` entirely for the plain no-image variant. + +### Self-closing tags + +Note the ` /]` — these take no content. + +**`chapter-break`** — full-width section transition over a background image. + +``` +[chapter-break image="photo-1.jpg" title="After Dark" number="II" alt="Description" /] +``` + +`number` is optional; the demo stories use roman numerals. + +**`snap-gallery`** — swipeable multi-image carousel with dots. + +``` +[snap-gallery images="hero.jpg,photo-1.jpg" captions="First caption,Second caption" alts="First alt,Second alt" /] +``` + +⚠️ See the comma gotcha below. + +**`full-bleed`** — single image edge-to-edge, as a visual pause. + +``` +[full-bleed image="photo-2.jpg" alt="Description" caption="Optional" credit="Optional" /] +``` + +**`image-caption`** — photo at a chosen width with caption beneath. + +``` +[image-caption image="photo-2.jpg" alt="Description" caption="Optional" credit="Optional" width="column" /] +``` + +`width` accepts `column` (default), `full`, `bleed`. Anything else falls back to `column`. + +`full-bleed` and `image-caption` are implemented but not yet used by any story — the demos only exercise the other four. + +--- + +## Gotchas + +### snap-gallery splits on commas — captions cannot contain them + +`images`, `captions` and `alts` are each split on `,` and zipped by index. **A comma inside a caption shifts every caption after it.** + +This is already live in the demo content. `04.stories/04.florence-without-a-map/story.md` has: + +``` +captions="The Arno at noon — greener than expected, the bridges older than you remember,Via dei Servi: …" +``` + +Two images, but three comma-separated pieces — so slide 1 gets "The Arno at noon — greener than expected", slide 2 gets " the bridges older than you remember", and the Via dei Servi text is silently dropped. + +Use em dashes or semicolons in gallery captions. There is no escaping mechanism. + +### Self-closing tags need the space before `/]` + +`[chapter-break … /]` — not `[chapter-break …/]` or `[chapter-break …]`. + +### A typo'd shortcode fails silently + +An unrecognised tag name or a malformed parameter list renders as literal text or vanishes — no error, no warning. Preview the page after every block; there is no in-editor validation. + +### `---` outside a scrolly-section is just a horizontal rule + +The panel-splitting behaviour only applies *inside* `[scrolly-section]`. + +--- + +## Worked example + +The best reference to copy from is [`user/pages/01.trips/italy-2026-demo/04.stories/01.sorano-rock-and-time/story.md`](../../user/pages/01.trips/italy-2026-demo/04.stories/01.sorano-rock-and-time/story.md) — it combines `scrolly-section`, `chapter-break` and `pull-quote` in one story. + +```markdown +--- +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 +--- + +Opening prose. Renders as a normal reading column. + +[scrolly-section image="hero.jpg" alt="Sorano seen from the approach road" caption="Sorano — tufa cliff town"] +First panel over the pinned image. + +--- + +Second panel. +[/scrolly-section] + +More prose between blocks. + +[chapter-break image="photo-1.jpg" title="After Dark" number="II" alt="Narrow medieval alley at dusk" /] + +[pull-quote image="photo-1.jpg" alt="Stone alley lit by a single lantern"] +A town built on rock, carved from rock, returning slowly to rock. +[/pull-quote] +``` + +--- + +## Publish + +1. Set **Published** on the Publishing tab +2. Optionally set **Featured highlight** to surface it on the homepage between trips +3. Push the content: + +```bash +make content-push +``` + +That commits and pushes `user/` to Gitea, which triggers the production pull. See [`deploy-cycle.md`](deploy-cycle.md). + +--- + +## The Blocks tab + +The story edit form has a **Blocks** tab holding a paste-ready example of each shortcode plus the comma warning. It's built from `type: spacer` fields in `story.yaml`, whose `text` admin2 renders as HTML (confirmed against admin2 2.0.12 — see its CHANGELOG entry for issue #91). + +If you edit those fields, note two things: the `text` values are YAML double-quoted scalars, so HTML attribute quotes must be escaped (`\"`) — and `

` is flattened by admin2's CSS reset, which is why the headings use `` in a styled `

` instead. Long `

` content needs `white-space:pre-wrap`, or it overflows underneath the Page Info sidebar.
+
+---
+
+## Why it's a raw textarea
+
+The admin2 story editor is a plain markdown field by design-so-far, not by limitation. Background and the options considered: [`docs/research/story-editing.md`](../research/story-editing.md). Note that its conclusion that admin2 cannot host a custom editor is **out of date** — admin-next ships a plugin field-component surface (`admin-next/fields/{type}.js` + `onApiBlueprintResolved`) that the installed api 1.0.9 / admin2 2.0.12 support. Nobody has built it yet.