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

10 KiB
Raw Permalink Blame History

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

{% 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
{% 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:

{% 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)

<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)

<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