36 KiB
Inline Journal Feed 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: Replace click-through journal entry cards with fully inline posts (photo strip + full text) across the trip page, dailies page, and home page.
Architecture: Each journal entry becomes an <article class="journal-post"> block that renders all its images in a CSS scroll-snap strip with dot indicators, followed by the full body text. The id, data-type, data-lat, data-lng attributes stay on the root so map targeting, filter JS, and flash animation continue to work. Story cards in all three feeds are unchanged.
Tech Stack: Grav 2.0 Twig templates, CSS scroll-snap (no library), vanilla JS IntersectionObserver-free dot sync via scroll event, Playwright tests
Global Constraints
- All CSS values must use design tokens (
var(--...)) — no hard-coded colours, sizes, or radii id="entry-{{ entry.slug }}"must remain on the journal post root (map scroll targeting)data-type="journal"must remain on the journal post root (filter bar JS)data-latanddata-lngmust remain on the journal post root (map marker rendering)- Story cards (
<a class="entry-card entry-card--story">) are not touched by any task - Two git repos: user content at
/home/mischa/Projects/travel-blog-intotheeast/user/(separate git repo); outer repo at/home/mischa/Nextcloud/Projects/travel-blog-intotheeast/. Templates and CSS commit to the user subrepo; tests commit to the outer repo. Always update the outer repo'susersubmodule pointer in the same commit as the test changes. - Dev server: http://localhost:8081
File Map
| File | Change |
|---|---|
user/themes/intotheeast/css/style.css |
Add .journal-post component; remove journal-card-only rules; update .is-highlighted selector |
user/themes/intotheeast/templates/partials/base.html.twig |
Add photo-strip dot-sync JS before </body> |
user/themes/intotheeast/templates/dailies.html.twig |
Replace journal card block with .journal-post; add weather_icons |
user/themes/intotheeast/templates/trip.html.twig |
Replace journal card block with .journal-post; add weather_icons |
user/themes/intotheeast/templates/home.html.twig |
Replace journal card block with .journal-post; add weather_icons |
tests/ui/dailies.spec.js |
Update T1 selector; update T2 selectors |
tests/ui/maps.spec.js |
Update M7 selector |
tests/ui/home.spec.js |
New file — H1 test |
Task 1: CSS foundation + dot-sync JS
Add all new .journal-post CSS and the photo-strip dot-sync JS. Remove CSS classes that are only used by the old journal entry card (not by story cards). This task has no template changes — existing tests must still pass at the end.
Files:
- Modify:
user/themes/intotheeast/css/style.css - Modify:
user/themes/intotheeast/templates/partials/base.html.twig
Interfaces:
-
Produces:
.journal-post,.journal-post-header,.journal-post-title,.journal-post-meta,.journal-post-permalink,.journal-post-location,.journal-post-weather,.journal-photo-strip,.journal-photo-slide,.journal-photo-dots,.journal-photo-dot.is-active,.journal-post-body,.journal-post.is-highlighted— all usable by Tasks 2–4 -
Step 1: Add
.journal-postCSS block tostyle.css
In user/themes/intotheeast/css/style.css, find the line:
/* ── Single entry ────────────────────────────────────────────────────────────── */
Insert the following block before that comment:
/* ── Journal post (inline feed) ─────────────────────────────────────────────── */
.journal-post {
border-bottom: 1px solid var(--color-border);
padding-bottom: var(--space-12);
margin-bottom: var(--space-12);
}
.journal-post-header {
margin-bottom: var(--space-4);
}
.journal-post-title {
font-family: var(--font-display);
font-size: var(--text-xl);
font-weight: 400;
line-height: var(--leading-snug);
color: var(--color-ink);
margin-bottom: var(--space-2);
}
.journal-post-meta {
font-size: var(--text-xs);
color: var(--color-ink-muted);
display: flex;
align-items: center;
flex-wrap: wrap;
gap: var(--space-2);
}
.journal-post-permalink {
color: var(--color-ink-muted);
text-decoration: none;
font-weight: 700;
letter-spacing: 0.07em;
}
.journal-post-permalink:hover { color: var(--color-accent); }
.journal-post-location,
.journal-post-weather {
color: var(--color-ink-muted);
}
.journal-photo-strip {
display: flex;
overflow-x: scroll;
scroll-snap-type: x mandatory;
scrollbar-width: none;
border-radius: var(--radius-md);
margin-bottom: var(--space-3);
}
.journal-photo-strip::-webkit-scrollbar { display: none; }
.journal-photo-slide {
flex: 0 0 100%;
scroll-snap-align: start;
aspect-ratio: 3 / 2;
overflow: hidden;
}
.journal-photo-slide img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.journal-photo-dots {
display: flex;
justify-content: center;
gap: var(--space-2);
margin-bottom: var(--space-4);
}
.journal-photo-dot {
width: 6px;
height: 6px;
border-radius: 9999px;
background: var(--color-border);
transition: background 0.2s;
}
.journal-photo-dot.is-active {
background: var(--color-ink-muted);
}
.journal-post-body {
font-size: var(--text-base);
line-height: var(--leading-normal);
color: var(--color-ink-2);
}
.journal-post-body p { margin-bottom: var(--space-4); }
.journal-post-body p:last-child { margin-bottom: 0; }
.journal-post.is-highlighted {
animation: card-highlight 0.7s ease-out forwards;
}
- Step 2: Remove journal-card-only CSS rules from
style.css
These rules are only used by the old journal entry card. Story cards do not use them. Remove each block exactly as shown.
Remove .entry-card-photo-overlay and its children:
.entry-card-photo-overlay {
position: absolute;
inset: auto 0 0 0;
padding: var(--space-5) var(--space-4) var(--space-3);
background: linear-gradient(to top, rgba(0,0,0,0.58) 0%, transparent 100%);
display: flex;
align-items: flex-end;
gap: var(--space-3);
flex-wrap: wrap;
}
.entry-date-overlay {
font-size: var(--text-xs);
font-weight: 700;
letter-spacing: 0.08em;
color: rgba(255,255,255,0.92);
}
.entry-location-overlay {
font-size: var(--text-xs);
color: rgba(255,255,255,0.85);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
}
Replace with nothing (delete the block entirely).
Remove the text-only meta block and its comment:
/* Card: text-only variant */
.entry-card-textmeta {
display: flex;
align-items: center;
gap: var(--space-3);
margin-bottom: var(--space-3);
flex-wrap: wrap;
}
.entry-date-plain {
font-size: var(--text-xs);
font-weight: 700;
letter-spacing: 0.07em;
color: var(--color-ink-muted);
}
.entry-location-plain {
font-size: var(--text-xs);
color: var(--color-ink-muted);
}
Replace with nothing.
Remove .entry-excerpt and .entry-read-more:
.entry-excerpt {
font-size: var(--text-base);
line-height: var(--leading-normal);
color: var(--color-ink-2);
margin-bottom: var(--space-3);
}
.entry-read-more {
font-size: var(--text-sm);
font-weight: 500;
color: var(--color-accent);
}
Replace with nothing.
Replace .entry-card.is-highlighted with .journal-post.is-highlighted:
Find:
.entry-card.is-highlighted {
animation: card-highlight 0.7s ease-out forwards;
}
Replace with:
.journal-post.is-highlighted {
animation: card-highlight 0.7s ease-out forwards;
}
- Step 3: Add dot-sync JS to
base.html.twig
In user/themes/intotheeast/templates/partials/base.html.twig, find:
{{ assets.js('bottom')|raw }}
</body>
Replace with:
{{ assets.js('bottom')|raw }}
<script>
(function () {
document.querySelectorAll('.journal-photo-strip').forEach(function (strip) {
var dots = strip.nextElementSibling;
if (!dots || !dots.classList.contains('journal-photo-dots')) return;
var dotEls = Array.from(dots.querySelectorAll('.journal-photo-dot'));
strip.addEventListener('scroll', function () {
var idx = Math.round(strip.scrollLeft / strip.offsetWidth);
dotEls.forEach(function (d, i) { d.classList.toggle('is-active', i === idx); });
}, { passive: true });
});
})();
</script>
</body>
- Step 4: Run existing tests to confirm nothing broke
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast
npx playwright test --project=chromium tests/ui/dailies.spec.js tests/ui/maps.spec.js tests/ui/trip-filter.spec.js tests/ui/stories.spec.js
Expected: all existing tests pass (M7 still passes because trip.html.twig has not changed yet — the JS still adds is-highlighted to .entry-card elements, and the old M7 selector .entry-card.is-highlighted finds the element).
- Step 5: Commit user subrepo
cd /home/mischa/Projects/travel-blog-intotheeast/user
git add themes/intotheeast/css/style.css themes/intotheeast/templates/partials/base.html.twig
git commit -m "feat: add journal-post CSS component and dot-sync JS; remove stale journal-card-only rules"
Task 2: dailies.html.twig + T1/T2 test updates
Replace the journal entry card in dailies.html.twig with the new .journal-post inline block. Update T1 and T2 tests to match the new structure.
Files:
- Modify:
user/themes/intotheeast/templates/dailies.html.twig - Modify:
tests/ui/dailies.spec.js
Interfaces:
-
Consumes:
.journal-postCSS from Task 1 -
Produces:
/trips/japan-korea-2026/dailiesrenders.journal-postblocks; T1 and T2 pass with new selectors -
Step 1: Update T1 and T2 tests to their new selectors
In tests/ui/dailies.spec.js, make the following changes:
T1 — change .entry-card to .journal-post:
// OLD
await expect(page.locator('.entry-card').first()).toBeVisible();
// NEW
await expect(page.locator('.journal-post').first()).toBeVisible();
T2 — replace the entire card locator + index block with id-based selectors:
Find:
// Both fixture entries must be visible on the page
const newerCard = page.locator(`.entry-card[href*="${NEWER_SLUG}"]`);
const olderCard = page.locator(`.entry-card[href*="${OLDER_SLUG}"]`);
await expect(newerCard).toBeVisible();
await expect(olderCard).toBeVisible();
// The newer entry should appear higher in the DOM (lower index)
const newerIdx = await newerCard.evaluate(el => {
return [...document.querySelectorAll('.entry-card')].findIndex(c => c === el);
});
const olderIdx = await olderCard.evaluate(el => {
return [...document.querySelectorAll('.entry-card')].findIndex(c => c === el);
});
Replace with:
// Both fixture entries must be visible on the page
const newerCard = page.locator(`#entry-${NEWER_SLUG}`);
const olderCard = page.locator(`#entry-${OLDER_SLUG}`);
await expect(newerCard).toBeVisible();
await expect(olderCard).toBeVisible();
// The newer entry should appear higher in the DOM (lower index)
const newerIdx = await newerCard.evaluate(el => {
return [...document.querySelectorAll('.journal-post')].findIndex(c => c.id === el.id);
});
const olderIdx = await olderCard.evaluate(el => {
return [...document.querySelectorAll('.journal-post')].findIndex(c => c.id === el.id);
});
- Step 2: Run T1 and T2 to verify they fail
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast
npx playwright test --project=chromium tests/ui/dailies.spec.js -g "T1:|T2:"
Expected: FAIL — .journal-post selector finds no elements (the page still renders .entry-card).
- Step 3: Add
weather_iconsmap and replace journal card indailies.html.twig
In user/themes/intotheeast/templates/dailies.html.twig, find the line:
{% if item.type == 'journal' %}
<a class="entry-card" id="entry-{{ entry.slug }}" data-type="journal" data-lat="{{ entry.header.lat }}" data-lng="{{ entry.header.lng }}" href="{{ entry.url }}">
This {% if item.type == 'journal' %} block ends at </a> before {% else %}. Replace the entire journal card block (from {% if item.type == 'journal' %} through the closing </a> of the journal branch, leaving the {% else %} story branch intact) with:
{% if item.type == 'journal' %}
{% set weather_icons = {
'Sunny': '☀️', 'Partly cloudy': '⛅', 'Cloudy': '☁️',
'Foggy': '🌫️', 'Drizzle': '🌦️', 'Rain': '🌧️',
'Snow': '❄️', 'Thunderstorm': '⛈️'
} %}
<article class="journal-post" id="entry-{{ entry.slug }}" data-type="journal" data-lat="{{ entry.header.lat }}" data-lng="{{ entry.header.lng }}">
<header class="journal-post-header">
<h2 class="journal-post-title">{{ entry.title }}</h2>
<p class="journal-post-meta">
<a class="journal-post-permalink" href="{{ entry.url }}">
<time datetime="{{ entry.date|date('Y-m-d') }}">{{ entry.date|date('d M Y')|upper }}</time>
</a>
{% if entry.header.location_city or entry.header.location_country %}
<span class="journal-post-location">
· 📍
{%- set _loc = [] -%}
{%- if entry.header.location_city -%}{%- set _loc = _loc|merge([entry.header.location_city]) -%}{%- endif -%}
{%- if entry.header.location_country -%}{%- set _loc = _loc|merge([entry.header.location_country]) -%}{%- endif -%}
{{ _loc|join(', ') }}
</span>
{% endif %}
{% if entry.header.weather_desc %}
<span class="journal-post-weather">· {{ weather_icons[entry.header.weather_desc] ?? '' }} {{ entry.header.weather_desc }}</span>
{% endif %}
</p>
</header>
{% set images = entry.media.images %}
{% if images|length > 0 %}
<div class="journal-photo-strip" data-slides="{{ images|length }}">
{% for img in images %}
<div class="journal-photo-slide">
<img src="{{ img.cropResize(900, 600).url }}" alt="{{ entry.title }}" loading="lazy">
</div>
{% endfor %}
</div>
{% if images|length > 1 %}
<div class="journal-photo-dots" aria-hidden="true">
{% for img in images %}
<span class="journal-photo-dot{% if loop.first %} is-active{% endif %}"></span>
{% endfor %}
</div>
{% endif %}
{% endif %}
<div class="journal-post-body">{{ entry.content|raw }}</div>
</article>
The exact text to find and replace is the old journal branch. The old branch starts with:
{% if item.type == 'journal' %}
<a class="entry-card" id="entry-{{ entry.slug }}" data-type="journal" data-lat="{{ entry.header.lat }}" data-lng="{{ entry.header.lng }}" href="{{ entry.url }}">
{% if hero %}
<div class="entry-card-photo">
<img src="{{ hero.cropResize(720, 405).url }}" alt="{{ entry.title }}" loading="lazy">
<div class="entry-card-photo-overlay">
<time class="entry-date-overlay" datetime="{{ entry.date|date('Y-m-d') }}">
{{ entry.date|date('d M Y')|upper }}
</time>
{% if entry.header.location_city or entry.header.location_country %}
<span class="entry-location-overlay">
📍
{% if entry.header.location_city %}{{ entry.header.location_city|slice(0,20) }}{% endif %}
{% if entry.header.location_city and entry.header.location_country %}, {% endif %}
{% if entry.header.location_country %}{{ entry.header.location_country }}{% endif %}
</span>
{% endif %}
</div>
</div>
{% else %}
<div class="entry-card-textmeta">
<time class="entry-date-plain" datetime="{{ entry.date|date('Y-m-d') }}">
{{ entry.date|date('d M Y')|upper }}
</time>
{% if entry.header.location_city or entry.header.location_country %}
<span class="entry-location-plain">
{%- set _loc = [] -%}
{%- if entry.header.location_city -%}{%- set _loc = _loc|merge([entry.header.location_city]) -%}{%- endif -%}
{%- if entry.header.location_country -%}{%- set _loc = _loc|merge([entry.header.location_country]) -%}{%- endif -%}
📍 {{ _loc|join(', ') }}
</span>
{% endif %}
</div>
{% endif %}
<div class="entry-card-body">
<h2 class="entry-title">{{ entry.title }}</h2>
<p class="entry-excerpt">{{ entry.summary|striptags|slice(0, 250)|trim }}</p>
<span class="entry-read-more">Read entry →</span>
</div>
</a>
- Step 4: Run T1 and T2 to verify they pass
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast
npx playwright test --project=chromium tests/ui/dailies.spec.js -g "T1:|T2:"
Expected: PASS.
- Step 5: Run the full suite to check no regressions
npx playwright test --project=chromium tests/ui/dailies.spec.js tests/ui/maps.spec.js tests/ui/trip-filter.spec.js tests/ui/stories.spec.js
Expected: all pass.
- Step 6: Commit
cd /home/mischa/Projects/travel-blog-intotheeast/user
git add themes/intotheeast/templates/dailies.html.twig
git commit -m "feat: replace journal entry card with inline journal-post in dailies feed"
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast
git add tests/ui/dailies.spec.js user
git commit -m "test: update T1/T2 selectors for inline journal-post structure"
Task 3: trip.html.twig + M7 test update
Replace the journal entry card in trip.html.twig with the .journal-post block. Update M7 which currently tests .entry-card.is-highlighted on the trip page.
Files:
- Modify:
user/themes/intotheeast/templates/trip.html.twig - Modify:
tests/ui/maps.spec.js
Interfaces:
-
Consumes:
.journal-postCSS and.journal-post.is-highlightedfrom Task 1; journal-post HTML pattern from Task 2 -
Produces:
/trips/japan-korea-2026renders.journal-postblocks; M7 passes with.journal-post.is-highlighted -
Step 1: Update M7 to the new selector
In tests/ui/maps.spec.js, find:
// Within 500ms of click + delay, one entry-card should have is-highlighted
await expect(page.locator('.entry-card.is-highlighted')).toBeVisible({ timeout: 1500 });
Replace with:
// Within 500ms of click + delay, one journal-post should have is-highlighted
await expect(page.locator('.journal-post.is-highlighted')).toBeVisible({ timeout: 1500 });
- Step 2: Run M7 to verify it fails
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast
npx playwright test --project=chromium tests/ui/maps.spec.js -g "M7:"
Expected: FAIL — .journal-post.is-highlighted not found (trip.html.twig still renders <a class="entry-card">).
- Step 3: Replace journal card in
trip.html.twig
In user/themes/intotheeast/templates/trip.html.twig, find and replace the journal branch of the {% if item.type == 'journal' %} block. The old branch to replace is:
{% if item.type == 'journal' %}
<a class="entry-card" id="entry-{{ entry.slug }}" data-type="journal" data-lat="{{ entry.header.lat }}" data-lng="{{ entry.header.lng }}" href="{{ entry.url }}">
{% if hero %}
<div class="entry-card-photo">
<img src="{{ hero.cropResize(720, 405).url }}" alt="{{ entry.title }}" loading="lazy">
<div class="entry-card-photo-overlay">
<time class="entry-date-overlay" datetime="{{ entry.date|date('Y-m-d') }}">
{{ entry.date|date('d M Y')|upper }}
</time>
{% if entry.header.location_city or entry.header.location_country %}
<span class="entry-location-overlay">
📍
{% if entry.header.location_city %}{{ entry.header.location_city|slice(0,20) }}{% endif %}
{% if entry.header.location_city and entry.header.location_country %}, {% endif %}
{% if entry.header.location_country %}{{ entry.header.location_country }}{% endif %}
</span>
{% endif %}
</div>
</div>
{% else %}
<div class="entry-card-textmeta">
<time class="entry-date-plain" datetime="{{ entry.date|date('Y-m-d') }}">
{{ entry.date|date('d M Y')|upper }}
</time>
{% if entry.header.location_city or entry.header.location_country %}
<span class="entry-location-plain">
{%- set _loc = [] -%}
{%- if entry.header.location_city -%}{%- set _loc = _loc|merge([entry.header.location_city]) -%}{%- endif -%}
{%- if entry.header.location_country -%}{%- set _loc = _loc|merge([entry.header.location_country]) -%}{%- endif -%}
📍 {{ _loc|join(', ') }}
</span>
{% endif %}
</div>
{% endif %}
<div class="entry-card-body">
<h2 class="entry-title">{{ entry.title }}</h2>
<p class="entry-excerpt">{{ entry.summary|striptags|slice(0, 250)|trim }}</p>
<span class="entry-read-more">Read entry →</span>
</div>
</a>
Replace with:
{% if item.type == 'journal' %}
{% set weather_icons = {
'Sunny': '☀️', 'Partly cloudy': '⛅', 'Cloudy': '☁️',
'Foggy': '🌫️', 'Drizzle': '🌦️', 'Rain': '🌧️',
'Snow': '❄️', 'Thunderstorm': '⛈️'
} %}
<article class="journal-post" id="entry-{{ entry.slug }}" data-type="journal" data-lat="{{ entry.header.lat }}" data-lng="{{ entry.header.lng }}">
<header class="journal-post-header">
<h2 class="journal-post-title">{{ entry.title }}</h2>
<p class="journal-post-meta">
<a class="journal-post-permalink" href="{{ entry.url }}">
<time datetime="{{ entry.date|date('Y-m-d') }}">{{ entry.date|date('d M Y')|upper }}</time>
</a>
{% if entry.header.location_city or entry.header.location_country %}
<span class="journal-post-location">
· 📍
{%- set _loc = [] -%}
{%- if entry.header.location_city -%}{%- set _loc = _loc|merge([entry.header.location_city]) -%}{%- endif -%}
{%- if entry.header.location_country -%}{%- set _loc = _loc|merge([entry.header.location_country]) -%}{%- endif -%}
{{ _loc|join(', ') }}
</span>
{% endif %}
{% if entry.header.weather_desc %}
<span class="journal-post-weather">· {{ weather_icons[entry.header.weather_desc] ?? '' }} {{ entry.header.weather_desc }}</span>
{% endif %}
</p>
</header>
{% set images = entry.media.images %}
{% if images|length > 0 %}
<div class="journal-photo-strip" data-slides="{{ images|length }}">
{% for img in images %}
<div class="journal-photo-slide">
<img src="{{ img.cropResize(900, 600).url }}" alt="{{ entry.title }}" loading="lazy">
</div>
{% endfor %}
</div>
{% if images|length > 1 %}
<div class="journal-photo-dots" aria-hidden="true">
{% for img in images %}
<span class="journal-photo-dot{% if loop.first %} is-active{% endif %}"></span>
{% endfor %}
</div>
{% endif %}
{% endif %}
<div class="journal-post-body">{{ entry.content|raw }}</div>
</article>
- Step 4: Run M7 to verify it passes
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast
npx playwright test --project=chromium tests/ui/maps.spec.js -g "M7:"
Expected: PASS.
- Step 5: Run full suite
npx playwright test --project=chromium tests/ui/dailies.spec.js tests/ui/maps.spec.js tests/ui/trip-filter.spec.js tests/ui/stories.spec.js
Expected: all pass.
- Step 6: Commit
cd /home/mischa/Projects/travel-blog-intotheeast/user
git add themes/intotheeast/templates/trip.html.twig
git commit -m "feat: replace journal entry card with inline journal-post in trip feed"
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast
git add tests/ui/maps.spec.js user
git commit -m "test: update M7 selector for journal-post.is-highlighted"
Task 4: home.html.twig + H1 test
Replace the journal entry card in home.html.twig and add a minimal home page test.
Files:
- Modify:
user/themes/intotheeast/templates/home.html.twig - Create:
tests/ui/home.spec.js
Interfaces:
-
Consumes:
.journal-postCSS from Task 1; journal-post HTML pattern from Task 2 -
Step 1: Write the failing H1 test
Create tests/ui/home.spec.js:
// @ts-check
// Tests: H1 — home page journal feed
const { test, expect } = require('@playwright/test');
// ── H1: Home page renders inline journal posts ─────────────────────────────────
test('H1: home page shows at least one inline journal-post block', async ({ page }) => {
await page.goto('/');
await expect(page.locator('.journal-post').first()).toBeVisible();
await expect(page.locator('.site-header')).toBeVisible();
});
- Step 2: Run H1 to verify it fails
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast
npx playwright test --project=chromium tests/ui/home.spec.js
Expected: FAIL — .journal-post not found (home page still renders <a class="entry-card">).
- Step 3: Replace journal card in
home.html.twig
In user/themes/intotheeast/templates/home.html.twig, find the journal branch:
{% if item.type == 'journal' %}
<a class="entry-card" id="entry-{{ entry.slug }}" data-lat="{{ entry.header.lat }}" data-lng="{{ entry.header.lng }}" href="{{ entry.url }}">
{% if hero %}
<div class="entry-card-photo">
<img src="{{ hero.cropResize(720, 405).url }}" alt="{{ entry.title }}" loading="lazy">
<div class="entry-card-photo-overlay">
<time class="entry-date-overlay" datetime="{{ entry.date|date('Y-m-d') }}">
{{ entry.date|date('d M Y')|upper }}
</time>
{% if entry.header.location_city or entry.header.location_country %}
<span class="entry-location-overlay">
📍
{% if entry.header.location_city %}{{ entry.header.location_city|slice(0,20) }}{% endif %}
{% if entry.header.location_city and entry.header.location_country %}, {% endif %}
{% if entry.header.location_country %}{{ entry.header.location_country }}{% endif %}
</span>
{% endif %}
</div>
</div>
{% else %}
<div class="entry-card-textmeta">
<time class="entry-date-plain" datetime="{{ entry.date|date('Y-m-d') }}">
{{ entry.date|date('d M Y')|upper }}
</time>
{% if entry.header.location_city or entry.header.location_country %}
<span class="entry-location-plain">
{%- set _loc = [] -%}
{%- if entry.header.location_city -%}{%- set _loc = _loc|merge([entry.header.location_city]) -%}{%- endif -%}
{%- if entry.header.location_country -%}{%- set _loc = _loc|merge([entry.header.location_country]) -%}{%- endif -%}
📍 {{ _loc|join(', ') }}
</span>
{% endif %}
</div>
{% endif %}
<div class="entry-card-body">
<h2 class="entry-title">{{ entry.title }}</h2>
<p class="entry-excerpt">{{ entry.summary|striptags|slice(0, 250)|trim }}</p>
<span class="entry-read-more">Read entry →</span>
</div>
</a>
Replace with:
{% if item.type == 'journal' %}
{% set weather_icons = {
'Sunny': '☀️', 'Partly cloudy': '⛅', 'Cloudy': '☁️',
'Foggy': '🌫️', 'Drizzle': '🌦️', 'Rain': '🌧️',
'Snow': '❄️', 'Thunderstorm': '⛈️'
} %}
<article class="journal-post" id="entry-{{ entry.slug }}" data-lat="{{ entry.header.lat }}" data-lng="{{ entry.header.lng }}">
<header class="journal-post-header">
<h2 class="journal-post-title">{{ entry.title }}</h2>
<p class="journal-post-meta">
<a class="journal-post-permalink" href="{{ entry.url }}">
<time datetime="{{ entry.date|date('Y-m-d') }}">{{ entry.date|date('d M Y')|upper }}</time>
</a>
{% if entry.header.location_city or entry.header.location_country %}
<span class="journal-post-location">
· 📍
{%- set _loc = [] -%}
{%- if entry.header.location_city -%}{%- set _loc = _loc|merge([entry.header.location_city]) -%}{%- endif -%}
{%- if entry.header.location_country -%}{%- set _loc = _loc|merge([entry.header.location_country]) -%}{%- endif -%}
{{ _loc|join(', ') }}
</span>
{% endif %}
{% if entry.header.weather_desc %}
<span class="journal-post-weather">· {{ weather_icons[entry.header.weather_desc] ?? '' }} {{ entry.header.weather_desc }}</span>
{% endif %}
</p>
</header>
{% set images = entry.media.images %}
{% if images|length > 0 %}
<div class="journal-photo-strip" data-slides="{{ images|length }}">
{% for img in images %}
<div class="journal-photo-slide">
<img src="{{ img.cropResize(900, 600).url }}" alt="{{ entry.title }}" loading="lazy">
</div>
{% endfor %}
</div>
{% if images|length > 1 %}
<div class="journal-photo-dots" aria-hidden="true">
{% for img in images %}
<span class="journal-photo-dot{% if loop.first %} is-active{% endif %}"></span>
{% endfor %}
</div>
{% endif %}
{% endif %}
<div class="journal-post-body">{{ entry.content|raw }}</div>
</article>
Note: home.html.twig journal posts do not include data-type (the home page has no filter bar) — this matches the existing <a class="entry-card"> on home which also had no data-type.
- Step 4: Run H1 to verify it passes
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast
npx playwright test --project=chromium tests/ui/home.spec.js
Expected: PASS.
- Step 5: Run the full suite
npx playwright test --project=chromium tests/ui/dailies.spec.js tests/ui/maps.spec.js tests/ui/trip-filter.spec.js tests/ui/stories.spec.js tests/ui/home.spec.js
Expected: all pass.
- Step 6: Commit
cd /home/mischa/Projects/travel-blog-intotheeast/user
git add themes/intotheeast/templates/home.html.twig
git commit -m "feat: replace journal entry card with inline journal-post on home page"
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast
git add tests/ui/home.spec.js user
git commit -m "test: add H1 home page journal-post test"