Replace the unreliable peek-strip approach with a floating '☰ Controls' button on the map. The button is always clearly visible, hides when the panel is open, and reappears when the panel closes. Also fix two iOS Safari issues that were hiding the sidebar entirely: - overflow:hidden on body clips position:fixed elements in mobile Safari; reset to overflow:visible in the mobile media query - Add viewport-fit=cover and env(safe-area-inset-bottom) padding so the sidebar clears the home indicator / browser toolbar - Use 100dvh instead of 100vh to avoid iOS address-bar overflow Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
152 lines
6.3 KiB
HTML
152 lines
6.3 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||
<title>{% if route_name %}{{ route_name }} – {% endif %}OSM Public Transport → GeoJSON</title>
|
||
{% if route_name %}<meta property="og:title" content="{{ route_name }} – OSM Public Transport → GeoJSON">{% endif %}
|
||
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}">
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
|
||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||
</head>
|
||
<body>
|
||
|
||
<nav class="navbar navbar-dark bg-dark px-3" style="height:56px">
|
||
<a class="navbar-brand" href="{{ url_for('index') }}">OSM Public Transport → GeoJSON</a>
|
||
<a class="nav-link text-white" href="{{ url_for('docs') }}">API docs</a>
|
||
</nav>
|
||
|
||
<div class="container-fluid h-100 p-0">
|
||
<div class="row g-0" id="main-row">
|
||
|
||
<!-- Sidebar -->
|
||
<div class="col-3 border-end" id="sidebar">
|
||
|
||
<!-- Mobile bottom-sheet handle (hidden on desktop) -->
|
||
<div id="sidebar-handle" class="d-flex d-md-none align-items-center gap-2 px-3">
|
||
<div id="sidebar-handle-pill"></div>
|
||
<span id="sidebar-handle-label" class="small text-muted">Controls & stops</span>
|
||
</div>
|
||
|
||
<!-- Scrollable content area -->
|
||
<div id="sidebar-inner">
|
||
|
||
<!-- Load form -->
|
||
<form method="post" action="{{ url_for('load') }}" class="mb-3">
|
||
<label class="form-label fw-semibold small">Relation ID or OSM URL</label>
|
||
<div class="input-group input-group-sm">
|
||
<input type="text" name="relation" class="form-control"
|
||
placeholder="e.g. 15083963"
|
||
value="{{ relation_id or '' }}">
|
||
<button class="btn btn-primary" type="submit">Load</button>
|
||
</div>
|
||
</form>
|
||
|
||
<!-- Error alert -->
|
||
{% if error %}
|
||
<div class="alert alert-danger alert-dismissible py-2 small" role="alert">
|
||
{{ error }}
|
||
<button type="button" class="btn-close btn-sm" data-bs-dismiss="alert"></button>
|
||
</div>
|
||
{% endif %}
|
||
<div id="js-alert" class="alert alert-danger alert-dismissible py-2 small d-none" role="alert">
|
||
<span id="js-alert-msg"></span>
|
||
<button type="button" class="btn-close btn-sm" data-bs-dismiss="alert"></button>
|
||
</div>
|
||
|
||
<!-- Route master panel (shown when a route_master relation is loaded) -->
|
||
<div id="route-master-panel" class="d-none">
|
||
<div class="mb-3">
|
||
<div class="fw-semibold" id="route-master-name"></div>
|
||
<a id="route-master-osm-link" class="small text-muted" target="_blank" rel="noopener">View on OSM ↗</a>
|
||
</div>
|
||
<div class="fw-semibold small mb-1">Routes</div>
|
||
<div id="route-master-list"></div>
|
||
</div>
|
||
|
||
<!-- Route info (hidden until loaded) -->
|
||
<div id="route-panel" class="d-none">
|
||
<div class="mb-3">
|
||
<div class="fw-semibold" id="route-name"></div>
|
||
<a id="route-osm-link" class="small text-muted" target="_blank" rel="noopener">View on OSM ↗</a>
|
||
</div>
|
||
|
||
<!-- From / To slots -->
|
||
<div class="mb-1">
|
||
<label class="form-label fw-semibold small mb-1">From</label>
|
||
<div class="slot-box active" id="slot-from" title="Click to make active, then click a stop">
|
||
<span class="placeholder" id="slot-from-text">Click a stop…</span>
|
||
<span class="pencil" id="slot-from-pencil">✎</span>
|
||
</div>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label fw-semibold small mb-1">To</label>
|
||
<div class="slot-box" id="slot-to" title="Click to make active, then click a stop">
|
||
<span class="placeholder" id="slot-to-text">Click a stop…</span>
|
||
<span class="pencil d-none" id="slot-to-pencil">✎</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Clear button -->
|
||
<button class="btn btn-outline-secondary btn-sm w-100 mb-3" id="btn-clear" type="button">
|
||
✕ Clear selection
|
||
</button>
|
||
|
||
<!-- Include stops toggle -->
|
||
<div class="form-check mb-3">
|
||
<input class="form-check-input" type="checkbox" id="include-stops" checked>
|
||
<label class="form-check-label small" for="include-stops">Include stops in GeoJSON</label>
|
||
</div>
|
||
|
||
<!-- Download buttons -->
|
||
<div class="d-grid gap-2 mb-3">
|
||
<button class="btn btn-outline-secondary btn-sm disabled" id="btn-download-segment">
|
||
↓ Download segment
|
||
</button>
|
||
<button class="btn btn-outline-secondary btn-sm" id="btn-download-full">
|
||
↓ Download full route
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Other directions -->
|
||
<div id="other-directions-panel" class="d-none mb-3">
|
||
<div class="fw-semibold small mb-1">Other directions</div>
|
||
<div id="other-directions-list"></div>
|
||
</div>
|
||
|
||
<!-- Stop list -->
|
||
<div class="fw-semibold small mb-1">Stops <span id="stop-count" class="text-muted"></span></div>
|
||
<div id="stop-list"></div>
|
||
</div>
|
||
|
||
</div><!-- /sidebar-inner -->
|
||
</div><!-- /sidebar -->
|
||
|
||
<!-- Map -->
|
||
<div class="col-9" id="map">
|
||
<!-- Mobile floating button to open the panel (hidden on desktop) -->
|
||
<button id="panel-fab" class="d-md-none" type="button" aria-label="Open controls">
|
||
<span id="panel-fab-icon">☰</span>
|
||
<span id="panel-fab-label">Controls</span>
|
||
</button>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<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>
|
||
// Injected by Flask so app.js works correctly under any mount path.
|
||
const RELATION_ID = {{ relation_id | tojson }};
|
||
const URLS = {
|
||
routeApi: {{ url_for('api_route', relation_id=0)[:-1] | tojson }},
|
||
segmentApi: {{ url_for('api_segment', relation_id=0)[:-1] | tojson }},
|
||
routeMasterApi: {{ url_for('api_route_master', relation_id=0)[:-1] | tojson }},
|
||
routePage: {{ url_for('route_page', relation_id=0)[:-1] | tojson }},
|
||
};
|
||
</script>
|
||
<script src="{{ url_for('static', filename='app.js') }}"></script>
|
||
</body>
|
||
</html>
|