Tracker ordering fix + March–April fixture entries #1
@@ -0,0 +1,10 @@
|
|||||||
|
# Deny all direct web access to this folder and everything beneath it.
|
||||||
|
# Grav reads these files server-side; they must never be served over HTTP.
|
||||||
|
# This is a defense-in-depth backup for the rules in the site root .htaccess.
|
||||||
|
<IfModule mod_authz_core.c>
|
||||||
|
Require all denied
|
||||||
|
</IfModule>
|
||||||
|
<IfModule !mod_authz_core.c>
|
||||||
|
Order allow,deny
|
||||||
|
Deny from all
|
||||||
|
</IfModule>
|
||||||
+36
-2
@@ -41,20 +41,54 @@ form:
|
|||||||
title: Location
|
title: Location
|
||||||
|
|
||||||
fields:
|
fields:
|
||||||
|
header.location_city:
|
||||||
|
type: text
|
||||||
|
label: City
|
||||||
|
placeholder: 'e.g. Kyoto'
|
||||||
|
|
||||||
|
header.location_country:
|
||||||
|
type: text
|
||||||
|
label: Country
|
||||||
|
placeholder: 'e.g. Japan'
|
||||||
|
|
||||||
header.lat:
|
header.lat:
|
||||||
type: number
|
type: number
|
||||||
label: Latitude
|
label: Latitude
|
||||||
help: 'GPS latitude (for map, Milestone 2)'
|
help: 'GPS latitude (for map)'
|
||||||
placeholder: '48.8566'
|
placeholder: '48.8566'
|
||||||
step: any
|
step: any
|
||||||
|
|
||||||
header.lng:
|
header.lng:
|
||||||
type: number
|
type: number
|
||||||
label: Longitude
|
label: Longitude
|
||||||
help: 'GPS longitude (for map, Milestone 2)'
|
help: 'GPS longitude (for map)'
|
||||||
placeholder: '2.3522'
|
placeholder: '2.3522'
|
||||||
step: any
|
step: any
|
||||||
|
|
||||||
|
weather:
|
||||||
|
type: tab
|
||||||
|
title: Weather
|
||||||
|
|
||||||
|
fields:
|
||||||
|
header.weather_temp_c:
|
||||||
|
type: number
|
||||||
|
label: 'Temperature (°C)'
|
||||||
|
help: 'Auto-filled from post form. Edit if needed.'
|
||||||
|
step: 1
|
||||||
|
|
||||||
|
header.weather_desc:
|
||||||
|
type: select
|
||||||
|
label: 'Weather Condition'
|
||||||
|
options:
|
||||||
|
Sunny: '☀️ Sunny'
|
||||||
|
'Partly cloudy': '⛅ Partly cloudy'
|
||||||
|
Cloudy: '☁️ Cloudy'
|
||||||
|
Foggy: '🌫️ Foggy'
|
||||||
|
Drizzle: '🌦️ Drizzle'
|
||||||
|
Rain: '🌧️ Rain'
|
||||||
|
Snow: '❄️ Snow'
|
||||||
|
Thunderstorm: '⛈️ Thunderstorm'
|
||||||
|
|
||||||
publishing:
|
publishing:
|
||||||
type: tab
|
type: tab
|
||||||
title: Publishing
|
title: Publishing
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Deny all direct web access to this folder and everything beneath it.
|
||||||
|
# Grav reads these files server-side; they must never be served over HTTP.
|
||||||
|
# This is a defense-in-depth backup for the rules in the site root .htaccess.
|
||||||
|
<IfModule mod_authz_core.c>
|
||||||
|
Require all denied
|
||||||
|
</IfModule>
|
||||||
|
<IfModule !mod_authz_core.c>
|
||||||
|
Order allow,deny
|
||||||
|
Deny from all
|
||||||
|
</IfModule>
|
||||||
@@ -4,8 +4,12 @@ date: '2026-06-17 10:00'
|
|||||||
template: entry
|
template: entry
|
||||||
published: true
|
published: true
|
||||||
hero_image: ''
|
hero_image: ''
|
||||||
lat: ''
|
lat: '52.3676'
|
||||||
lng: ''
|
lng: '4.9041'
|
||||||
|
location_city: 'Amsterdam'
|
||||||
|
location_country: 'Netherlands'
|
||||||
|
weather_temp_c: 19
|
||||||
|
weather_desc: 'Partly cloudy'
|
||||||
---
|
---
|
||||||
|
|
||||||
First entry. Bags are packed, passport is ready, the adventure starts here.
|
First entry. Bags are packed, passport is ready, the adventure starts here.
|
||||||
|
|||||||
@@ -56,6 +56,26 @@ form:
|
|||||||
type: text
|
type: text
|
||||||
placeholder: ''
|
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
|
||||||
|
|
||||||
buttons:
|
buttons:
|
||||||
-
|
-
|
||||||
type: submit
|
type: submit
|
||||||
@@ -73,6 +93,10 @@ form:
|
|||||||
date: '{{ form.value.date }}'
|
date: '{{ form.value.date }}'
|
||||||
lat: '{{ form.value.lat }}'
|
lat: '{{ form.value.lat }}'
|
||||||
lng: '{{ form.value.lng }}'
|
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!'
|
||||||
-
|
-
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ body {
|
|||||||
padding: 1.5rem 1.25rem;
|
padding: 1.5rem 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Feed */
|
/* ── Feed ──────────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
.feed { display: flex; flex-direction: column; gap: 2rem; }
|
.feed { display: flex; flex-direction: column; gap: 2rem; }
|
||||||
|
|
||||||
.entry-card {
|
.entry-card {
|
||||||
@@ -53,15 +54,31 @@ body {
|
|||||||
color: #666;
|
color: #666;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
margin-bottom: 0.4rem;
|
margin-bottom: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.entry-card-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-card-meta .entry-date { margin-bottom: 0; }
|
||||||
|
|
||||||
.entry-card .entry-title { font-size: 1.3rem; margin-bottom: 0.75rem; }
|
.entry-card .entry-title { font-size: 1.3rem; margin-bottom: 0.75rem; }
|
||||||
.entry-card .entry-title a { color: inherit; text-decoration: none; }
|
.entry-card .entry-title a { color: inherit; text-decoration: none; }
|
||||||
.entry-card .entry-title a:hover { text-decoration: underline; }
|
.entry-card .entry-title a:hover { text-decoration: underline; }
|
||||||
|
|
||||||
.entry-thumb { margin-bottom: 0.75rem; }
|
.entry-thumb { margin-bottom: 0.75rem; }
|
||||||
.entry-thumb img { width: 100%; height: 200px; object-fit: cover; border-radius: 4px; }
|
.entry-thumb img {
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.entry-excerpt { color: #444; margin-bottom: 0.75rem; }
|
.entry-excerpt { color: #444; margin-bottom: 0.75rem; }
|
||||||
|
|
||||||
@@ -73,17 +90,231 @@ body {
|
|||||||
|
|
||||||
.feed-empty { color: #666; font-style: italic; }
|
.feed-empty { color: #666; font-style: italic; }
|
||||||
|
|
||||||
/* Single entry */
|
/* ── Location & Weather badges ─────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
.entry-location {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: #555;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-location--card {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-weather {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: #555;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Single entry ───────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
.entry-header { margin-bottom: 1.5rem; }
|
.entry-header { margin-bottom: 1.5rem; }
|
||||||
.entry-header .entry-date { margin-bottom: 0.5rem; }
|
.entry-header .entry-date { margin-bottom: 0.3rem; }
|
||||||
.entry .entry-title { font-size: 1.8rem; }
|
.entry-header .entry-location { margin-bottom: 0.2rem; display: block; }
|
||||||
|
.entry-header .entry-weather { margin-bottom: 0.75rem; }
|
||||||
|
.entry .entry-title { font-size: 1.8rem; margin-top: 0.5rem; }
|
||||||
.entry-body { margin-bottom: 2rem; }
|
.entry-body { margin-bottom: 2rem; }
|
||||||
.entry-body p { margin-bottom: 1em; }
|
.entry-body p { margin-bottom: 1em; }
|
||||||
.entry-body img { max-width: 100%; height: auto; border-radius: 4px; }
|
.entry-body img { max-width: 100%; height: auto; border-radius: 4px; }
|
||||||
.entry-footer { border-top: 1px solid #e5e5e5; padding-top: 1rem; }
|
.entry-footer { border-top: 1px solid #e5e5e5; padding-top: 1rem; }
|
||||||
.entry-footer a { color: #0066cc; text-decoration: none; font-size: 0.9rem; }
|
.entry-footer a { color: #0066cc; text-decoration: none; font-size: 0.9rem; }
|
||||||
|
|
||||||
/* Login form */
|
/* ── Photo gallery ──────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
.entry-gallery {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 4px;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 520px) {
|
||||||
|
.entry-gallery { grid-template-columns: repeat(3, 1fr); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-thumb {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-thumb img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
transition: opacity 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-thumb:hover img,
|
||||||
|
.gallery-thumb:focus img { opacity: 0.85; }
|
||||||
|
|
||||||
|
.gallery-thumb:focus { outline: 2px solid #0066cc; outline-offset: 2px; }
|
||||||
|
|
||||||
|
/* ── Lightbox ───────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
.lightbox {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0,0,0,0.92);
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox[hidden] { display: none; }
|
||||||
|
|
||||||
|
.lightbox-img {
|
||||||
|
max-width: 92vw;
|
||||||
|
max-height: 90vh;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-close,
|
||||||
|
.lightbox-prev,
|
||||||
|
.lightbox-next {
|
||||||
|
position: absolute;
|
||||||
|
background: rgba(255,255,255,0.15);
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-close { top: 1rem; right: 1rem; }
|
||||||
|
.lightbox-prev { left: 0.75rem; top: 50%; transform: translateY(-50%); }
|
||||||
|
.lightbox-next { right: 0.75rem; top: 50%; transform: translateY(-50%); }
|
||||||
|
|
||||||
|
.lightbox-close:hover,
|
||||||
|
.lightbox-prev:hover,
|
||||||
|
.lightbox-next:hover { background: rgba(255,255,255,0.28); }
|
||||||
|
|
||||||
|
/* ── Map page ───────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
.map-page .site-main { max-width: none; padding: 0; }
|
||||||
|
|
||||||
|
.map-container {
|
||||||
|
height: calc(100vh - 61px);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-empty {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
color: #666;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Stats page ─────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-block {
|
||||||
|
border: 1px solid #e5e5e5;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.25rem 1rem;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
display: block;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #0066cc;
|
||||||
|
line-height: 1.1;
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #666;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-countries {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #444;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-countries-label {
|
||||||
|
font-weight: 600;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.4rem;
|
||||||
|
color: #1a1a1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-note {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
color: #999;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Mini-map on tracker feed ───────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
.feed-map-wrap {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feed-map {
|
||||||
|
height: 240px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 520px) {
|
||||||
|
.feed-map { height: 320px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.feed-map-link {
|
||||||
|
display: block;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #0066cc;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 0.4rem 0.6rem;
|
||||||
|
background: #fafafa;
|
||||||
|
border-top: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Login form ─────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
.login-form { max-width: 400px; margin: 2rem auto; padding: 0 1rem; }
|
.login-form { max-width: 400px; margin: 2rem auto; padding: 0 1rem; }
|
||||||
.login-form .form-field { margin-bottom: 1.25rem; }
|
.login-form .form-field { margin-bottom: 1.25rem; }
|
||||||
.login-form .form-label label { display: block; font-size: 0.9rem; font-weight: 600; margin-bottom: 0.4rem; }
|
.login-form .form-label label { display: block; font-size: 0.9rem; font-weight: 600; margin-bottom: 0.4rem; }
|
||||||
@@ -101,12 +332,33 @@ body {
|
|||||||
.login-form .button.secondary { background: #f0f0f0; color: #333; text-decoration: none; line-height: 44px; padding: 0 1rem; }
|
.login-form .button.secondary { background: #f0f0f0; color: #333; text-decoration: none; line-height: 44px; padding: 0 1rem; }
|
||||||
.login-form .rememberme { display: flex; align-items: center; gap: 0.5rem; font-size: 0.9rem; }
|
.login-form .rememberme { display: flex; align-items: center; gap: 0.5rem; font-size: 0.9rem; }
|
||||||
|
|
||||||
/* Post form */
|
/* ── Post form ──────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
.post-form-wrap h1 { font-size: 1.4rem; margin-bottom: 1.5rem; }
|
.post-form-wrap h1 { font-size: 1.4rem; margin-bottom: 1.5rem; }
|
||||||
.post-form-wrap .btn-location {
|
|
||||||
display: block; width: 100%; margin-top: 1rem;
|
.form-actions-extra {
|
||||||
padding: 0.85rem 1rem; min-height: 44px;
|
display: flex;
|
||||||
background: #f0f0f0; border: 1px solid #ccc;
|
gap: 0.75rem;
|
||||||
border-radius: 6px; font-size: 1rem; cursor: pointer;
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-extra {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.75rem 0.5rem;
|
||||||
|
min-height: 44px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-extra:hover { background: #e5e5e5; }
|
||||||
|
|
||||||
|
.field-status {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #555;
|
||||||
|
margin-top: 0.4rem;
|
||||||
|
min-height: 1.2em;
|
||||||
}
|
}
|
||||||
.post-form-wrap .location-status { font-size: 0.85rem; color: #666; margin-top: 0.5rem; text-align: center; }
|
|
||||||
|
|||||||
@@ -1,16 +1,111 @@
|
|||||||
{% extends 'default.html.twig' %}
|
{% extends 'default.html.twig' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{% set weather_icons = {
|
||||||
|
'Sunny': '☀️', 'Partly cloudy': '⛅', 'Cloudy': '☁️',
|
||||||
|
'Foggy': '🌫️', 'Drizzle': '🌦️', 'Rain': '🌧️',
|
||||||
|
'Snow': '❄️', 'Thunderstorm': '⛈️'
|
||||||
|
} %}
|
||||||
|
|
||||||
<article class="entry">
|
<article class="entry">
|
||||||
<header class="entry-header">
|
<header class="entry-header">
|
||||||
<time class="entry-date" datetime="{{ page.date|date('Y-m-d') }}">
|
<time class="entry-date" datetime="{{ page.date|date('Y-m-d') }}">
|
||||||
{{ page.date|date('l, d F Y') }}
|
{{ page.date|date('l, d F Y') }}
|
||||||
</time>
|
</time>
|
||||||
|
|
||||||
|
{% if page.header.location_city or page.header.location_country %}
|
||||||
|
<p class="entry-location">
|
||||||
|
📍
|
||||||
|
{% if page.header.location_city %}{{ page.header.location_city }}{% endif %}
|
||||||
|
{% if page.header.location_city and page.header.location_country %}, {% endif %}
|
||||||
|
{% if page.header.location_country %}{{ page.header.location_country }}{% endif %}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if page.header.weather_desc or page.header.weather_temp_c %}
|
||||||
|
<p class="entry-weather">
|
||||||
|
{% if page.header.weather_desc %}
|
||||||
|
{{ weather_icons[page.header.weather_desc] ?? '🌡️' }} {{ page.header.weather_desc }}
|
||||||
|
{% endif %}
|
||||||
|
{% if page.header.weather_temp_c %}
|
||||||
|
· {{ page.header.weather_temp_c|round }}°C
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<h1 class="entry-title">{{ page.title }}</h1>
|
<h1 class="entry-title">{{ page.title }}</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="entry-body">
|
<div class="entry-body">
|
||||||
{{ page.content }}
|
{{ page.content }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% set images = page.media.images %}
|
||||||
|
{% if images|length > 0 %}
|
||||||
|
<div class="entry-gallery" id="entry-gallery">
|
||||||
|
{% for image in images %}
|
||||||
|
<button class="gallery-thumb" data-full="{{ image.url }}" data-alt="{{ image.filename }}" aria-label="View {{ image.filename }}">
|
||||||
|
<img src="{{ image.cropResize(300, 300).url }}" alt="{{ image.filename }}" loading="lazy">
|
||||||
|
</button>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lightbox" id="lightbox" role="dialog" aria-modal="true" aria-label="Photo viewer" hidden>
|
||||||
|
<button class="lightbox-close" id="lb-close" aria-label="Close">✕</button>
|
||||||
|
<button class="lightbox-prev" id="lb-prev" aria-label="Previous">‹</button>
|
||||||
|
<img class="lightbox-img" id="lb-img" src="" alt="">
|
||||||
|
<button class="lightbox-next" id="lb-next" aria-label="Next">›</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
var gallery = document.getElementById('entry-gallery');
|
||||||
|
var lightbox = document.getElementById('lightbox');
|
||||||
|
var lbImg = document.getElementById('lb-img');
|
||||||
|
var thumbs = Array.from(gallery.querySelectorAll('.gallery-thumb'));
|
||||||
|
var current = 0;
|
||||||
|
|
||||||
|
function open(index) {
|
||||||
|
current = index;
|
||||||
|
var btn = thumbs[index];
|
||||||
|
lbImg.src = btn.dataset.full;
|
||||||
|
lbImg.alt = btn.dataset.alt;
|
||||||
|
lightbox.hidden = false;
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
document.getElementById('lb-close').focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
lightbox.hidden = true;
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
thumbs[current].focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function prev() { open((current - 1 + thumbs.length) % thumbs.length); }
|
||||||
|
function next() { open((current + 1) % thumbs.length); }
|
||||||
|
|
||||||
|
thumbs.forEach(function(btn, i) {
|
||||||
|
btn.addEventListener('click', function() { open(i); });
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('lb-close').addEventListener('click', close);
|
||||||
|
document.getElementById('lb-prev').addEventListener('click', prev);
|
||||||
|
document.getElementById('lb-next').addEventListener('click', next);
|
||||||
|
|
||||||
|
lightbox.addEventListener('click', function(e) {
|
||||||
|
if (e.target === lightbox) close();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if (lightbox.hidden) return;
|
||||||
|
if (e.key === 'Escape') close();
|
||||||
|
if (e.key === 'ArrowLeft') prev();
|
||||||
|
if (e.key === 'ArrowRight') next();
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<footer class="entry-footer">
|
<footer class="entry-footer">
|
||||||
<a href="{{ base_url_absolute }}/tracker">← Back to journal</a>
|
<a href="{{ base_url_absolute }}/tracker">← Back to journal</a>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -4,11 +4,29 @@
|
|||||||
<div class="post-form-wrap">
|
<div class="post-form-wrap">
|
||||||
<h1>{{ page.title }}</h1>
|
<h1>{{ page.title }}</h1>
|
||||||
{% include 'forms/form.html.twig' ignore missing %}
|
{% include 'forms/form.html.twig' ignore missing %}
|
||||||
<button type="button" id="get-location" class="btn-location">Get Current Location</button>
|
|
||||||
<p id="location-status" class="location-status"></p>
|
<div class="form-actions-extra">
|
||||||
|
<button type="button" id="get-location" class="btn-extra">📍 Get Current Location</button>
|
||||||
|
<button type="button" id="get-weather" class="btn-extra">🌤 Get Weather</button>
|
||||||
|
</div>
|
||||||
|
<p id="location-status" class="field-status"></p>
|
||||||
|
<p id="weather-status" class="field-status"></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
var WMO_MAP = {
|
||||||
|
0:'Sunny',1:'Partly cloudy',2:'Partly cloudy',3:'Cloudy',
|
||||||
|
45:'Foggy',48:'Foggy',
|
||||||
|
51:'Drizzle',53:'Drizzle',55:'Drizzle',56:'Drizzle',57:'Drizzle',
|
||||||
|
61:'Rain',63:'Rain',65:'Rain',66:'Rain',67:'Rain',80:'Rain',81:'Rain',82:'Rain',
|
||||||
|
71:'Snow',73:'Snow',75:'Snow',77:'Snow',85:'Snow',86:'Snow',
|
||||||
|
95:'Thunderstorm',96:'Thunderstorm',99:'Thunderstorm'
|
||||||
|
};
|
||||||
|
|
||||||
|
function getField(name) {
|
||||||
|
return document.querySelector('input[name="data[' + name + ']"]');
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('get-location').addEventListener('click', function() {
|
document.getElementById('get-location').addEventListener('click', function() {
|
||||||
var status = document.getElementById('location-status');
|
var status = document.getElementById('location-status');
|
||||||
status.textContent = 'Getting location…';
|
status.textContent = 'Getting location…';
|
||||||
@@ -19,14 +37,45 @@ document.getElementById('get-location').addEventListener('click', function() {
|
|||||||
navigator.geolocation.getCurrentPosition(function(pos) {
|
navigator.geolocation.getCurrentPosition(function(pos) {
|
||||||
var lat = pos.coords.latitude.toFixed(6);
|
var lat = pos.coords.latitude.toFixed(6);
|
||||||
var lng = pos.coords.longitude.toFixed(6);
|
var lng = pos.coords.longitude.toFixed(6);
|
||||||
var latField = document.querySelector('input[name="data[lat]"]');
|
var latField = getField('lat');
|
||||||
var lngField = document.querySelector('input[name="data[lng]"]');
|
var lngField = getField('lng');
|
||||||
if (latField) latField.value = lat;
|
if (latField) latField.value = lat;
|
||||||
if (lngField) lngField.value = lng;
|
if (lngField) lngField.value = lng;
|
||||||
status.textContent = 'Location set: ' + lat + ', ' + lng;
|
status.textContent = '📍 Location set: ' + lat + ', ' + lng;
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
status.textContent = 'Could not get location: ' + err.message;
|
status.textContent = 'Could not get location: ' + err.message;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('get-weather').addEventListener('click', function() {
|
||||||
|
var status = document.getElementById('weather-status');
|
||||||
|
var latField = getField('lat');
|
||||||
|
var lngField = getField('lng');
|
||||||
|
var lat = latField ? latField.value.trim() : '';
|
||||||
|
var lng = lngField ? lngField.value.trim() : '';
|
||||||
|
if (!lat || !lng) {
|
||||||
|
status.textContent = 'Enter or get coordinates first.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
status.textContent = 'Fetching weather…';
|
||||||
|
var url = 'https://api.open-meteo.com/v1/forecast?latitude=' + lat +
|
||||||
|
'&longitude=' + lng +
|
||||||
|
'¤t=temperature_2m,weather_code&temperature_unit=celsius';
|
||||||
|
fetch(url)
|
||||||
|
.then(function(r) { return r.json(); })
|
||||||
|
.then(function(data) {
|
||||||
|
var temp = Math.round(data.current.temperature_2m);
|
||||||
|
var code = data.current.weather_code;
|
||||||
|
var desc = WMO_MAP[code] || 'Cloudy';
|
||||||
|
var tempField = getField('weather_temp_c');
|
||||||
|
var descField = getField('weather_desc');
|
||||||
|
if (tempField) tempField.value = temp;
|
||||||
|
if (descField) descField.value = desc;
|
||||||
|
status.textContent = '🌤 Weather set: ' + desc + ' · ' + temp + '°C';
|
||||||
|
})
|
||||||
|
.catch(function() {
|
||||||
|
status.textContent = 'Could not fetch weather — enter manually if needed.';
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -6,17 +6,39 @@
|
|||||||
{% if entries|length > 0 %}
|
{% if entries|length > 0 %}
|
||||||
{% for entry in entries %}
|
{% for entry in entries %}
|
||||||
<article class="entry-card">
|
<article class="entry-card">
|
||||||
<time class="entry-date" datetime="{{ entry.date|date('Y-m-d') }}">
|
{% set hero = null %}
|
||||||
{{ entry.date|date('d M Y') }}
|
{% if entry.header.hero_image and entry.media[entry.header.hero_image] is defined %}
|
||||||
</time>
|
{% set hero = entry.media[entry.header.hero_image] %}
|
||||||
|
{% elseif entry.media.images|length > 0 %}
|
||||||
|
{% set hero = entry.media.images|first %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if hero %}
|
||||||
|
<div class="entry-thumb">
|
||||||
|
<a href="{{ entry.url }}">
|
||||||
|
<img src="{{ hero.cropResize(680, 383).url }}" alt="{{ entry.title }}" loading="lazy">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="entry-card-meta">
|
||||||
|
<time class="entry-date" datetime="{{ entry.date|date('Y-m-d') }}">
|
||||||
|
{{ entry.date|date('d M Y') }}
|
||||||
|
</time>
|
||||||
|
{% if entry.header.location_city or entry.header.location_country %}
|
||||||
|
<span class="entry-location entry-location--card">
|
||||||
|
📍
|
||||||
|
{% if entry.header.location_city %}{{ entry.header.location_city|slice(0,25) }}{% 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>
|
||||||
|
|
||||||
<h2 class="entry-title">
|
<h2 class="entry-title">
|
||||||
<a href="{{ entry.url }}">{{ entry.title }}</a>
|
<a href="{{ entry.url }}">{{ entry.title }}</a>
|
||||||
</h2>
|
</h2>
|
||||||
{% if entry.header.hero_image %}
|
|
||||||
<div class="entry-thumb">
|
|
||||||
<img src="{{ entry.media[entry.header.hero_image].url }}" alt="{{ entry.title }}">
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="entry-excerpt">
|
<div class="entry-excerpt">
|
||||||
{{ entry.summary }}
|
{{ entry.summary }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user