sync: 2026-08-21 15:04:46

This commit is contained in:
2026-08-21 15:04:46 +02:00
parent 26d9a0dcf7
commit 6d7709e008
3 changed files with 579 additions and 87 deletions

View File

@@ -634,6 +634,89 @@
font-size: 10px;
}
/* Permanent name label shown above each pin */
.pin-label {
background: rgba(255, 255, 255, 0.92);
border: none;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
color: #202124;
font-size: 12px;
font-weight: 500;
padding: 2px 7px;
white-space: nowrap;
}
.pin-label::before {
display: none;
}
/* Custom circular pin that reflects the POI type */
.poi-pin {
background: transparent;
border: none;
}
.poi-pin-inner {
width: 34px;
height: 34px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 17px;
line-height: 1;
color: white;
border: 2px solid white;
box-shadow:
0 2px 6px rgba(0, 0, 0, 0.35);
}
/* User location marker (blue dot) */
.user-loc-marker {
background: transparent;
border: none;
}
.user-loc-dot {
position: relative;
width: 18px;
height: 18px;
border-radius: 50%;
background: #1a73e8;
border: 3px solid white;
box-shadow:
0 0 0 2px rgba(26, 115, 232, 0.35),
0 2px 6px rgba(0, 0, 0, 0.4);
}
.user-loc-dot::after {
content: "";
position: absolute;
inset: -10px;
border-radius: 50%;
background:
radial-gradient(
circle,
rgba(26, 115, 232, 0.25) 0%,
rgba(26, 115, 232, 0) 70%
);
}
/* =====================================================
MOBILE
@@ -931,6 +1014,8 @@
let selectedMarker = null;
let userLocationMarker = null;
let zoomMessageTimer = null;
@@ -977,55 +1062,55 @@
].join(",");
try {
let data =
await performSearch(
query,
viewbox
);
const response =
await fetch(
"/search?q=" +
encodeURIComponent(
query + ", "
) +
"&viewbox=" +
encodeURIComponent(viewbox)
/*
* If nothing was found in the visible area,
* fall back to searching the entire world.
*/
if (!data.length) {
data =
await performSearch(
query,
null
);
}
const data =
await response.json();
if (
data === null
) {
if (
!response.ok ||
data.error
) {
resultsCard.innerHTML = `
<div class="result">
<div class="result-text">
${escapeHtml(
data.error ||
"Search failed"
)}
</div>
resultsCard.innerHTML = `
<div class="result">
<div class="result-text">
Search failed
</div>
`;
</div>
`;
return;
}
return;
}
if (!data.length) {
if (!data.length) {
resultsCard.innerHTML = `
<div class="result">
<div class="result-text">
No results found
</div>
resultsCard.innerHTML = `
<div class="result">
<div class="result-text">
No results found
</div>
`;
</div>
`;
return;
}
return;
}
resultsCard.innerHTML = "";
@@ -1101,11 +1186,19 @@
L.marker([
place.lat,
place.lon
]).addTo(map);
], {
icon: createPoiIcon(place)
}).addTo(map);
marker.bindTooltip(
getName(place)
escapeHtml(getName(place)),
{
permanent: true,
direction: "top",
offset: [0, -20],
className: "pin-label"
}
);
@@ -1136,9 +1229,11 @@
if (bounds.length === 1) {
const zoom = getZoomForPlace(data[0]);
map.flyTo(
bounds[0],
17,
zoom,
{
duration: 0.7
}
@@ -1158,19 +1253,53 @@
);
}
}
catch (error) {
}
console.error(error);
resultsCard.innerHTML = `
<div class="result">
<div class="result-text">
Search failed
</div>
</div>
`;
/* =========================================================
PERFORM SEARCH
========================================================= */
async function performSearch(
query,
viewbox
) {
let url =
"/search?q=" +
encodeURIComponent(
query + ", "
);
if (viewbox) {
url +=
"&viewbox=" +
encodeURIComponent(
viewbox
);
}
const response =
await fetch(url);
const data =
await response.json();
if (
!response.ok ||
data.error
) {
return null;
}
return data;
}
@@ -1178,6 +1307,51 @@
SELECT PLACE
========================================================= */
/*
* Determine an appropriate zoom level based on the
* kind and type of the selected place.
*/
function getZoomForPlace(place) {
const kind = place.kind || "other";
const type = (place.type || "").toLowerCase();
// POIs: zoom in close to see the building/spot.
if (kind === "poi") {
return 17;
}
// Addresses: street-level view.
if (kind === "address") {
return 18;
}
// Areas: vary by administrative level.
if (kind === "area") {
if (type === "country" || type === "state" || type === "region") {
return 6;
}
if (type === "county" || type === "municipality") {
return 9;
}
if (type === "city" || type === "town") {
return 13;
}
if (type === "village" || type === "hamlet") {
return 14;
}
if (type === "suburb" || type === "neighbourhood" || type === "quarter") {
return 15;
}
// Water bodies, natural features, etc.
return 12;
}
// Default fallback.
return 15;
}
function selectPlace(place) {
placeCard.style.display =
@@ -1202,12 +1376,15 @@
]).addTo(map);
const zoom = getZoomForPlace(place);
map.flyTo(
[
place.lat,
place.lon
],
17,
zoom,
{
duration: 0.7
}
@@ -2006,51 +2183,125 @@
LOCATION BUTTON
========================================================= */
function showUserLocation(lat, lon) {
const coords = [lat, lon];
if (!userLocationMarker) {
userLocationMarker =
L.marker(coords, {
icon: createUserLocationIcon(),
zIndexOffset: 1000
}).addTo(map);
} else {
userLocationMarker.setLatLng(coords);
}
map.flyTo(
coords,
16,
{
duration: 0.8
}
);
}
function locateMe() {
if (
!navigator.geolocation
) {
alert(
"Geolocation is not supported."
);
return;
}
navigator.geolocation
.getCurrentPosition(
position => {
showUserLocation(
position.coords.latitude,
position.coords.longitude
);
},
error => {
console.warn(
"Geolocation failed:",
error
);
alert(
"Could not determine your location."
);
}
);
}
document
.getElementById(
"location-button"
)
.addEventListener(
"click",
() => {
if (
!navigator.geolocation
) {
alert(
"Geolocation is not supported."
);
return;
}
navigator.geolocation
.getCurrentPosition(
position => {
map.flyTo(
[
position.coords.latitude,
position.coords.longitude
],
16
);
},
() => {
alert(
"Could not determine your location."
);
}
);
}
locateMe
);
/*
* On launch, focus the map around the viewer's device
* location and drop a pin there. If the location cannot
* be determined, fall back to Amsterdam.
*/
window.addEventListener(
"load",
() => {
if (
!navigator.geolocation
) {
return;
}
navigator.geolocation
.getCurrentPosition(
position => {
showUserLocation(
position.coords.latitude,
position.coords.longitude
);
},
() => {
/*
* Location unavailable — keep the
* default Amsterdam view.
*/
}
);
}
);
/* =========================================================
ZOOM MESSAGE
========================================================= */
@@ -2346,6 +2597,96 @@
}
function getPoiColor(place) {
const type =
getType(place)
.toLowerCase();
if (
type.includes("restaurant") ||
type.includes("cafe") ||
type.includes("food")
)
return "#e8710a";
if (
type.includes("hotel") ||
type.includes("hostel")
)
return "#7b1fa2";
if (
type.includes("museum")
)
return "#5c6bc0";
if (
type.includes("shop") ||
type.includes("supermarket")
)
return "#43a047";
if (
type.includes("station") ||
type.includes("transport")
)
return "#0288d1";
if (
type.includes("pharmacy")
)
return "#e53935";
if (
type.includes("park")
)
return "#2e7d32";
return "#1a73e8";
}
function createPoiIcon(place) {
const html = `
<div
class="poi-pin-inner"
style="background:${getPoiColor(place)};"
>
${getIcon(place)}
</div>
`;
return L.divIcon({
className: "poi-pin",
html: html,
iconSize: [34, 34],
iconAnchor: [17, 17]
});
}
function createUserLocationIcon() {
return L.divIcon({
className: "user-loc-marker",
html: `<div class="user-loc-dot"></div>`,
iconSize: [18, 18],
iconAnchor: [9, 9]
});
}
function safeUrl(url) {
if (!url)