diff --git a/docs/superpowers/plans/2026-06-21-homepage-redesign.md b/docs/superpowers/plans/2026-06-21-homepage-redesign.md new file mode 100644 index 0000000..b440dd1 --- /dev/null +++ b/docs/superpowers/plans/2026-06-21-homepage-redesign.md @@ -0,0 +1,942 @@ +# Homepage Redesign Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Context-aware homepage with a persistent two-column map+feed layout: active-trip mode shows the live feed + GPX on the home map; between-trips mode shows a curated highlights grid from all trips with markers-only on the map. + +**Architecture:** Single `home.html.twig` with a `{% if config.site.travelling %}` branch. Active trip branch keeps the existing feed and adds GPX loading to the home map. Between-trips branch selects one random `featured:true` entry per trip (max 6), renders a highlight card grid, and passes coordinates to the map (no GPX, no journey line). Three blueprint files expose new data fields. A site-config blueprint exposes the mode switch and active-trip selector in Admin2. + +**Tech Stack:** Grav CMS 2.0 (PHP/Twig), MapLibre GL v4, toGeoJSON CDN, Playwright (Node.js) + +## Global Constraints + +- All `user/` file changes committed via `git -C user` from the project root (or `git` from within `user/`) +- Test files in `tests/ui/` and `scripts/` committed via plain `git` from the project root +- No new Grav plugins +- No JS build pipeline — plain CSS and vanilla JS only +- `config.site.active_trip` stores a **full page route**: `/trips/italy-2026-demo` (not a bare slug) +- `config.site.travelling` is `true` for active-trip mode, `false` for between-trips mode +- `entry.header.featured` and `story.header.featured` (bool) gate highlight eligibility — no type-based auto-include; both stories and journal entries use the same flag +- `trip.header.tagline` (string) is the trip description shown on highlight cards +- Dev server at `http://localhost:8081` must be running (`make start`) for all Playwright tests +- Demo data must be loaded (`make demo-load`) before running Playwright tests +- Playwright test IDs continue sequentially: next map test is M8; next home tests are H2–H5 + +--- + +### Task 1: Blueprints, config, and demo seed data + +**Files:** +- Create: `user/blueprints/config/site.yaml` +- Modify: `user/themes/intotheeast/blueprints/trip.yaml` — add `tagline` field in Trip tab +- Modify: `user/themes/intotheeast/blueprints/entry.yaml` — add `featured` toggle in Entry tab +- Modify: `user/themes/intotheeast/blueprints/story.yaml` — add `featured` toggle in Publishing tab +- Modify: `user/config/site.yaml` — change `active_trip` to full route, add `travelling: true` +- Modify: `user/pages/01.trips/italy-2026-demo/04.stories/val-dorcia-at-dawn/story.md` — add `featured: true` +- Modify: first journal entry under `user/pages/01.trips/italy-2026-demo/01.dailies/` — add `featured: true` +- Modify: matching demo source files in `user/docs/demo/trips/italy-2026-demo/` — mirror the `featured: true` additions + +**Interfaces:** +- Produces: `config.site.travelling` (bool) — read in Twig as `config.site.travelling` +- Produces: `config.site.active_trip` (string, full route) — used in Task 2 path lookups +- Produces: `entry.header.featured` / `story.header.featured` (bool) — used in Task 3 selection logic +- Produces: `trip.header.tagline` (string) — used in Task 3 card rendering + +- [ ] **Step 1: Record baseline test result** + +```bash +make test +``` + +Expected: 13 passed, 1 failed (`parent set to /trips/japan-korea-2026/dailies` — pre-existing). Record this so you can verify nothing changed after your edits. + +- [ ] **Step 2: Create `user/blueprints/config/site.yaml`** + +```yaml +form: + validation: loose + fields: + active_trip: + type: pages + label: Active Trip + start_route: '/trips' + show_root: false + show_slug: true + + travelling: + type: toggle + label: Currently Travelling + highlight: 1 + default: false + options: + 1: 'Yes' + 0: 'No' + validate: + type: bool +``` + +Note: `type: pages` is confirmed present in Admin2's JS bundle but untested in a site config blueprint. If it fails to render in Admin2, fall back to `type: select` with explicit `options:` entries — one per trip slug — and no other code changes are needed. + +- [ ] **Step 3: Add `tagline` to `user/themes/intotheeast/blueprints/trip.yaml`** + +In the `trip` tab's `fields` block, after `header.album_url`, add: + +```yaml + header.tagline: + type: text + label: Tagline + placeholder: '6 weeks from Venice to Sicily by train' + help: 'Short description shown on homepage highlight cards' +``` + +- [ ] **Step 4: Add `featured` toggle to `user/themes/intotheeast/blueprints/entry.yaml`** + +In the `entry` tab's `fields` block, after `header.force_connect`, add: + +```yaml + header.featured: + type: toggle + label: Featured highlight + help: 'Show as a homepage highlight when not travelling' + highlight: 1 + default: 0 + options: + 1: 'Yes' + 0: 'No' + validate: + type: bool +``` + +- [ ] **Step 5: Add `featured` toggle to `user/themes/intotheeast/blueprints/story.yaml`** + +In the `publishing` tab's `fields` block, after `header.published`, add: + +```yaml + header.featured: + type: toggle + label: Featured highlight + help: 'Show as a homepage highlight when not travelling' + highlight: 1 + default: 0 + options: + 1: 'Yes' + 0: 'No' + validate: + type: bool +``` + +- [ ] **Step 6: Update `user/config/site.yaml`** + +Replace the file contents with: + +```yaml +title: 'Into the East' +description: 'A travel blog by Mischa' +author: + name: Mischa + email: mischa@gorinskat.nl +taxonomies: [category, tag] +metadata: + description: 'Into the East — travel journal' +active_trip: /trips/italy-2026-demo +travelling: true +``` + +- [ ] **Step 7: Mark the demo story as featured** + +Open `user/pages/01.trips/italy-2026-demo/04.stories/val-dorcia-at-dawn/story.md` and add `featured: true` to its YAML frontmatter block. For example, if the existing frontmatter ends with `published: true`, add the line after it: + +```yaml +featured: true +``` + +- [ ] **Step 8: Mark one demo journal entry as featured** + +Find the first entry folder: + +```bash +ls user/pages/01.trips/italy-2026-demo/01.dailies/ | head -1 +``` + +Open `user/pages/01.trips/italy-2026-demo/01.dailies//entry.md` and add `featured: true` to its YAML frontmatter. Ensure the entry has `lat` and `lng` set — if the first entry doesn't, pick the first one that does (check with `grep -l "^lat:" user/pages/01.trips/italy-2026-demo/01.dailies/*/entry.md | head -1`). + +- [ ] **Step 9: Mirror featured flags to demo source** + +The demo source lives in `user/docs/demo/trips/italy-2026-demo/`. Apply the same `featured: true` additions to: +- `user/docs/demo/trips/italy-2026-demo/stories/val-dorcia-at-dawn/story.md` +- The matching journal entry in `user/docs/demo/trips/italy-2026-demo/dailies//entry.md` + +This ensures featured flags survive `make demo-reset`. + +- [ ] **Step 10: Verify tests unchanged** + +```bash +make test +``` + +Expected: identical to Step 1 (13 passed, 1 pre-existing failure). These are YAML-only changes — no template or script changed. + +- [ ] **Step 11: Commit** + +```bash +git -C user add blueprints/config/site.yaml \ + themes/intotheeast/blueprints/trip.yaml \ + themes/intotheeast/blueprints/entry.yaml \ + themes/intotheeast/blueprints/story.yaml \ + config/site.yaml \ + pages/01.trips/italy-2026-demo/04.stories/val-dorcia-at-dawn/story.md \ + docs/demo/trips/italy-2026-demo/stories/val-dorcia-at-dawn/story.md +git -C user commit -m "feat: add blueprints for active_trip/travelling config, tagline, featured fields" +``` + +Then commit the journal entry (substitute the actual slug discovered in Step 8): + +```bash +git -C user add pages/01.trips/italy-2026-demo/01.dailies//entry.md \ + docs/demo/trips/italy-2026-demo/dailies//entry.md +git -C user commit -m "chore: mark demo entries as featured for homepage highlight testing" +``` + +--- + +### Task 2: Active trip mode — route-based lookup + GPX on home map + +**Files:** +- Modify: `user/themes/intotheeast/templates/home.html.twig` — full replacement +- Modify: `tests/ui/maps.spec.js` — add M8 + +**Interfaces:** +- Consumes: `config.site.travelling` (bool) — Task 1 +- Consumes: `config.site.active_trip` (full route string) — Task 1 +- Produces: `window.homeMap` global (already existed — now with GPX sources `home-gpx-0` … and `home-journey`) +- Produces: `{% else %}` placeholder in template for Task 3 to fill + +- [ ] **Step 1: Write failing test M8** + +Add to `tests/ui/maps.spec.js`: + +```js +// ── M8: Home map has GPX journey source on active trip ──────────────────────── +test('M8: home map has a journey source after GPX settles (active trip)', async ({ page }) => { + // Requires travelling: true in user/config/site.yaml (set in Task 1). + // Requires GPX files attached to the active trip (italy-2026-demo has 7). + const errors = []; + page.on('pageerror', e => errors.push(e.message)); + + await page.goto('/'); + await expect(page.locator('#home-map canvas.maplibregl-canvas')).toBeVisible({ timeout: 10000 }); + await expect(page.locator('#home-map .maplibregl-marker').first()).toBeVisible({ timeout: 15000 }); + + await page.waitForFunction(function () { + return window.homeMap && + (window.homeMap.getSource('home-journey') !== undefined || + window.homeMap.getSource('home-gpx-0') !== undefined); + }, { timeout: 20000 }); + + const hasSource = await page.evaluate(function () { + return !!(window.homeMap.getSource('home-journey') || window.homeMap.getSource('home-gpx-0')); + }); + + expect(hasSource, 'Home map has a journey or GPX source').toBe(true); + expect(errors, 'No JS errors on home page').toHaveLength(0); +}); +``` + +Run to confirm it fails: + +```bash +npx playwright test tests/ui/maps.spec.js --grep "M8" +``` + +Expected: FAIL (GPX sources not yet added to home map). + +- [ ] **Step 2: Replace `home.html.twig` with the active-trip-mode version** + +Replace the entire file with: + +```twig +{% extends 'partials/base.html.twig' %} + +{% block content %} +{% set trip_route = config.site.active_trip %} +{% set trip = grav.pages.find(trip_route) %} + +{% if config.site.travelling %} +{# ══════════════════════════════════════════════════════════ ACTIVE TRIP MODE #} + +{% set dailies_page = grav.pages.find(trip_route ~ '/dailies') %} +{% set stories_page = grav.pages.find(trip_route ~ '/stories') %} +{% set journal_entries = dailies_page ? dailies_page.children.published() : [] %} +{% set story_entries = stories_page ? stories_page.children.published() : [] %} + +{% set all_items = [] %} +{% for e in journal_entries %} + {% set all_items = all_items|merge([{'type': 'journal', 'page': e, 'date': e.header.date}]) %} +{% endfor %} +{% for s in story_entries %} + {% set all_items = all_items|merge([{'type': 'story', 'page': s, 'date': s.header.date}]) %} +{% endfor %} +{% set all_items = all_items|sort_by_key('date', 3) %} + +{% set journal_count = journal_entries|length %} +{% set story_count = story_entries|length %} + +{% set map_entries = [] %} +{% for item in all_items %} + {% if item.type == 'journal' and item.page.header.lat is not empty and item.page.header.lng is not empty %} + {% set map_entries = map_entries|merge([{ + 'lat': item.page.header.lat|number_format(6, '.', ''), + 'lng': item.page.header.lng|number_format(6, '.', ''), + 'slug': item.page.slug, + 'title': item.page.title, + 'url': item.page.url, + 'force_connect': item.page.header.force_connect ? true : false + }]) %} + {% endif %} +{% endfor %} + +{% set home_gpx_urls = [] %} +{% if trip %} + {% for name, media in trip.media.all %} + {% if name|split('.')|last == 'gpx' %} + {% set home_gpx_urls = home_gpx_urls|merge([trip.url ~ '/' ~ name]) %} + {% endif %} + {% endfor %} +{% endif %} + +
+
+
+
+ +
+
+

