feat(ui,roundtrip): modern neutral “Apple Glass” UI, scenic round trips, and isochrone guidance
Some checks failed
build-and-push / docker (push) Failing after 10m2s

- UI (neutral minimal, glass-like)
  - Rewrote app shell and control panel in [app/web/static/index.html](cci:7://file:///home/pedan/freemoto/app/web/static/index.html:0:0-0:0)
  - Modernized responsive styles in [app/web/static/styles.css](cci:7://file:///home/pedan/freemoto/app/web/static/styles.css:0:0-0:0)
  - Proper internal scrolling for control panel, suggestions, waypoints, and directions sheet
  - Improved dark mode, spacing, and visual polish

- Round Trip generation
  - Direction preference (N/E/S/W) with biased waypoint bearings ([route.js](cci:7://file:///home/pedan/freemoto/app/web/static/route.js:0:0-0:0))
  - Ferry avoidance by default on round trips; added strong ferry cost
  - Coastal safety: reverse-geocode water check and automatic inland adjustment for generated waypoints
  - New “Scenic optimizer” option: generate candidate loops, route them, score twistiness, pick the best
    - Heading-change/turn-density based scoring using decoded polyline

- Isochrone-guided loops
  - Backend: added `/isochrone` proxy in [app/web/main.go](cci:7://file:///home/pedan/freemoto/app/web/main.go:0:0-0:0) (derives Valhalla base from `VALHALLA_URL`)
  - Frontend: optional “Use isochrone guidance” + time (minutes); sample preferred sector of polygon

- Settings persistence and live updates
  - Direction, scenic, isochrone, and distance persist via localStorage
  - Changing options regenerates round trips when enabled

- Docker/Build
  - Confirmed multi-arch build (amd64/arm64) and push to git.ztsw.de/pedan/freemoto/freemoto-web:latest

Refs: [index.html](cci:7://file:///home/pedan/freemoto/app/web/static/index.html:0:0-0:0), [styles.css](cci:7://file:///home/pedan/freemoto/app/web/static/styles.css:0:0-0:0), [route.js](cci:7://file:///home/pedan/freemoto/app/web/static/route.js:0:0-0:0), [main.js](cci:7://file:///home/pedan/freemoto/app/web/static/main.js:0:0-0:0), [app/web/main.go](cci:7://file:///home/pedan/freemoto/app/web/main.go:0:0-0:0), [Dockerfile](cci:7://file:///home/pedan/freemoto/Dockerfile:0:0-0:0)
This commit is contained in:
2025-09-19 14:45:21 +02:00
parent c8aa3fb3b2
commit c37ca4e8b4
5 changed files with 494 additions and 38 deletions

View File

@@ -38,6 +38,10 @@ document.addEventListener('DOMContentLoaded', function() {
var roundTripKm = document.getElementById('roundTripKm');
var roundTripBtn = document.getElementById('roundTripBtn');
var roundTripNoRepeat = document.getElementById('roundTripNoRepeat');
var roundTripDir = document.getElementById('roundTripDir');
var roundTripScenic = document.getElementById('roundTripScenic');
var roundTripIsochrone = document.getElementById('roundTripIsochrone');
var isochroneMinutes = document.getElementById('isochroneMinutes');
if (zoomInBtn && zoomOutBtn) {
zoomInBtn.addEventListener('click', function() {
map.zoomIn();
@@ -156,6 +160,9 @@ document.addEventListener('DOMContentLoaded', function() {
roundTripKm.addEventListener('change', function(){
var v = parseInt(roundTripKm.value, 10);
if (!isNaN(v)) localStorage.setItem('freemoto-roundtrip-km', String(v));
if (roundTripToggle && roundTripToggle.checked && typeof window.createRoundTrip === 'function') {
window.createRoundTrip();
}
});
}
if (roundTripBtn) {
@@ -174,6 +181,54 @@ document.addEventListener('DOMContentLoaded', function() {
});
}
// Direction preference persistence and live update
if (roundTripDir) {
var savedDir = localStorage.getItem('freemoto-roundtrip-dir') || 'any';
roundTripDir.value = savedDir;
roundTripDir.addEventListener('change', function(){
localStorage.setItem('freemoto-roundtrip-dir', roundTripDir.value);
if (roundTripToggle && roundTripToggle.checked && typeof window.createRoundTrip === 'function') {
// Regenerate the round trip to reflect new direction
window.createRoundTrip();
}
});
}
// Scenic optimizer toggle
if (roundTripScenic) {
var savedScenic = localStorage.getItem('freemoto-roundtrip-scenic') === '1';
roundTripScenic.checked = savedScenic;
roundTripScenic.addEventListener('change', function(){
localStorage.setItem('freemoto-roundtrip-scenic', roundTripScenic.checked ? '1' : '0');
if (roundTripToggle && roundTripToggle.checked && typeof window.createRoundTrip === 'function') {
window.createRoundTrip();
}
});
}
// Isochrone toggle and minutes
if (roundTripIsochrone) {
var savedIso = localStorage.getItem('freemoto-roundtrip-isochrone') === '1';
roundTripIsochrone.checked = savedIso;
roundTripIsochrone.addEventListener('change', function(){
localStorage.setItem('freemoto-roundtrip-isochrone', roundTripIsochrone.checked ? '1' : '0');
if (roundTripToggle && roundTripToggle.checked && typeof window.createRoundTrip === 'function') {
window.createRoundTrip();
}
});
}
if (isochroneMinutes) {
var savedMin = parseInt(localStorage.getItem('freemoto-isochrone-minutes') || '60', 10);
if (!isNaN(savedMin)) isochroneMinutes.value = savedMin;
isochroneMinutes.addEventListener('change', function(){
var v = parseInt(isochroneMinutes.value, 10);
if (!isNaN(v)) localStorage.setItem('freemoto-isochrone-minutes', String(v));
if (roundTripToggle && roundTripToggle.checked && typeof window.createRoundTrip === 'function') {
window.createRoundTrip();
}
});
}
// Swap start/end like Google Maps
(function(){
var swapBtn = document.getElementById('swapBtn');