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)
246 lines
12 KiB
HTML
246 lines
12 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>FreeMoto · Route Planner</title>
|
||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||
<link href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" rel="stylesheet" />
|
||
<link href="/styles.css?v=20250918" rel="stylesheet" />
|
||
</head>
|
||
<body>
|
||
<!-- App Bar (Neutral minimal, glassy) -->
|
||
<header class="appbar navbar px-3">
|
||
<div class="container-fluid d-flex align-items-center justify-content-between">
|
||
<div class="d-flex align-items-center gap-2">
|
||
<span class="navbar-brand mb-0 h1 d-flex align-items-center gap-2">
|
||
<span aria-hidden="true">🏍️</span>
|
||
<span>FreeMoto</span>
|
||
</span>
|
||
<span id="summaryPill" class="badge rounded-pill text-bg-light d-none"></span>
|
||
</div>
|
||
<div class="d-flex align-items-center gap-2">
|
||
<div class="btn-group btn-group-sm" role="group" aria-label="Zoom controls">
|
||
<button class="btn btn-outline-secondary" id="zoomOutBtn" title="Zoom out">−</button>
|
||
<button class="btn btn-outline-secondary" id="zoomInBtn" title="Zoom in">+</button>
|
||
</div>
|
||
<button class="btn btn-outline-secondary btn-sm" id="clearRouteBtn" title="Clear route">Clear</button>
|
||
<button class="btn btn-outline-secondary btn-sm" id="themeToggle" title="Toggle dark mode">🌙</button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Map canvas -->
|
||
<div id="map" aria-label="Map"></div>
|
||
|
||
<!-- Control Panel (neutral, glassy, scrollable) -->
|
||
<aside class="nav-panel shadow">
|
||
<div class="d-flex align-items-center justify-content-between mb-2">
|
||
<div class="d-flex align-items-center gap-2">
|
||
<span class="fw-semibold">Planner</span>
|
||
</div>
|
||
<button class="btn btn-sm btn-outline-secondary d-none d-md-inline" onclick="resetMarkers()">Reset</button>
|
||
</div>
|
||
|
||
<div id="routeInfoCard" class="alert alert-info d-none" role="alert"></div>
|
||
<div id="nextManeuverBanner" class="alert alert-secondary py-2 px-3 d-none" role="status"></div>
|
||
|
||
<!-- Route inputs (compact) -->
|
||
<div class="section-title mb-1">Route</div>
|
||
<div class="row g-2 align-items-stretch">
|
||
<div class="col-12">
|
||
<div class="input-group position-relative">
|
||
<span class="input-group-text" title="Start" aria-label="Start">A</span>
|
||
<input type="text" class="form-control" id="sourceInput" placeholder="Start address" autocomplete="off" />
|
||
<button class="btn btn-outline-secondary" type="button" id="useCurrentSource" title="Use current location">📍</button>
|
||
<div id="sourceSuggestions" class="list-group position-absolute w-100"></div>
|
||
</div>
|
||
</div>
|
||
<div class="col-12 d-flex justify-content-center">
|
||
<button id="swapBtn" class="btn btn-light border rounded-circle shadow-sm" type="button" title="Swap start/end">⇅</button>
|
||
</div>
|
||
<div class="col-12">
|
||
<div class="input-group position-relative">
|
||
<span class="input-group-text" title="Destination" aria-label="Destination">B</span>
|
||
<input type="text" class="form-control" id="destInput" placeholder="Destination address" autocomplete="off" />
|
||
<button class="btn btn-outline-secondary" type="button" id="useCurrentDest" title="Use current location">📍</button>
|
||
<div id="destSuggestions" class="list-group position-absolute w-100"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Quick sliders -->
|
||
<div class="row g-3 mt-1">
|
||
<div class="col-12">
|
||
<label for="twistiness" class="form-label mb-1 text-muted">Twistiness</label>
|
||
<div class="d-flex align-items-center gap-2">
|
||
<input type="range" class="form-range" id="twistiness" min="0" max="100" value="50" />
|
||
<span id="twistinessValue" class="badge rounded-pill text-bg-light">50</span>
|
||
</div>
|
||
</div>
|
||
<div class="col-12">
|
||
<label for="highwayPref" class="form-label mb-1 text-muted">Highways</label>
|
||
<div class="d-flex align-items-center gap-2">
|
||
<input type="range" class="form-range" id="highwayPref" min="0" max="100" value="50" />
|
||
<span id="highwayPrefValue" class="badge rounded-pill text-bg-light">50</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="d-grid gap-2 my-2 d-none d-md-grid">
|
||
<button type="button" id="plotRouteBtn" class="btn btn-success">Plot Route</button>
|
||
</div>
|
||
|
||
<!-- Options (collapsible minimal) -->
|
||
<div class="accordion mb-2" id="optionsAccordion">
|
||
<div class="accordion-item">
|
||
<h2 class="accordion-header">
|
||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#optionsCollapse" aria-expanded="false" aria-controls="optionsCollapse">
|
||
Options
|
||
</button>
|
||
</h2>
|
||
<div id="optionsCollapse" class="accordion-collapse collapse" data-bs-parent="#optionsAccordion">
|
||
<div class="accordion-body">
|
||
<div class="form-check mb-1">
|
||
<input class="form-check-input" type="checkbox" id="useShortest">
|
||
<label class="form-check-label" for="useShortest">Shortest route</label>
|
||
</div>
|
||
<div class="form-check mb-1">
|
||
<input class="form-check-input" type="checkbox" id="avoidHighways">
|
||
<label class="form-check-label" for="avoidHighways">Avoid freeways</label>
|
||
</div>
|
||
<div class="form-check mb-1">
|
||
<input class="form-check-input" type="checkbox" id="avoidTollRoads">
|
||
<label class="form-check-label" for="avoidTollRoads">Avoid tolls</label>
|
||
</div>
|
||
<div class="form-check mb-1">
|
||
<input class="form-check-input" type="checkbox" id="avoidFerries">
|
||
<label class="form-check-label" for="avoidFerries">Avoid ferries</label>
|
||
</div>
|
||
<div class="form-check mb-2">
|
||
<input class="form-check-input" type="checkbox" id="avoidUnpaved">
|
||
<label class="form-check-label" for="avoidUnpaved">Avoid unpaved</label>
|
||
</div>
|
||
<hr>
|
||
<div class="row g-2 align-items-center mb-2">
|
||
<div class="col-12 col-md-6 d-flex align-items-center gap-2">
|
||
<div class="form-check form-switch">
|
||
<input class="form-check-input" type="checkbox" id="roundTripToggle">
|
||
<label class="form-check-label" for="roundTripToggle">Round Trip</label>
|
||
</div>
|
||
</div>
|
||
<div class="col-8 col-md-4">
|
||
<div class="input-group input-group-sm">
|
||
<input type="number" min="5" max="500" step="5" id="roundTripKm" class="form-control" placeholder="Distance (km)" value="100">
|
||
<span class="input-group-text">km</span>
|
||
</div>
|
||
</div>
|
||
<div class="col-4 col-md-2 d-grid">
|
||
<button type="button" id="roundTripBtn" class="btn btn-sm btn-outline-primary">Create</button>
|
||
</div>
|
||
</div>
|
||
<div class="row g-2 align-items-center mb-2">
|
||
<div class="col-12 col-md-6">
|
||
<label for="roundTripDir" class="form-label mb-0 text-muted">Direction preference</label>
|
||
</div>
|
||
<div class="col-12 col-md-6">
|
||
<select id="roundTripDir" class="form-select form-select-sm">
|
||
<option value="any" selected>Any</option>
|
||
<option value="N">North</option>
|
||
<option value="E">East</option>
|
||
<option value="S">South</option>
|
||
<option value="W">West</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="row g-2 align-items-center mb-2">
|
||
<div class="col-12 col-md-6">
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" id="roundTripScenic">
|
||
<label class="form-check-label" for="roundTripScenic">Scenic optimizer (twistier)</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="row g-2 align-items-center mb-2">
|
||
<div class="col-12 col-md-6">
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" id="roundTripIsochrone">
|
||
<label class="form-check-label" for="roundTripIsochrone">Use isochrone guidance</label>
|
||
</div>
|
||
</div>
|
||
<div class="col-12 col-md-6">
|
||
<div class="input-group input-group-sm">
|
||
<span class="input-group-text">Time</span>
|
||
<input type="number" min="10" max="240" step="5" id="isochroneMinutes" class="form-control" placeholder="Minutes" value="60">
|
||
<span class="input-group-text">min</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="row g-2 align-items-center mb-2">
|
||
<div class="col-12 col-md-6">
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" id="roundTripNoRepeat">
|
||
<label class="form-check-label" for="roundTripNoRepeat">Avoid repeated segments</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<hr>
|
||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||
<label class="form-label mb-0">Export</label>
|
||
<select id="exportMode" class="form-select form-select-sm" style="max-width: 180px;">
|
||
<option value="both" selected>Track + Route</option>
|
||
<option value="track">Track only</option>
|
||
<option value="route">Route only</option>
|
||
</select>
|
||
<div class="form-check form-switch ms-2">
|
||
<input class="form-check-input" type="checkbox" id="voiceToggle">
|
||
<label class="form-check-label" for="voiceToggle">Voice</label>
|
||
</div>
|
||
<button type="button" class="btn btn-warning btn-sm export-gpx">Export GPX</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mb-2">
|
||
<div class="section-title">Waypoints</div>
|
||
<ul id="waypointList" class="list-group small"></ul>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- Panel toggle button (mobile portrait) -->
|
||
<button id="panelToggle" class="btn btn-outline-secondary btn-sm panel-toggle d-md-none" type="button" aria-expanded="true" title="Toggle panel">☰</button>
|
||
|
||
<!-- Floating actions (mobile) -->
|
||
<div class="floating-group d-md-none">
|
||
<button class="btn btn-primary fab" id="recenterBtn" title="Recenter">◎</button>
|
||
<button class="btn btn-primary" id="plotRouteBtn" title="Plot Route">Route</button>
|
||
<button class="btn btn-dark export-gpx" title="Export GPX">GPX</button>
|
||
</div>
|
||
|
||
<!-- Directions Bottom Sheet -->
|
||
<div id="directionsSheet" class="sheet" aria-live="polite">
|
||
<div class="card shadow-lg sheet-card">
|
||
<div class="card-body p-0">
|
||
<div class="handle" role="separator" aria-label="Resize directions"></div>
|
||
<div class="sheet-header d-flex align-items-center justify-content-between px-3 py-2">
|
||
<div class="fw-semibold">Directions</div>
|
||
<button class="btn btn-sm btn-outline-secondary" id="closeDirections">Close</button>
|
||
</div>
|
||
<div class="sheet-body px-2">
|
||
<ul class="list-group list-group-flush mt-2" id="directionsList"></ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Scripts -->
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||
<script src="/main.js"></script>
|
||
<script src="/route.js"></script>
|
||
<script src="/geolocate.js"></script>
|
||
</body>
|
||
</html>
|