{{ trip ? trip.title : trip_route }}

+ + {{ journal_count }} journal {{ journal_count == 1 ? 'entry' : 'entries' }} + {% if story_count > 0 %} · {{ story_count }} {{ story_count == 1 ? 'story' : 'stories' }}{% endif %} + +
+ +
+ {% if all_items|length > 0 %} + {% for item in all_items %} + {% set entry = item.page %} + + {% if item.type == 'journal' %} + {% set weather_icons = { + 'Sunny': '☀️', 'Partly cloudy': '⛅', 'Cloudy': '☁️', + 'Foggy': '🌫️', 'Drizzle': '🌦️', 'Rain': '🌧️', + 'Snow': '❄️', 'Thunderstorm': '⛈️' + } %} +
+
+

{{ entry.title }}

+ +
+ + {% set images = entry.media.images %} + {% if images|length > 0 %} +
+ {% for img in images %} +
+ {{ entry.title }} +
+ {% endfor %} +
+ {% if images|length > 1 %} + + {% endif %} + {% endif %} + +
{{ entry.content|raw }}
+
+ {% else %} + {% set hero = null %} + {% if entry.header.hero_image and entry.media[entry.header.hero_image] is defined %} + {% set hero = entry.media[entry.header.hero_image] %} + {% elseif entry.media.images|length > 0 %} + {% set hero = entry.media.images|first %} + {% endif %} + + {% if hero %} +
+ {{ entry.title }} +
+ {% endif %} +
+ ✦ Story +

