diff --git a/AGENTS.md b/AGENTS.md
index 03a276a..c6c42f7 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -221,7 +221,17 @@ session with **no other memory of this project**. So:
transient watch error (timeout/temporarily unavailable) must **not** tear down
the watcher — only `denied` does (headless CDP testing made this obvious:
moving the mocked position emits a transient error that recovered).
- Still deliberately absent: voice guidance and re-routing if you leave the route. **Transit** mode uses **Transitous** (`api.transitous.org`,
+ Still deliberately absent: voice guidance and re-routing if you leave the route.
+ - **Dialog layout (2026-09-27):** app-like flex column —
+ `flex max-h-[calc(100dvh-2rem)] flex-col overflow-hidden`: header, mode row,
+ "From" row and the **map** are `shrink-0` (map `h-[38vh] sm:h-[400px]`), the
+ middle (stat tiles + steps) is the only scroll region (`min-h-0 flex-1
+ overflow-y-auto`), and the **Start/Stop bar is pinned** at the bottom so it's
+ always reachable. ⚠️ Don't go back to a single `overflow-hidden` content box:
+ that made everything below the fold **unreachable** once the map grew. The
+ summary is three `StatTile`s (distance / time / arrive) + a detail line
+ (steps, driving-roads note, avg speed) and the map carries a metric
+ `L.control.scale`. **Transit** mode uses **Transitous** (`api.transitous.org`,
MOTIS 2, open CORS) — note origin/destination go to that service, unlike road
modes. **No external map-app handoff anywhere** (product decision) and no
scooter/moped profile. **Speed (2026-09-27):** `RouteDialog` seeds origin from
diff --git a/release-notes.txt b/release-notes.txt
index 4e1baba..0d50ba4 100644
--- a/release-notes.txt
+++ b/release-notes.txt
@@ -1,6 +1,9 @@
v1.5.3
🧭 Real navigation, not just a route preview
+- A much bigger map (with a metric scale bar), and the plan now reads at a
+ glance: distance, time and arrival time as clear stats, plus step count and
+ the average speed the route assumes. Start/Stop stays pinned at the bottom.
- Travel modes finally give genuinely different answers: walking 13 min, biking
5, driving 2 on the same trip. Before, every mode showed the same number.
- Route steps — the actual turns and street names. Tap a step to jump to it on
diff --git a/src/components/RouteDialog.tsx b/src/components/RouteDialog.tsx
index 893b6c5..b4ef27d 100644
--- a/src/components/RouteDialog.tsx
+++ b/src/components/RouteDialog.tsx
@@ -94,6 +94,27 @@ interface RouteDialogProps {
* maps app is opened — the route comes from the keyless public OSRM server and
* is drawn here. See src/lib/routing.ts for the honest scope notes.
*/
+/** Big number + label tile used in the route summary. */
+function StatTile({ label, value, sub }: { label: string; value: string; sub?: string }) {
+ return (
+
+
{value}
+
+ {label}
+
+ {sub &&
{sub}
}
+
+ );
+}
+
+/** The average speed the routing engine assumed for this mode, e.g. "21 km/h avg". */
+function speedLabel(distanceM: number, durationS: number): string | undefined {
+ if (!(distanceM > 0) || !(durationS > 0)) return undefined;
+ const kmh = distanceM / 1000 / (durationS / 3600);
+ if (!Number.isFinite(kmh) || kmh <= 0) return undefined;
+ return `${kmh < 10 ? kmh.toFixed(1) : Math.round(kmh)} km/h avg`;
+}
+
/** Local clock time you'd arrive if you set off now. */
function etaClock(durationS: number): string {
return new Date(Date.now() + durationS * 1000).toLocaleTimeString([], {
@@ -322,6 +343,9 @@ export function RouteDialog({
className: 'skatespots-dark-tiles',
}).addTo(map);
+ // Metric scale bar — genuinely useful on a route map (how long is that line?).
+ L.control.scale({ imperial: false, position: 'topleft' }).addTo(map);
+
// Destination marker
const destIcon = L.divIcon({
className: 'route-dest-icon',
@@ -489,8 +513,10 @@ export function RouteDialog({
return (