227 lines
10 KiB
Markdown
227 lines
10 KiB
Markdown
# 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 |
|