Compare commits

...

10 Commits

Author SHA1 Message Date
m038 14791ab69e feat: add March–April trip fixture entries, remove stale test entry
Seven fixture entries (March 25 Narita through April 1 Seoul) used as
Playwright test fixtures for tracker ordering and entry-page tests.
Removes the leftover June 18 test entry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 22:34:29 +02:00
m038 c63378740c fix: use page.collection() for tracker descending date order
page.children ignores the order.by/dir frontmatter config; page.collection()
applies it, so entries now render newest-first as intended.

Also wire Grav asset pipeline into base template (assets.css/js tags).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 22:34:19 +02:00
m038 f0768c297b fix: switch photos to filepond for AJAX upload + increase limit to 100MB 2026-06-18 20:26:22 +02:00
m038 4558f94c3f fix: button color, replace native validation with custom inline errors 2026-06-18 20:10:05 +02:00
m038 682ba00bea fix: make Post Daily button full-width and mobile-friendly 2026-06-18 20:02:20 +02:00
m038 6b24215190 fix: rewire add-page-by-form so posts actually get created
Two root-cause bugs:
1. Wrong action name: 'add-page-by-form' is not handled by the plugin;
   the plugin only matches 'addpage' or 'add_page'. Using the wrong name
   meant the action silently no-oped while 'message' still fired, showing
   'Entry posted successfully!' for a post that was never written.
2. Config in wrong place: parent/slug/template must be in 'pageconfig' and
   'pagefrontmatter' frontmatter blocks on the form page — the plugin reads
   from page->header(), not from the process block params.

Fix: move config to pageconfig/pagefrontmatter, change action to 'add_page'.
Slug is built from date+title fields (e.g. 2026-06-18-1430-my-title).
Photos destination changed to '@self' so the plugin copies from flash to
the new entry folder correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 19:51:58 +02:00
m038 50ab4f522b fix: redirect back to /post after login instead of always going to /tracker 2026-06-18 19:42:33 +02:00
m038 f631ca3cfd fix: raise Grav upload_limit to 25MB to match PHP config 2026-06-18 19:40:54 +02:00
m038 8cc141d7d2 fix: correct HTML rendering in entry body and feed excerpts
- entry.html.twig: add |raw to page.content (autoescape: true in
  system.yaml was escaping all HTML output including rendered markdown)
- tracker.html.twig: use |striptags|slice(0,250) for clean plain-text
  excerpts instead of raw HTML summary