{{ entry.title }}

+
+
+ {% endif %} + {% endfor %} + {% else %} +

No entries yet. The journey is about to begin.

+ {% endif %} +
+
+
+ +{% if map_entries|length > 0 %} + + +{% if home_gpx_urls|length > 0 %} + +{% endif %} + + +{% endif %} + +{% else %} +{# ════════════════════════════════════════════════ BETWEEN-TRIPS MODE (Task 3) #} +

Off season — highlights coming in Task 3.

+{% endif %} + +{% endblock %} +``` + +- [ ] **Step 3: Run M8 test** + +```bash +npx playwright test tests/ui/maps.spec.js --grep "M8" +``` + +Expected: PASS. The home map now loads GPX URLs, adds `home-gpx-N` layer sources, and replaces the simple `home-journey` source with connector-suppressed segments. + +- [ ] **Step 4: Run existing home + map tests** + +```bash +npx playwright test tests/ui/maps.spec.js tests/ui/home.spec.js +``` + +Expected: M4 and H1 still pass; M8 passes. + +- [ ] **Step 5: Commit** + +```bash +git -C user add themes/intotheeast/templates/home.html.twig +git -C user commit -m "feat: add travelling branch and GPX to home map (active trip mode)" +git add tests/ui/maps.spec.js +git commit -m "test(maps): add M8 — home map GPX source on active trip" +``` + +--- + +### Task 3: Between-trips highlights mode + CSS + Playwright tests + +**Files:** +- Modify: `user/themes/intotheeast/templates/home.html.twig` — replace `{% else %}` placeholder with full highlights branch +- Modify: `user/themes/intotheeast/css/style.css` — append highlight card and grid styles +- Create: `tests/ui/home-highlights.spec.js` + +**Interfaces:** +- Consumes: `config.site.travelling` (bool) — Task 1 +- Consumes: `entry.header.featured` / `story.header.featured` (bool) — Task 1 +- Consumes: `trip.header.tagline` (string) — Task 1 +- Produces: `.home-highlights-grid` — the grid container, used in Playwright selectors +- Produces: `.home-highlight-card[id="highlight-"]` — per-card IDs for map marker scroll-to +- Produces: `.home-highlights-cta` — CTA link to `/trips` +- Produces: `window.homeMap` global in between-trips mode (same name, separate branch) + +- [ ] **Step 1: Write failing tests** + +Create `tests/ui/home-highlights.spec.js`: + +```js +// @ts-check +// Tests: H2–H5 — Between-trips highlights mode +// These tests temporarily set travelling: false in user/config/site.yaml, +// run the assertions, then restore the original value. +// Requires demo data with featured entries: run `make demo-load` first. +const { test, expect } = require('@playwright/test'); +const fs = require('fs'); +const path = require('path'); + +const SITE_YAML_PATH = path.join(__dirname, '../../user/config/site.yaml'); + +test.describe('Between-trips highlights mode', () => { + let originalSiteYaml; + + test.beforeAll(async () => { + originalSiteYaml = fs.readFileSync(SITE_YAML_PATH, 'utf8'); + const patched = originalSiteYaml.replace(/^travelling:\s*true/m, 'travelling: false'); + fs.writeFileSync(SITE_YAML_PATH, patched); + // Brief pause for Grav to re-read config on next request + await new Promise(r => setTimeout(r, 400)); + }); + + test.afterAll(async () => { + fs.writeFileSync(SITE_YAML_PATH, originalSiteYaml); + }); + + // ── H2: Highlights grid is visible ────────────────────────────────────────── + test('H2: homepage shows highlights grid when not travelling', async ({ page }) => { + await page.goto('/'); + await expect(page.locator('.home-highlights-grid')).toBeVisible({ timeout: 10000 }); + }); + + // ── H3: Highlight cards contain trip link ──────────────────────────────────── + test('H3: highlight cards have a View-trip link', async ({ page }) => { + await page.goto('/'); + await expect(page.locator('.home-highlight-card').first()).toBeVisible({ timeout: 10000 }); + await expect(page.locator('.home-highlight-trip-link').first()).toBeVisible(); + }); + + // ── H4: Between-trips home map renders without JS errors ──────────────────── + test('H4: home map renders in between-trips mode without JS errors', async ({ page }) => { + const errors = []; + page.on('pageerror', e => errors.push(e.message)); + await page.goto('/'); + await expect(page.locator('#home-map canvas.maplibregl-canvas')).toBeVisible({ timeout: 10000 }); + expect(errors, 'No JS errors').toHaveLength(0); + }); + + // ── H5: CTA links to /trips ────────────────────────────────────────────────── + test('H5: "Explore all past trips" CTA links to /trips', async ({ page }) => { + await page.goto('/'); + const cta = page.locator('.home-highlights-cta'); + await expect(cta).toBeVisible({ timeout: 10000 }); + await expect(cta).toHaveAttribute('href', /\/trips/); + }); +}); +``` + +Run to confirm they fail: + +```bash +npx playwright test tests/ui/home-highlights.spec.js +``` + +Expected: H2–H5 all FAIL (`.home-highlights-grid` not present). + +- [ ] **Step 2: Append highlight CSS to `user/themes/intotheeast/css/style.css`** + +Append to the end of `style.css`: + +```css +/* ── Between-trips highlights grid ──────────────────────────────────────────── */ + +.home-highlights-header { + margin-bottom: var(--space-8); + padding-bottom: var(--space-6); + border-bottom: 1px solid var(--color-border); +} + +.home-highlights-title { + font-family: var(--font-display); + font-size: var(--text-2xl); + font-weight: 400; + color: var(--color-ink); + margin-bottom: var(--space-2); +} + +.home-highlights-subtitle { + font-size: var(--text-sm); + color: var(--color-ink-muted); +} + +.home-highlights-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--space-6); + margin-bottom: var(--space-10); +} + +@media (max-width: 900px) { + .home-highlights-grid { grid-template-columns: repeat(2, 1fr); } +} + +@media (max-width: 600px) { + .home-highlights-grid { grid-template-columns: 1fr; } +} + +.home-highlight-card { + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + background: var(--color-canvas); + overflow: hidden; + display: flex; + flex-direction: column; +} + +.home-highlight-image { + aspect-ratio: 16 / 9; + overflow: hidden; +} + +.home-highlight-image img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +.home-highlight-body { + padding: var(--space-4); + display: flex; + flex-direction: column; + gap: var(--space-2); + flex: 1; +} + +.home-highlight-badge { + font-size: var(--text-xs); + font-weight: 600; + font-variant: small-caps; + letter-spacing: 0.08em; + color: var(--color-accent); +} + +.home-highlight-badge--journal { + color: var(--color-ink-muted); +} + +.home-highlight-title { + font-family: var(--font-display); + font-size: var(--text-lg); + font-weight: 400; + color: var(--color-ink); + text-decoration: none; + line-height: 1.3; +} + +.home-highlight-title:hover { color: var(--color-accent); } + +.home-highlight-trip { + margin-top: auto; + padding-top: var(--space-3); + border-top: 1px solid var(--color-border); + font-size: var(--text-xs); + color: var(--color-ink-muted); + display: flex; + flex-direction: column; + gap: var(--space-1); +} + +.home-highlight-trip-name { + font-weight: 600; + color: var(--color-ink-2); +} + +.home-highlight-tagline { font-style: italic; } + +.home-highlight-trip-link { + color: var(--color-accent); + text-decoration: none; + font-weight: 500; +} + +.home-highlight-trip-link:hover { text-decoration: underline; } + +.home-highlights-cta-wrap { + text-align: center; + padding-top: var(--space-4); + border-top: 1px solid var(--color-border); +} + +.home-highlights-cta { + display: inline-block; + color: var(--color-accent); + font-size: var(--text-sm); + font-weight: 500; + text-decoration: none; + padding: var(--space-3) var(--space-6); + border: 1px solid var(--color-accent); + border-radius: var(--radius-sm); +} + +.home-highlights-cta:hover { + background: var(--color-accent); + color: var(--color-canvas); +} +``` + +- [ ] **Step 3: Replace the placeholder `{% else %}` branch in `home.html.twig`** + +Find this exact block (added in Task 2): + +```twig +{% else %} +{# ════════════════════════════════════════════════ BETWEEN-TRIPS MODE (Task 3) #} +

Off season — highlights coming in Task 3.

+{% endif %} +``` + +Replace it with: + +```twig +{% else %} +{# ══════════════════════════════════════════════════════ BETWEEN-TRIPS MODE #} + +{# ── Highlight selection ─────────────────────────────────────────────────── #} +{% set trips_page = grav.pages.find('/trips') %} +{% set pool = [] %} +{% if trips_page %} + {% for trip_item in trips_page.children.published() %} + {% set t_dailies = grav.pages.find(trip_item.route ~ '/dailies') %} + {% set t_stories = grav.pages.find(trip_item.route ~ '/stories') %} + {% set candidates = [] %} + {% if t_dailies %} + {% for e in t_dailies.children.published() %} + {% if e.header.featured %} + {% set candidates = candidates|merge([{'type': 'journal', 'page': e, 'trip': trip_item}]) %} + {% endif %} + {% endfor %} + {% endif %} + {% if t_stories %} + {% for s in t_stories.children.published() %} + {% if s.header.featured %} + {% set candidates = candidates|merge([{'type': 'story', 'page': s, 'trip': trip_item}]) %} + {% endif %} + {% endfor %} + {% endif %} + {% if candidates|length > 0 %} + {% set pool = pool|merge([random(candidates)]) %} + {% endif %} + {% endfor %} +{% endif %} +{% set pool = pool|shuffle %} +{% set highlights = pool|slice(0, 6) %} + +{# ── Map entries (entries with coordinates) ──────────────────────────────── #} +{% set highlights_map_entries = [] %} +{% for item in highlights %} + {% if item.page.header.lat is not empty and item.page.header.lng is not empty %} + {% set highlights_map_entries = highlights_map_entries|merge([{ + 'lat': item.page.header.lat|number_format(6, '.', ''), + 'lng': item.page.header.lng|number_format(6, '.', ''), + 'slug': item.page.slug, + 'title': item.page.title, + 'url': item.page.url + }]) %} + {% endif %} +{% endfor %} + +
+
+
+
+ +
+
+

Into the East

+

A few moments from past journeys

+
+ + {% if highlights|length > 0 %} +
+ {% for item in highlights %} + {% set entry = item.page %} + {% set hero = null %} + {% if entry.header.hero_image and entry.media[entry.header.hero_image] is defined %} + {% set hero = entry.media[entry.header.hero_image] %} + {% elseif entry.media.images|length > 0 %} + {% set hero = entry.media.images|first %} + {% endif %} +
+ {% if hero %} +
+ {{ entry.title }} +
+ {% endif %} +
+ {% if item.type == 'story' %} + ✦ Story + {% else %} + ▸ Journal + {% endif %} + {{ entry.title }} +
+ {{ item.trip.title }} + {% if item.trip.header.tagline %} + {{ item.trip.header.tagline }} + {% endif %} + → View trip +
+
+
+ {% endfor %} +
+ {% else %} +

No highlights yet — mark entries as featured to show them here.

+ {% endif %} + + +
+
+ +{% if highlights_map_entries|length > 0 %} + + + + +{% endif %} + +{% endif %} + +{% endblock %} +``` + +- [ ] **Step 4: Run H2–H5 tests** + +```bash +npx playwright test tests/ui/home-highlights.spec.js +``` + +Expected: H2, H3, H4, H5 all PASS. + +- [ ] **Step 5: Verify no regressions** + +```bash +make test +npx playwright test tests/ui/ +``` + +Expected: `make test` same as baseline (13 passed, 1 pre-existing failure). All Playwright tests pass — H1 and M4 use `travelling: true`; H2–H5 temporarily flip to `false` and restore it. + +- [ ] **Step 6: Commit** + +```bash +git -C user add themes/intotheeast/templates/home.html.twig \ + themes/intotheeast/css/style.css +git -C user commit -m "feat: add between-trips highlights mode with grid and map markers" +git add tests/ui/home-highlights.spec.js +git commit -m "test(home): add H2–H5 between-trips highlights Playwright tests" +```