Phase 4 M1: Entry enrichment — location, weather, gallery, hero image
This commit is contained in:
@@ -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
|
||||
|
||||
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:
|
||||
type: number
|
||||
label: Latitude
|
||||
help: 'GPS latitude (for map, Milestone 2)'
|
||||
help: 'GPS latitude (for map)'
|
||||
placeholder: '48.8566'
|
||||
step: any
|
||||
|
||||
header.lng:
|
||||
type: number
|
||||
label: Longitude
|
||||
help: 'GPS longitude (for map, Milestone 2)'
|
||||
help: 'GPS longitude (for map)'
|
||||
placeholder: '2.3522'
|
||||
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:
|
||||
type: tab
|
||||
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
|
||||
published: true
|
||||
hero_image: ''
|
||||
lat: ''
|
||||
lng: ''
|
||||
lat: '52.3676'
|
||||
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.
|
||||
|
||||
@@ -56,6 +56,26 @@ form:
|
||||
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
|
||||
|
||||
buttons:
|
||||
-
|
||||
type: submit
|
||||
@@ -73,6 +93,10 @@ form:
|
||||
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!'
|
||||
-
|
||||
|
||||
@@ -39,7 +39,8 @@ body {
|
||||
padding: 1.5rem 1.25rem;
|
||||
}
|
||||
|
||||
/* Feed */
|
||||
/* ── Feed ──────────────────────────────────────────────────────────────────── */
|
||||
|
||||
.feed { display: flex; flex-direction: column; gap: 2rem; }
|
||||
|
||||
.entry-card {
|
||||
@@ -53,15 +54,31 @@ body {
|
||||
color: #666;
|
||||
text-transform: uppercase;
|
||||
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 a { color: inherit; text-decoration: none; }
|
||||
.entry-card .entry-title a:hover { text-decoration: underline; }
|
||||
|
||||
.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; }
|
||||
|
||||
@@ -73,17 +90,231 @@ body {
|
||||
|
||||
.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 .entry-date { margin-bottom: 0.5rem; }
|
||||
.entry .entry-title { font-size: 1.8rem; }
|
||||
.entry-header .entry-date { margin-bottom: 0.3rem; }
|
||||
.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 p { margin-bottom: 1em; }
|
||||
.entry-body img { max-width: 100%; height: auto; border-radius: 4px; }
|
||||
.entry-footer { border-top: 1px solid #e5e5e5; padding-top: 1rem; }
|
||||
.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 .form-field { margin-bottom: 1.25rem; }
|
||||
.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 .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 .btn-location {
|
||||
display: block; width: 100%; margin-top: 1rem;
|
||||
padding: 0.85rem 1rem; min-height: 44px;
|
||||
background: #f0f0f0; border: 1px solid #ccc;
|
||||
border-radius: 6px; font-size: 1rem; cursor: pointer;
|
||||
|
||||
.form-actions-extra {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
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' %}
|
||||
|
||||
{% block content %}
|
||||
{% set weather_icons = {
|
||||
'Sunny': '☀️', 'Partly cloudy': '⛅', 'Cloudy': '☁️',
|
||||
'Foggy': '🌫️', 'Drizzle': '🌦️', 'Rain': '🌧️',
|
||||
'Snow': '❄️', 'Thunderstorm': '⛈️'
|
||||
} %}
|
||||
|
||||
<article class="entry">
|
||||
<header class="entry-header">
|
||||
<time class="entry-date" datetime="{{ page.date|date('Y-m-d') }}">
|
||||
{{ page.date|date('l, d F Y') }}
|
||||
</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>
|
||||
</header>
|
||||
|
||||
<div class="entry-body">
|
||||
{{ page.content }}
|
||||
</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">
|
||||
<a href="{{ base_url_absolute }}/tracker">← Back to journal</a>
|
||||
</footer>
|
||||
|
||||
@@ -4,11 +4,29 @@
|
||||
<div class="post-form-wrap">
|
||||
<h1>{{ page.title }}</h1>
|
||||
{% 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>
|
||||
|
||||
<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() {
|
||||
var status = document.getElementById('location-status');
|
||||
status.textContent = 'Getting location…';
|
||||
@@ -19,14 +37,45 @@ document.getElementById('get-location').addEventListener('click', function() {
|
||||
navigator.geolocation.getCurrentPosition(function(pos) {
|
||||
var lat = pos.coords.latitude.toFixed(6);
|
||||
var lng = pos.coords.longitude.toFixed(6);
|
||||
var latField = document.querySelector('input[name="data[lat]"]');
|
||||
var lngField = document.querySelector('input[name="data[lng]"]');
|
||||
var latField = getField('lat');
|
||||
var lngField = getField('lng');
|
||||
if (latField) latField.value = lat;
|
||||
if (lngField) lngField.value = lng;
|
||||
status.textContent = 'Location set: ' + lat + ', ' + lng;
|
||||
status.textContent = '📍 Location set: ' + lat + ', ' + lng;
|
||||
}, function(err) {
|
||||
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>
|
||||
{% endblock %}
|
||||
|
||||
@@ -6,17 +6,39 @@
|
||||
{% if entries|length > 0 %}
|
||||
{% for entry in entries %}
|
||||
<article class="entry-card">
|
||||
{% set hero = null %}
|
||||
{% if entry.header.hero_image and entry.media[entry.header.hero_image] is defined %}
|
||||
{% set hero = entry.media[entry.header.hero_image] %}
|
||||
{% elseif entry.media.images|length > 0 %}
|
||||
{% set hero = entry.media.images|first %}
|
||||
{% endif %}
|
||||
|
||||
{% if hero %}
|
||||
<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">
|
||||
<a href="{{ entry.url }}">{{ entry.title }}</a>
|
||||
</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">
|
||||
{{ entry.summary }}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user