- Both templates: fix location display whitespace (Tokyo , Japan → Tokyo, Japan)
  using parts array pattern with Twig whitespace control

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 19:30:47 +02:00
m038 0eb5e8c6a5 docs: update demo instructions to use make commands 2026-06-18 17:52:38 +02:00
18 changed files with 264 additions and 72 deletions
+1 -1
View File
@@ -8,5 +8,5 @@ user_registration:
route: /login route: /login
built_in_css: true built_in_css: true
built_in_js: true built_in_js: true
redirect_after_login: /tracker redirect_after_login: ''
site_host: https://intotheeast.com site_host: https://intotheeast.com
+2 -2
View File
@@ -128,7 +128,7 @@ twig:
cache: false cache: false
debug: true debug: true
auto_reload: true auto_reload: true
autoescape: true autoescape: false
undefined_functions: true undefined_functions: true
undefined_filters: true undefined_filters: true
safe_functions: { } safe_functions: { }
@@ -195,7 +195,7 @@ media:
unsupported_inline_types: null unsupported_inline_types: null
allowed_fallback_types: null allowed_fallback_types: null
auto_metadata_exif: false auto_metadata_exif: false
upload_limit: 2097152 upload_limit: 104857600
session: session:
enabled: true enabled: true
initialize: true initialize: true
+3 -15
View File
@@ -27,16 +27,8 @@ Seven sample journal entries for a fictional Japan + South Korea trip (MarchA
## How to deploy demo content ## How to deploy demo content
Copy the tracker entries into the Grav pages tree:
```bash ```bash
cp -r user/docs/demo/tracker/. user/pages/01.tracker/ make demo-load
```
Then clear the Grav cache so the new pages appear immediately:
```bash
docker exec intotheeast_grav bash -c "cd /app/www/public && php bin/grav clearcache"
``` ```
Open `http://localhost:8081/tracker` — all 7 entries should appear. Open `http://localhost:8081/tracker` — all 7 entries should appear.
@@ -45,14 +37,10 @@ Open `http://localhost:8081/tracker` — all 7 entries should appear.
## How to reset (remove demo content) ## How to reset (remove demo content)
Remove all demo entries (leaves the real `tracker.md` index page and any real entries untouched if they have different slugs): Removes all demo entries. Real entries with different slugs are left untouched.
```bash ```bash
for dir in user/docs/demo/tracker/*/; do make demo-reset
folder=$(basename "$dir")
rm -rf "user/pages/01.tracker/$folder"
done
docker exec intotheeast_grav bash -c "cd /app/www/public && php bin/grav clearcache"
``` ```
--- ---
+2 -3
View File
@@ -77,9 +77,8 @@ Design direction: **Field Notes** — editorial travel journal aesthetic, not so
Seven sample entries for design/QA showcasing: feed, map route, stats, weather variety (including snow). Seven sample entries for design/QA showcasing: feed, map route, stats, weather variety (including snow).
```bash ```bash
# Deploy demo content make demo-load # copy entries into tracker, clear cache
cp -r user/docs/demo/tracker/. user/pages/01.tracker/ make demo-reset # remove demo entries, clear cache
docker exec intotheeast_grav bash -c "cd /app/www/public && php bin/grav clearcache"
``` ```
Full instructions: `user/docs/demo/README.md` Full instructions: `user/docs/demo/README.md`
@@ -0,0 +1,23 @@
---
title: 'Wheels Down at Narita'
date: '2026-03-25 15:40'
template: entry
published: true
hero_image: ''
lat: '35.7720'
lng: '140.3929'
location_city: 'Tokyo'
location_country: 'Japan'
weather_temp_c: 16
weather_desc: 'Sunny'
---
Eleven hours of flight time, two mediocre films, and one surprisingly good noodle dish from the trolley. Then the descent through scattered cloud, the first glimpse of grey-green patchwork below, and that particular feeling when the wheels finally touch down on a continent you have never stood on before.
Narita is large and orderly and very, very calm. Immigration moved faster than any airport I have ever been through. The officer looked at my passport, looked at me, stamped it once, and handed it back without a word. That was it. Entry to Japan.
The Narita Express runs direct to Shinjuku. I found a window seat and spent 90 minutes watching the city materialise from the outside in — rice fields giving way to low housing, then arterial roads, then the sudden verticality of central Tokyo rising up all at once as if someone just switched a setting.
The hotel is small but perfect. A room roughly the width of my arms outstretched, a window looking onto a grey concrete wall, and a bed that feels like sleeping on a cloud. I went out for ramen at a place around the corner where you order from a vending machine and sit at a counter alone with a small wooden partition between you and the next person. Nobody spoke. It was the best meal I have had in months.
Tomorrow: Ueno. The forecast says the cherry blossoms may finally be open.
@@ -0,0 +1,23 @@
---
title: 'Sakura in Ueno Park'
date: '2026-03-26 10:00'
template: entry
published: true
hero_image: ''
lat: '35.7155'
lng: '139.7753'
location_city: 'Tokyo'
location_country: 'Japan'
weather_temp_c: 14
weather_desc: 'Partly cloudy'
---
I arrived at Ueno Park at ten in the morning thinking I would beat the crowds. I was wrong. Several thousand people had the same idea, and the same Instagram instinct. But here is the thing about cherry blossom season in Japan — the crowds are almost part of it. Families with picnic sheets. Couples with matching outfits. Office workers in suits sitting on blue tarps eating convenience-store onigiri. Everyone doing the same thing: looking up at the same trees.
The blossoms were at maybe seventy percent. Enough to understand what the fuss is about.
I walked the park from one end to the other and then sat under a particularly generous tree for about an hour just watching people react to something beautiful. There is a Japanese word for it — *hanami* — which translates roughly as "flower viewing" and is more or less an entire cultural practice. You do not rush past the blossoms. You sit with them.
Later I found the Tokyo National Museum at the top of the park. Three floors of Japanese history, almost entirely in Japanese, which I cannot read, but context is its own language. A display case of Edo-period swords. Painted screens showing mountains I now recognise. A reconstructed tea house in the garden, closed for the season but visible through the glass.
Dinner: tonkatsu on a side street off Ueno-Okachimachi station. The woman who runs the counter has been there for at least thirty years by the look of it. She refilled my miso soup without being asked, twice.
@@ -0,0 +1,27 @@
---
title: 'Summit Clouds and Snow'
date: '2026-03-27 07:15'
template: entry
published: true
hero_image: ''
lat: '35.5095'
lng: '138.7646'
location_city: 'Kawaguchiko'
location_country: 'Japan'
weather_temp_c: 1
weather_desc: 'Snow'
---
Nobody told me it would snow.
I took the early bus from Shinjuku at 6:45am because the forecast for the Fuji Five Lakes region said "clear morning, clouds by noon." That is the window you want — Fuji is notorious for hiding inside its own weather system, and most visitors spend an entire day staring at a blank white sky where a mountain ought to be.
I got the mountain. For about forty minutes.
By the time the bus pulled into Kawaguchiko, the first flakes were already coming down. Light at first — the decorative kind that you hold your hand out for. Then, steadily, not decorative at all. I walked down to the lake with my bag under my jacket and stood at the water's edge while the snow thickened and Fuji turned from a sharply defined white cone into a suggestion, and then into nothing.
The lake surface was perfectly still. The snow fell straight down. There were no other tourists on the path, or if there were I could not see them. It was one of those moments of completely accidental solitude that you cannot plan for and would not trade.
I sat on a wooden bench on the lakefront for longer than made any meteorological sense. The snow kept falling. A single cormorant sat on a rock offshore and did not move the entire time I was there.
Caught the bus back to Shinjuku in the afternoon. The mountain never reappeared. I do not mind even slightly.
@@ -0,0 +1,23 @@
---
title: 'A Thousand Torii Gates'
date: '2026-03-28 11:30'
template: entry
published: true
hero_image: ''
lat: '34.9671'
lng: '135.7727'
location_city: 'Kyoto'
location_country: 'Japan'
weather_temp_c: 18
weather_desc: 'Sunny'
---
The Shinkansen from Tokyo to Kyoto takes two hours and twelve minutes. You travel at 285km/h. At one point Fuji appears out the right-hand window, clear and enormous and completely snow-covered, and the entire carriage rotates slightly to look at it. The mountain is visible for about four minutes. Then it is gone.
Kyoto is everything Tokyo is not: low, slow, wooden. The streets around Fushimi Inari were already warm with tourists at 11am but the shrine itself is large enough to absorb them. You walk under a tunnel of orange torii gates — thousands of them, each donated by a business and engraved with the donor's name — up a hillside through cedar forest, and the further you climb the more the crowd thins out.
I walked for two hours. Most visitors turn back at the first lookout. I kept going, past smaller shrines and stone fox statues and mossy steps worn down by a century of feet. Near the top the path was almost empty. The air smelled of pine and incense.
The city below spread out in all directions. Very few tall buildings — there are strict height regulations to preserve the sightlines. The Kamo River was a thin silver line running south. Distant mountains still wearing snow.
Dinner at a kaiseki restaurant in Gion, the old entertainment district. Eight small courses, each plated like a small still life. I ate slowly and said nothing and it was the right approach.
@@ -0,0 +1,21 @@
---
title: 'The Deer of Nara'
date: '2026-03-29 14:00'
template: entry
published: true
hero_image: ''
lat: '34.6851'
lng: '135.8048'
location_city: 'Nara'
location_country: 'Japan'
weather_temp_c: 17
weather_desc: 'Partly cloudy'
---
The deer at Nara are not afraid of you. This is the first thing you notice — not just that they tolerate humans, but that they regard you with a kind of benign indifference that borders on contempt. They walk into traffic. They push their noses into your pockets. They bow, which sounds enchanting and is, in practice, a manoeuvre to knock crackers out of your hand faster.
I bought a small bundle of *shika senbei* — deer crackers — from a vendor at the park entrance. They were gone in about forty-five seconds to a small gang of deer who appeared from nowhere and surrounded me in a tight semicircle. One bit my sleeve. Another headbutted a woman walking past who was not even involved.
Todai-ji temple is at the far end of the park and contains the largest bronze Buddha in Japan. The building is immense — apparently it was rebuilt at two-thirds the original size in the 18th century and is still the largest wooden structure in the world. The Buddha sits in the dim interior looking calm about this. There is a wooden pillar near the back with a hole cut through its base the same width as one of the Buddha's nostrils. Schoolchildren queue to crawl through it. Wisdom awaits on the other side.
The train back to Kyoto takes 45 minutes through flat agricultural land. The deer do not follow you.
@@ -0,0 +1,25 @@
---
title: 'Dotonbori After Dark'
date: '2026-03-30 18:00'
template: entry
published: true
hero_image: ''
lat: '34.6687'
lng: '135.5017'
location_city: 'Osaka'
location_country: 'Japan'
weather_temp_c: 19
weather_desc: 'Cloudy'
---
Osaka is louder than Kyoto and prouder of it. Kyoto has temples and restraint. Osaka has neon and takoyaki and a sign the size of a building advertising a restaurant with a mechanical crab on the front. Both are correct.
I arrived from Kyoto mid-afternoon, dropped my bag, and went directly to Dotonbori to get my bearings before the evening crowd descended. The canal runs through the entertainment district, and on both sides there are restaurants stacked six floors high with illuminated signs competing for your attention so aggressively that after ten minutes you start to tune out the sensory overload and just walk.
At six in the evening the neon started properly. The famous running man billboard. The Glico sign. Streets full of people eating while walking — takoyaki (octopus balls, better than they sound), skewered meats, cones of spicy shrimp. Osaka has a word for its own food philosophy: *kuidaore*, which means "eat until you drop."
I took it as guidance.
Three hours of eating across four separate establishments. Kushikatsu — battered and deep-fried everything — at a counter in an alley so narrow that diners on opposite sides can shake hands across the table. Soft-serve matcha ice cream on the street. Okonomiyaki from a woman who pressed the pancake flat with a heavy iron tool and would not let me touch anything.
The canal was dark and the lights were reflected in it and for a while I just stood on the bridge watching people eat.
@@ -0,0 +1,23 @@
---
title: 'Seoul Calling'
date: '2026-04-01 09:00'
template: entry
published: true
hero_image: ''
lat: '37.5635'
lng: '126.9851'
location_city: 'Seoul'
location_country: 'South Korea'
weather_temp_c: 10
weather_desc: 'Rain'
---
The flight from Osaka to Seoul takes one hour and forty minutes. Shorter than some commutes I have had. At Incheon I changed SIM cards, changed currency, changed alphabet, and walked out into a grey April morning with rain coming in off the Yellow Sea.
Korea hits differently than Japan. Japan felt deliberate and enclosed, every surface managed, every system timed to the second. Seoul feels faster and more argumentative, as if things are still being decided. The streets around Myeongdong were already busy at 9am: coffee shops the size of ballrooms, street vendors selling *hotteok* (sweet pancakes) from portable griddles, and the particular energy of a city that moves at one speed regardless of the weather.
My guesthouse is in Mapo-gu, a neighbourhood that turns out to be significantly cooler than anywhere the guidebooks sent me. Independent coffee roasters. Record shops. A gallery in a converted printing house showing black-and-white photography of the Han River in the 1970s.
I spent the afternoon walking the Han River itself — a massive green ribbon running through the city with dedicated cycling paths, outdoor fitness equipment, and Koreans doing every possible outdoor activity despite the rain. A group of older men playing badminton with very serious expressions. Two people kayaking. A family of five sharing a communal barbecue under an umbrella.
Dinner: Korean fried chicken at a place that opened at 5pm and was full by 5:05. Beer so cold it was almost painful. Outside, the rain kept up steadily. I stayed longer than I meant to.
@@ -1,15 +0,0 @@
---
title: 'QA Test Entry'
date: '2026-06-18 10:00'
template: entry
published: true
hero_image: ''
lat: '35.689487'
lng: '139.691711'
location_city: 'Tokyo'
location_country: 'Japan'
weather_temp_c: ''
weather_desc: ''
---
This is the QA test body. Second sentence for length.
+16 -17
View File
@@ -3,7 +3,16 @@ title: 'New Entry'
template: post-form template: post-form
access: access:
site.login: true site.login: true
login_redirect_here: true
pageconfig:
parent: '/tracker'
slug_field: 'date,title'
overwrite_mode: false
pagefrontmatter:
template: entry
published: true
form: form:
name: new-entry name: new-entry
action: /post action: /post
@@ -37,9 +46,9 @@ form:
- -
name: photos name: photos
label: Photos (max 4) label: Photos (max 4)
type: file type: filepond
multiple: true multiple: true
destination: 'user://pages/01.tracker' destination: '@self'
limit: 4 limit: 4
accept: accept:
- 'image/*' - 'image/*'
@@ -76,27 +85,17 @@ form:
name: weather_desc name: weather_desc
type: hidden type: hidden
novalidate: true
buttons: buttons:
- -
type: submit type: submit
value: Post Entry value: Post Daily
classes: btn-post classes: btn-post
process: process:
- -
add-page-by-form: add_page: true
parent: '/tracker'
slug: "{{ form.value.date|date('Y-m-d-Hi') }}-{{ form.value.title|lower|regex_replace('/[^a-z0-9]+/', '-')|trim('-') }}"
template: 'entry'
frontmatter:
title: '{{ form.value.title }}'
date: '{{ form.value.date }}'
lat: '{{ form.value.lat }}'
lng: '{{ form.value.lng }}'
location_city: '{{ form.value.location_city }}'
location_country: '{{ form.value.location_country }}'
weather_temp_c: '{{ form.value.weather_temp_c }}'
weather_desc: '{{ form.value.weather_desc }}'
- -
message: 'Entry posted successfully!' message: 'Entry posted successfully!'
- -
+22 -8
View File
@@ -547,27 +547,41 @@ body {
.post-form-wrap textarea { resize: vertical; min-height: 160px; line-height: var(--leading-normal); } .post-form-wrap textarea { resize: vertical; min-height: 160px; line-height: var(--leading-normal); }
/* Submit button */ /* Submit button */
.post-form-wrap .form-actions input[type="submit"], .post-form-wrap .btn-post {
.post-form-wrap .form-actions .btn,
.post-form-wrap .form-actions button[type="submit"] {
display: block; display: block;
width: 100%; width: 100%;
padding: 1rem; box-sizing: border-box;
min-height: 52px; padding: 1.1rem 1rem;
min-height: 56px;
background: var(--color-accent); background: var(--color-accent);
color: var(--color-accent-on); color: var(--color-accent-on);
border: none; border: none;
border-radius: var(--radius-md); border-radius: var(--radius-md);
font-family: var(--font-ui); font-family: var(--font-ui);
font-size: var(--text-base); font-size: var(--text-md);
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
transition: background 0.15s; transition: background 0.15s;
margin-top: var(--space-6); margin-top: var(--space-6);
text-align: center;
-webkit-appearance: none;
appearance: none;
} }
.post-form-wrap .form-actions input[type="submit"]:hover, .post-form-wrap .btn-post:hover { background: var(--color-accent-hover); }
.post-form-wrap .form-actions button[type="submit"]:hover { background: var(--color-accent-hover); }
/* Inline field validation */
.post-form-wrap .field-invalid {
border-color: #c0392b !important;
outline-color: #c0392b !important;
}
.post-form-wrap .field-error {
display: block;
color: #c0392b;
font-size: var(--text-sm);
margin-top: var(--space-1);
}
/* Location / weather action buttons */ /* Location / weather action buttons */
.form-action-row { .form-action-row {
+5 -5
View File
@@ -28,10 +28,10 @@
</time> </time>
{% if page.header.location_city or page.header.location_country %} {% if page.header.location_city or page.header.location_country %}
<p class="entry-location"> <p class="entry-location">
📍 {%- set _loc = [] -%}
{% if page.header.location_city %}{{ page.header.location_city }}{% endif %} {%- if page.header.location_city -%}{%- set _loc = _loc|merge([page.header.location_city]) -%}{%- endif -%}
{% if page.header.location_city and page.header.location_country %}, {% endif %} {%- if page.header.location_country -%}{%- set _loc = _loc|merge([page.header.location_country]) -%}{%- endif -%}
{% if page.header.location_country %}{{ page.header.location_country }}{% endif %} 📍 {{ _loc|join(', ') }}
</p> </p>
{% endif %} {% endif %}
{% if page.header.weather_desc or page.header.weather_temp_c %} {% if page.header.weather_desc or page.header.weather_temp_c %}
@@ -51,7 +51,7 @@
</header> </header>
<div class="entry-body"> <div class="entry-body">
{{ page.content }} {{ page.content|raw }}
</div> </div>
{% set images = page.media.images %} {% set images = page.media.images %}
@@ -9,6 +9,8 @@
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;1,9..40,400&family=DM+Serif+Display:ital@0;1&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;1,9..40,400&family=DM+Serif+Display:ital@0;1&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ url('theme://css/tokens.css') }}"> <link rel="stylesheet" href="{{ url('theme://css/tokens.css') }}">
<link rel="stylesheet" href="{{ url('theme://css/style.css') }}"> <link rel="stylesheet" href="{{ url('theme://css/style.css') }}">
{{ assets.css()|raw }}
{{ assets.js()|raw }}
</head> </head>
<body class="{% if page.template == 'map' %}map-page{% endif %}"> <body class="{% if page.template == 'map' %}map-page{% endif %}">
<header class="site-header"> <header class="site-header">
@@ -22,5 +24,6 @@
<main class="site-main"> <main class="site-main">
{% block content %}{% endblock %} {% block content %}{% endblock %}
</main> </main>
{{ assets.js('bottom')|raw }}
</body> </body>
</html> </html>
@@ -13,6 +13,45 @@
<p id="weather-status" class="form-status"></p> <p id="weather-status" class="form-status"></p>
</div> </div>
<script>
// Custom validation — form uses novalidate so we handle it here
(function() {
var REQUIRED = ['title', 'content'];
var form = document.querySelector('form[name="new-entry"]');
if (!form) return;
function clearErrors() {
form.querySelectorAll('.field-error').forEach(function(el) { el.remove(); });
form.querySelectorAll('.field-invalid').forEach(function(el) { el.classList.remove('field-invalid'); });
}
function showError(field, msg) {
field.classList.add('field-invalid');
var err = document.createElement('span');
err.className = 'field-error';
err.textContent = msg;
field.parentNode.insertBefore(err, field.nextSibling);
}
form.addEventListener('submit', function(e) {
clearErrors();
var firstInvalid = null;
REQUIRED.forEach(function(name) {
var field = form.querySelector('[name="data[' + name + ']"]');
if (field && !field.value.trim()) {
showError(field, name.charAt(0).toUpperCase() + name.slice(1) + ' is required.');
if (!firstInvalid) firstInvalid = field;
}
});
if (firstInvalid) {
e.preventDefault();
firstInvalid.focus();
firstInvalid.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
});
}());
</script>
<script> <script>
var WMO_MAP = { var WMO_MAP = {
0:'Sunny',1:'Partly cloudy',2:'Partly cloudy',3:'Cloudy', 0:'Sunny',1:'Partly cloudy',2:'Partly cloudy',3:'Cloudy',
@@ -1,7 +1,7 @@
{% extends 'default.html.twig' %} {% extends 'default.html.twig' %}
{% block content %} {% block content %}
{% set entries = page.children %} {% set entries = page.collection() %}
{# Collect GPS entries for mini-map #} {# Collect GPS entries for mini-map #}
{% set map_entries = [] %} {% set map_entries = [] %}
@@ -97,10 +97,10 @@ if (latLngs.length === 1) {
</time> </time>
{% if entry.header.location_city or entry.header.location_country %} {% if entry.header.location_city or entry.header.location_country %}
<span class="entry-location-plain"> <span class="entry-location-plain">
📍 {%- set _loc = [] -%}
{% if entry.header.location_city %}{{ entry.header.location_city }}{% endif %} {%- if entry.header.location_city -%}{%- set _loc = _loc|merge([entry.header.location_city]) -%}{%- endif -%}
{% if entry.header.location_city and entry.header.location_country %}, {% endif %} {%- if entry.header.location_country -%}{%- set _loc = _loc|merge([entry.header.location_country]) -%}{%- endif -%}
{% if entry.header.location_country %}{{ entry.header.location_country }}{% endif %} 📍 {{ _loc|join(', ') }}
</span> </span>
{% endif %} {% endif %}
</div> </div>
@@ -108,7 +108,7 @@ if (latLngs.length === 1) {
<div class="entry-card-body"> <div class="entry-card-body">
<h2 class="entry-title">{{ entry.title }}</h2> <h2 class="entry-title">{{ entry.title }}</h2>
<p class="entry-excerpt">{{ entry.summary }}</p> <p class="entry-excerpt">{{ entry.summary|striptags|slice(0, 250)|trim }}</p>
<span class="entry-read-more">Read entry →</span> <span class="entry-read-more">Read entry →</span>
</div> </div>
</a> </a>