Compare commits
15
Commits
4aa9ae9b23
...
536ca2085f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
536ca2085f | ||
|
|
21f0c8fdd5 | ||
|
|
ac88e8c003 | ||
|
|
13656e311a | ||
|
|
ad72739b99 | ||
|
|
b317640dd0 | ||
|
|
9bcb0fe4db | ||
|
|
b5f169f25f | ||
|
|
32c3d8c065 | ||
|
|
a9ec49e164 | ||
|
|
6e8eadbab5 | ||
|
|
ca15bd8bd7 | ||
|
|
1418dd275b | ||
|
|
93370033ea | ||
|
|
2840018372 |
@@ -9,5 +9,10 @@
|
||||
/config/plugins/git-sync.yaml
|
||||
/config/plugins/api-private.php
|
||||
/config/security.yaml
|
||||
/config/security-private.php
|
||||
/config/versions.yaml
|
||||
# Per-host env override tree — holds LIVE secrets (JWT, CSRF salt, git-sync
|
||||
# token) + per-host overrides. Never commit/sync: git-sync stages anything
|
||||
# not gitignored, so an untracked /env/ would boomerang to Gitea.
|
||||
/env/
|
||||
/themes/intotheeast/node_modules/
|
||||
|
||||
@@ -10,10 +10,12 @@ admin_next:
|
||||
preferences:
|
||||
pluginsViewMode: cards
|
||||
colorMode: dark
|
||||
accentHue: 271
|
||||
accentSaturation: 91
|
||||
fontFamily: inter
|
||||
accentHue: 38
|
||||
accentSaturation: 92
|
||||
fontFamily: public-sans
|
||||
pagesViewMode: tree
|
||||
fontSize: large
|
||||
editorMode: normal
|
||||
content_editor: ''
|
||||
groups: { }
|
||||
access:
|
||||
|
||||
@@ -49,7 +49,8 @@ invitations:
|
||||
popularity:
|
||||
enabled: true
|
||||
exclude_admin: true
|
||||
exclude_ips: { }
|
||||
exclude_ips:
|
||||
- 83.135.64.30
|
||||
history:
|
||||
daily: 30
|
||||
monthly: 12
|
||||
@@ -57,4 +58,3 @@ popularity:
|
||||
ignore:
|
||||
- '/test*'
|
||||
- /modular
|
||||
salt: 37517ad926e4f900863af7f1b58718cdccc34f7265dedd09d33b6f820046c510
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
// Auto-generated private secret. Do NOT commit to version control.
|
||||
// Used for CSRF nonce signing and admin rate-limit hashing. Regenerate by
|
||||
// deleting this file; the next request will write a new value.
|
||||
|
||||
return 'lsUHWFkCwvGZrL';
|
||||
@@ -5,4 +5,14 @@ date: '2026-09-01'
|
||||
date_start: '2026-09-01'
|
||||
date_end: '2026-09-08'
|
||||
cover_image: ''
|
||||
tagline: 'A week through the hill towns of southern Tuscany by bike'
|
||||
---
|
||||
|
||||
We set out from Campiglia with panniers full and no fixed plan beyond a rough
|
||||
line south through the Val d'Orcia. What followed was a week of gravel farm
|
||||
roads, long climbs into walled towns, and slow evenings that smelled of
|
||||
woodsmoke and ripe figs.
|
||||
|
||||
This is the running account of that ride — the villages that surprised us, the
|
||||
food that kept us pedalling, and the quiet stretches of road where the whole
|
||||
trip finally made sense.
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
title: Journal
|
||||
template: default
|
||||
routable: false
|
||||
visible: false
|
||||
content:
|
||||
items: '@self.children'
|
||||
order:
|
||||
by: date
|
||||
dir: desc
|
||||
filter:
|
||||
published: true
|
||||
---
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Stories
|
||||
template: default
|
||||
routable: false
|
||||
visible: false
|
||||
published: true
|
||||
---
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: 'Vestkustryten - Denmark 2026'
|
||||
template: trip
|
||||
date: '2026-07-10'
|
||||
date_start: '2026-07-10'
|
||||
date_end: '2026-07-20'
|
||||
cover_image: ''
|
||||
tagline: '10 days along the West Coast to the top of Denmark'
|
||||
published: false
|
||||
---
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
---
|
||||
title: 'New Entry'
|
||||
title: 'New journal entry'
|
||||
template: post-form
|
||||
access:
|
||||
site.login: true
|
||||
|
||||
# Keep in sync with active_trip in user/config/site.yaml
|
||||
pageconfig:
|
||||
parent: '/trips/italy-2026-demo/dailies'
|
||||
parent: /trips/italy-2026-demo/dailies
|
||||
slug_field: 'date,title'
|
||||
overwrite_mode: false
|
||||
|
||||
pagefrontmatter:
|
||||
template: entry
|
||||
published: true
|
||||
|
||||
form:
|
||||
name: new-entry
|
||||
action: /post
|
||||
@@ -26,77 +22,66 @@ form:
|
||||
autofocus: true
|
||||
validate:
|
||||
required: true
|
||||
|
||||
-
|
||||
name: date
|
||||
label: Date & Time
|
||||
label: 'Date & Time'
|
||||
type: datetime
|
||||
default: now
|
||||
format: 'Y-m-d H:i'
|
||||
validate:
|
||||
required: true
|
||||
|
||||
-
|
||||
name: content
|
||||
label: "What happened today?"
|
||||
label: 'What happened today?'
|
||||
type: textarea
|
||||
rows: 10
|
||||
validate:
|
||||
required: true
|
||||
|
||||
-
|
||||
name: photos
|
||||
label: Photos (max 4)
|
||||
label: 'Photos (max 4)'
|
||||
type: filepond
|
||||
multiple: true
|
||||
destination: '@self'
|
||||
limit: 4
|
||||
accept:
|
||||
- 'image/*'
|
||||
|
||||
-
|
||||
name: lat
|
||||
label: Latitude
|
||||
type: text
|
||||
placeholder: 'tap "Get Location" below'
|
||||
|
||||
-
|
||||
name: lng
|
||||
label: Longitude
|
||||
type: text
|
||||
placeholder: ''
|
||||
|
||||
-
|
||||
name: location_city
|
||||
label: City
|
||||
type: text
|
||||
placeholder: 'e.g. Kyoto'
|
||||
|
||||
-
|
||||
name: location_country
|
||||
label: Country
|
||||
type: text
|
||||
placeholder: 'e.g. Japan'
|
||||
|
||||
-
|
||||
name: weather_temp_c
|
||||
type: hidden
|
||||
|
||||
-
|
||||
name: weather_desc
|
||||
type: hidden
|
||||
|
||||
novalidate: true
|
||||
|
||||
buttons:
|
||||
-
|
||||
type: submit
|
||||
value: Post Daily
|
||||
value: 'Post Daily'
|
||||
classes: btn-post
|
||||
|
||||
process:
|
||||
add_page: true
|
||||
upload: true
|
||||
message: 'Entry posted successfully!'
|
||||
reset: true
|
||||
---
|
||||
|
||||
|
||||
@@ -38,10 +38,10 @@ form:
|
||||
help: 'Leave blank if trip is ongoing'
|
||||
|
||||
header.cover_image:
|
||||
type: text
|
||||
label: 'Cover Image Filename'
|
||||
placeholder: 'cover.jpg'
|
||||
help: 'Used in the trips listing page'
|
||||
type: pagemediaselect
|
||||
accept: ['.jpg', '.jpeg', '.png', '.webp', '.gif']
|
||||
label: 'Cover Image'
|
||||
help: 'Pick from images uploaded to this trip page. Shown on the trips listing and the trip-page banner. Falls back to the first journal entry photo if left unset.'
|
||||
|
||||
header.album_url:
|
||||
type: text
|
||||
|
||||
@@ -950,6 +950,66 @@ body::after {
|
||||
color: var(--color-ink-muted);
|
||||
}
|
||||
|
||||
/* ── Trip page header extras: one-liner, description, banner ──────────────────── */
|
||||
|
||||
.home-trip-tagline {
|
||||
font-size: var(--text-md);
|
||||
line-height: var(--leading-snug);
|
||||
color: var(--color-ink-2);
|
||||
margin: 0 0 var(--space-2);
|
||||
}
|
||||
|
||||
.trip-header-desc {
|
||||
margin: var(--space-3) 0 var(--space-4);
|
||||
}
|
||||
|
||||
.trip-header-desc-body {
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--leading-normal);
|
||||
color: var(--color-ink-2);
|
||||
}
|
||||
|
||||
.trip-header-desc-body > :first-child { margin-top: 0; }
|
||||
.trip-header-desc-body > :last-child { margin-bottom: 0; }
|
||||
.trip-header-desc-body p { margin: 0 0 var(--space-2); }
|
||||
|
||||
/* Collapsed preview ≈3 lines. Uses max-height (not -webkit-line-clamp) so it
|
||||
holds across the multiple <p> that markdown content renders. */
|
||||
.trip-header-desc[data-collapsed="true"] .trip-header-desc-body {
|
||||
max-height: 4.8em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.trip-header-desc-toggle {
|
||||
display: inline-block;
|
||||
margin-top: var(--space-1);
|
||||
padding: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
font: inherit;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-accent);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.trip-header-desc-toggle:hover { color: var(--color-accent-hover); }
|
||||
|
||||
.trip-header-banner {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
margin: var(--space-4) 0 0;
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
background: var(--color-border);
|
||||
}
|
||||
|
||||
.trip-header-banner img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ── Trip page filter bar ────────────────────────────────────────────────────── */
|
||||
|
||||
.feed-sort-bar {
|
||||
@@ -1045,6 +1105,7 @@ body::after {
|
||||
.home-map-col { position: static; height: 40vh; align-self: stretch; }
|
||||
.home-map { height: 40vh; }
|
||||
.home-feed-col { padding: var(--space-6) var(--space-5); }
|
||||
.trip-header-banner { height: 130px; }
|
||||
}
|
||||
|
||||
/* ── Past trips archive ──────────────────────────────────────────────────────── */
|
||||
@@ -1114,6 +1175,13 @@ body::after {
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.trip-card-tagline {
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--leading-snug);
|
||||
color: var(--color-ink-muted);
|
||||
margin: 0 0 var(--space-3);
|
||||
}
|
||||
|
||||
.trip-card-meta {
|
||||
display: flex;
|
||||
gap: var(--space-4);
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
{#
|
||||
Shared trip cover: one source of truth for cover resolution + retina
|
||||
rendering, used by both the trip-list card (trips.html.twig) and the
|
||||
trip-page banner strip (trip-feed-col.html.twig) so the two surfaces
|
||||
cannot drift (KTD2).
|
||||
|
||||
Resolution (R7 / R11):
|
||||
1. author-selected header.cover_image, when it resolves to page media;
|
||||
2. else the first published journal entry's first image;
|
||||
3. else nothing.
|
||||
A set-but-missing cover_image (deleted/moved file) is `is not defined` in
|
||||
page media, so it falls through to the auto-pick rather than rendering a
|
||||
broken image (R11). Resolution matches against `media.images` (not all
|
||||
media), so a non-image selection — e.g. a `.gpx` from the trip page's own
|
||||
media, which the picker used to offer — also falls through instead of
|
||||
routing a non-image Medium into cropResize.
|
||||
|
||||
Twig macros can only emit strings — they cannot return a Medium object —
|
||||
so resolution and rendering live together in one macro: it emits the
|
||||
wrapper + retina <img> when a cover exists, and nothing at all when none
|
||||
does (which gives R9/AE4 a clean text-only header for free).
|
||||
|
||||
Rendering (R6 / R14): cropZoom crops-to-fill (cover), so the 1x derivative is
|
||||
an actual w×h strip matching the banner/card aspect — NOT cropResize, which
|
||||
fits-inside and would hand a portrait source back as a narrow sliver that the
|
||||
wrapper's object-fit:cover then upscales into a blur. `alt` is the trip title.
|
||||
|
||||
Retina, all-or-nothing: a 2x derivative (2w×2h) is added to the `srcset` only
|
||||
when the source is genuinely wide enough to supply it without upscaling
|
||||
(`cover.width >= 2w`); otherwise the srcset is 1x only — no blurry, re-encoded
|
||||
upscale, and no odd intermediate widths. Note most imported phone photos are
|
||||
≤1440px wide, so an auto-picked fallback cover is usually 1x-only; the 2x
|
||||
kicks in mainly for an explicitly-set wide landscape `cover_image`.
|
||||
#}
|
||||
{% macro render(trip_page, alt, w, h, wrapper_class, sizes) %}
|
||||
{%- set cover = null -%}
|
||||
{%- if trip_page.header.cover_image and trip_page.media.images[trip_page.header.cover_image] is defined -%}
|
||||
{%- set cover = trip_page.media.images[trip_page.header.cover_image] -%}
|
||||
{%- else -%}
|
||||
{%- set dailies_page = grav.pages.find(trip_page.route ~ '/dailies') -%}
|
||||
{%- if dailies_page -%}
|
||||
{%- set first_entry = dailies_page.children.published()|first -%}
|
||||
{%- if first_entry and first_entry.media.images|length > 0 -%}
|
||||
{%- set cover = first_entry.media.images|first -%}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- if cover -%}
|
||||
<div class="{{ wrapper_class }}">
|
||||
<img src="{{ cover.cropZoom(w, h).url }}"
|
||||
srcset="{{ cover.cropZoom(w, h).url }} {{ w }}w{% if cover.width >= (w * 2) %}, {{ cover.cropZoom(w * 2, h * 2).url }} {{ (w * 2) }}w{% endif %}"
|
||||
sizes="{{ sizes|default('100vw') }}"
|
||||
alt="{{ alt }}"
|
||||
loading="lazy">
|
||||
</div>
|
||||
{%- endif -%}
|
||||
{% endmacro %}
|
||||
@@ -1,8 +1,16 @@
|
||||
{% import 'macros/stats.html.twig' as stats_m %}
|
||||
{% import 'macros/cycling.html.twig' as cycling_m %}
|
||||
{% import 'macros/cover.html.twig' as cover %}
|
||||
{# trip_header_extras: gated one-liner + description + banner for the trip-page
|
||||
caller only. Home's active-trip include omits it (`only`), so it defaults off
|
||||
and that header renders exactly as before (KTD4 / R12). #}
|
||||
{% set trip_header_extras = trip_header_extras|default(false) %}
|
||||
<div class="home-feed-col">
|
||||
<div class="home-trip-header">
|
||||
<h1 class="home-trip-name">{{ trip_page.title }}</h1>
|
||||
{% if trip_header_extras and trip_page.header.tagline %}
|
||||
<p class="home-trip-tagline">{{ trip_page.header.tagline }}</p>
|
||||
{% endif %}
|
||||
{% if trip_page.header.date_start %}
|
||||
<p class="trip-dates">
|
||||
{{ trip_page.header.date_start|date('d M Y') }}
|
||||
@@ -13,6 +21,15 @@
|
||||
{{ journal_count }} journal {{ journal_count == 1 ? 'entry' : 'entries' }}
|
||||
{% if story_count > 0 %} · {{ story_count }} {{ story_count == 1 ? 'story' : 'stories' }}{% endif %}
|
||||
</span>
|
||||
{% if trip_header_extras and trip_page.content|striptags|trim %}
|
||||
<div class="trip-header-desc" data-collapsed="true">
|
||||
<div class="trip-header-desc-body">{{ trip_page.content|raw }}</div>
|
||||
<button type="button" class="trip-header-desc-toggle" aria-expanded="false">Read more</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if trip_header_extras %}
|
||||
{{ cover.render(trip_page, trip_page.title, 720, 220, 'trip-header-banner', '100vw') }}
|
||||
{% endif %}
|
||||
<div class="trip-filter-bar">
|
||||
<div class="trip-filter-group">
|
||||
<button class="trip-filter-btn is-active" data-filter="all" aria-pressed="true">All content</button>
|
||||
@@ -62,4 +79,27 @@
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% if trip_header_extras %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.querySelectorAll('.trip-header-desc').forEach(function (desc) {
|
||||
var body = desc.querySelector('.trip-header-desc-body');
|
||||
var btn = desc.querySelector('.trip-header-desc-toggle');
|
||||
if (!body || !btn) return;
|
||||
// Drop the toggle when the description already fits the collapsed preview.
|
||||
if (body.scrollHeight <= body.clientHeight + 4) {
|
||||
btn.hidden = true;
|
||||
desc.removeAttribute('data-collapsed');
|
||||
return;
|
||||
}
|
||||
btn.addEventListener('click', function () {
|
||||
var collapsed = desc.getAttribute('data-collapsed') === 'true';
|
||||
desc.setAttribute('data-collapsed', collapsed ? 'false' : 'true');
|
||||
btn.setAttribute('aria-expanded', collapsed ? 'true' : 'false');
|
||||
btn.textContent = collapsed ? 'Show less' : 'Read more';
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -78,7 +78,8 @@
|
||||
has_gpx: has_gpx,
|
||||
gpx_urls: gpx_urls,
|
||||
gps_points: gps_points,
|
||||
show_sort: true
|
||||
show_sort: true,
|
||||
trip_header_extras: true
|
||||
} only %}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{% extends 'partials/base.html.twig' %}
|
||||
|
||||
{% block content %}
|
||||
{% import 'macros/cover.html.twig' as cover %}
|
||||
<h1 class="trips-heading">Past Trips</h1>
|
||||
{% set trips = page.children.published()|sort((a, b) => a.date < b.date ? 1 : -1) %}
|
||||
{% if trips|length == 0 %}
|
||||
@@ -13,21 +14,11 @@
|
||||
{% set journal_count = dailies_page ? dailies_page.children.published()|length : 0 %}
|
||||
{% set story_count = stories_page ? stories_page.children.published()|length : 0 %}
|
||||
<a class="trip-card" href="{{ trip.url }}">
|
||||
{% set cover = null %}
|
||||
{% if trip.header.cover_image and trip.media[trip.header.cover_image] is defined %}
|
||||
{% set cover = trip.media[trip.header.cover_image] %}
|
||||
{% elseif dailies_page %}
|
||||
{% set first_entry = dailies_page.children.published()|first %}
|
||||
{% if first_entry and first_entry.media.images|length > 0 %}
|
||||
{% set cover = first_entry.media.images|first %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if cover %}
|
||||
<div class="trip-card-cover">
|
||||
<img src="{{ cover.cropResize(720, 240).url }}" alt="{{ trip.title }}" loading="lazy">
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ cover.render(trip, trip.title, 720, 240, 'trip-card-cover', '(max-width: 700px) 100vw, 360px') }}
|
||||
<div class="trip-card-title">{{ trip.title }}</div>
|
||||
{% if trip.header.tagline %}
|
||||
<div class="trip-card-tagline">{{ trip.header.tagline }}</div>
|
||||
{% endif %}
|
||||
<div class="trip-card-meta">
|
||||
{% if trip.header.date_start %}
|
||||
<span class="trip-card-dates">
|
||||
|
||||
Reference in New Issue
Block a user