Files
intotheeast-com/docs/superpowers/specs/2026-06-19-home-and-trip-pages-design.md
T

227 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Home Page & Content Flow Design Spec
**Goal:** Replace the redirect-based home page with a real home page showing the active trip's feed and map side by side, add a proper past-trips archive, enrich the trip page with a sticky sidebar index, and introduce story cards into all feeds.
**Architecture:** Pure Twig + CSS changes on top of the existing Grav stack. The home page is a new Grav page (`00.home/home.md`) with a new `home.html.twig` template. Feeds (home + dailies) are extended to merge journal entries and story entries into one chronological collection, with stories rendered as visually distinct cards. No new plugins, no build pipeline.
**Tech Stack:** Grav CMS (PHP/Twig), Vanilla CSS, Leaflet.js (already loaded in `dailies.html.twig`)
---
## Global Constraints
- All changes in `user/` — commit with `git -C user`
- No new Grav plugins
- No JS framework — all interactivity is vanilla JS
- No build pipeline — CSS shipped as plain files
- Existing token names in `tokens.css` must not change
- Theme directory: `user/themes/intotheeast/`
- Active trip slug: `config.site.active_trip` (set in `user/config/site.yaml`)
- `system.yaml` `home.alias` redirect must be removed — `/` becomes a real page
- `config.site.active_trip` in `site.yaml` must always be set to a trip slug (even between trips, point it at the last trip) — the home page template has no fallback if this value is empty
---
## 1. URL Structure
| URL | Page file | Template |
|---|---|---|
| `/` | `user/pages/00.home/home.md` | `home.html.twig` (new) |
| `/trips` | `user/pages/01.trips/trips.md` | `trips.html.twig` (update existing) |
| `/trips/<slug>/` | existing | `trip.html.twig` (update existing) |
| `/trips/<slug>/dailies` | existing | `dailies.html.twig` (update existing) |
**system.yaml change:** Remove `home: alias: /trips/japan-korea-2026/dailies`. Set `home: alias: /` (or remove the alias entirely so Grav serves the `00.home` page at `/`).
---
## 2. Home Page (`/`)
### Layout
Two-column CSS grid on desktop. Map left (~45%), entry feed right (~55%).
```
┌─────────────────────────────────────────────────────────┐
│ [Trip name] · 31 journal entries · 4 stories │
├────────────────────────┬────────────────────────────────┤
│ │ [story card] │
│ Leaflet map │ [journal card] │
│ (sticky, │ [journal card] │
│ 45% width) │ [story card] │
│ │ ... │
└────────────────────────┴────────────────────────────────┘
```
- Map is `position: sticky; top: 0; height: 100vh`
- Entry feed is scrollable, sorted descending by date
- Feed contains both journal entries and story entries merged (see §5)
### Data
```twig
{% set slug = config.site.active_trip %}
{% set trip = grav.pages.find('/trips/' ~ slug) %}
{% set dailies = grav.pages.find('/trips/' ~ slug ~ '/dailies') %}
{% set stories_page = grav.pages.find('/trips/' ~ slug ~ '/stories') %}
{% set journal_entries = dailies ? dailies.children.published().order('date', 'desc') : [] %}
{% set story_entries = stories_page ? stories_page.children.published() : [] %}
{# merge and sort handled in template — see §5 #}
```
### Map
Reuse the existing Leaflet setup from `dailies.html.twig` (`feed-map`). Markers come from journal entries with `lat`/`lng` in frontmatter. GPX route line loaded from trip page media if present (same pattern as `map.html.twig`). Clicking a marker scrolls to that entry card in the feed (use `data-entry-id` on cards + `scrollIntoView`).
### Mobile
Stack vertically: map on top at `height: 40vh`, feed below. No hamburger needed — simpler than the dedicated map page.
---
## 3. Past Trips Archive (`/trips`)
Update `trips.html.twig`. Show each trip as a card, sorted newest first.
Each card contains:
- Trip title (links to `/trips/<slug>/`)
- Date range: `date_start` `date_end` from trip page frontmatter (show "Ongoing" if no `date_end`)
- Entry count: journal entries + story entries counted separately
```twig
{% set journal_count = grav.pages.find(trip.route ~ '/dailies').children.published()|length %}
{% set story_count = grav.pages.find(trip.route ~ '/stories').children.published()|length %}
```
Display: **31 journal entries · 4 stories**
The active trip appears as the first card. No special treatment needed beyond chronological ordering — it naturally sits at the top.
---
## 4. Trip Page (`/trips/<slug>/`)
Update `trip.html.twig`. Current state: shows title, dates, nav links, 3 recent entries. Target state:
### Header (update existing `.trip-hero`)
```
Japan & Korea 2026
Jun 2026 Aug 2026 · 31 journal entries · 4 stories
```
Add entry counts below the date line (small, secondary text).
### Two-column layout
Add a right sidebar alongside the existing content:
```
┌──────────────────────────────────┬──────────────────────┐
│ [full chronological feed] │ Journal │
│ (centered, existing max-width) │ Jun 19 Kyoto │
│ │ Jun 18 Osaka │
│ │ ... │
│ │ │
│ │ Stories │
│ │ The night train │
│ │ First ramen │
└──────────────────────────────────┴──────────────────────┘
```
- Right sidebar: `position: sticky; top: 1rem`
- Two sections: **Journal** (list of entry titles as jump-links via `#entry-<slug>`) and **Stories** (same)
- Each item in the sidebar is a jump-link to `#entry-<slug>` anchor on the feed card
- Feed comes from `dailies.children` + `stories.children` merged (see §5)
- On mobile: sidebar collapses to hidden (toggle-able or just hidden — defer this decision to implementation)
### Remove the current "Recent entries" section
The right-sidebar index replaces it. The full merged feed is the main content.
---
## 5. Story Cards in Feeds (home + trip page)
Feeds in both `home.html.twig` and `trip.html.twig` show a merged chronological list of journal entries and story entries.
### Merging collections in Twig
Grav doesn't natively merge two page collections and sort them. Use a Twig loop to build a combined array:
```twig
{% set all_items = [] %}
{% for e in journal_entries %}
{% set all_items = all_items|merge([{'type': 'journal', 'page': e, 'date': e.date}]) %}
{% endfor %}
{% for s in story_entries %}
{% set all_items = all_items|merge([{'type': 'story', 'page': s, 'date': s.date}]) %}
{% endfor %}
{# Sort descending by date #}
{% set all_items = all_items|sort((a, b) => a.date < b.date ? 1 : -1) %}
```
### Journal card (existing format, unchanged)
```html
<article class="entry-card" id="entry-{{ item.page.slug }}" data-lat="{{ item.page.header.lat }}" data-lng="{{ item.page.header.lng }}">
<!-- existing card markup -->
</article>
```
Add `id` and `data-lat`/`data-lng` attributes for sidebar jump-links and map sync.
### Story card (new)
```html
<article class="entry-card entry-card--story" id="entry-{{ item.page.slug }}">
<a class="entry-card-inner" href="{{ item.page.url }}">
{% if hero %}
<div class="entry-card-photo entry-card-photo--story">
<img src="{{ hero.cropResize(720, 405).url }}" alt="{{ item.page.title }}" loading="lazy">
</div>
{% endif %}
<div class="entry-card-body">
<span class="story-badge">✦ Story</span>
<h2 class="entry-title">{{ item.page.title }}</h2>
</div>
</a>
</article>
```
**Visual treatment:** `entry-card--story` gets a teal left border (3px, `var(--color-accent)`) and no excerpt text. The `✦ Story` badge is small-caps, accent color.
### Story page (full-screen)
Story pages (`/trips/<slug>/stories/<story-slug>`) use `stories.html.twig` (already exists). That template should:
- Override `{% block nav %}` to render **only** a fixed escape link — not an empty block, not the global nav
- Escape link: `← Back` fixed top-left, links to `page.parent.parent.url` (the trip page)
Implementation of the Snowfall-style scroll-snap interior is **deferred to Milestone 3** — this spec only covers the story card in the feed and the escape link on the story page.
---
## 6. Navigation
Update `base.html.twig` nav. Current: single "Journal" link pointing to active trip dailies. New:
- **Home** → `/`
- **Past Trips** → `/trips`
The per-trip sub-nav (Journal / Map / Stats / Stories) stays on the trip page — it is not in the global nav.
---
## 7. Files Changed
| File | Change |
|---|---|
| `user/pages/00.home/home.md` | **Create** — new home page, `template: home` |
| `user/themes/intotheeast/templates/home.html.twig` | **Create** — side-by-side map + feed |
| `user/themes/intotheeast/templates/trips.html.twig` | **Update** — trip cards with counts |
| `user/themes/intotheeast/templates/trip.html.twig` | **Update** — counts in header, two-column + sidebar |
| `user/themes/intotheeast/templates/dailies.html.twig` | **Update** — merge stories into feed, story cards, add `id`/`data-` attrs |
| `user/themes/intotheeast/templates/stories.html.twig` | **Update** — add escape link, remove global nav |
| `user/themes/intotheeast/templates/partials/base.html.twig` | **Update** — new nav links |
| `user/themes/intotheeast/css/style.css` | **Update** — home layout, story card styles, sidebar styles |
| `user/config/system.yaml` | **Update** — remove `home.alias` redirect |