aboutsummaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-02-27 10:09:16 -0800
committerAdam Soltys <[email protected]>2026-02-27 10:09:16 -0800
commitbf7d8d373c11cd7a6e1487175147c9a84e775a98 (patch)
tree82fb58568de073ff03dfca3d55d8ce5adcf51608 /src/components
parent2aa2ac29eb241fc8ded3671e728e578e72652056 (diff)
downloadcoinos-ui-bf7d8d373c11cd7a6e1487175147c9a84e775a98.tar.gz
coinos-ui-bf7d8d373c11cd7a6e1487175147c9a84e775a98.zip
map updates
Diffstat (limited to 'src/components')
-rw-r--r--src/components/About.svelte2
-rw-r--r--src/components/Footer.svelte25
-rw-r--r--src/components/LandingHero.svelte4
-rw-r--r--src/components/LocationsMap.svelte168
4 files changed, 124 insertions, 75 deletions
diff --git a/src/components/About.svelte b/src/components/About.svelte
index 874ec8db..b2f376ed 100644
--- a/src/components/About.svelte
+++ b/src/components/About.svelte
@@ -60,7 +60,7 @@
</div>
</div>
-<div>
+<div class="mb-20">
<h3 class="text-5xl font-medium mb-5 text-center">{$t("about.locations.header")}</h3>
<p class="text-secondary text-xl w-full md:w-10/12 lg:w-2/3 2xl:w-[800px] mx-auto mb-10">
{@html $t("about.locations.description")}
diff --git a/src/components/Footer.svelte b/src/components/Footer.svelte
index 8e80151c..0bdee16e 100644
--- a/src/components/Footer.svelte
+++ b/src/components/Footer.svelte
@@ -9,7 +9,6 @@
titleID: "nostr",
},
{ url: "https://t.me/coinoswallet", titleID: "telegram" },
- { url: "https://linktr.ee/coinos.io", titleID: "linktree" },
];
const companyLinks = [
@@ -20,21 +19,18 @@
<footer class="space-y-10 md:space-y-0 text-xl block md:flex justify-around pb-8 px-4">
<div class="space-y-5">
- <div class="text-center w-full">
+ <div class="text-center w-full hidden md:block">
<a href="/" aria-label="Home">
<iconify-icon noobserver icon="coinos:logo" width="224"></iconify-icon>
</a>
</div>
- <div class="flex gap-2 justify-center sm:hidden">
- <div>
- <a href="/register">
- <button class="btn">{$t("nav.startInSeconds")}</button>
- </a>
- </div>
+ <div class="w-full sm:hidden">
+ <a href="/register">
+ <button class="btn btn-accent w-full text-2xl">{$t("nav.startInSeconds")}</button>
+ </a>
</div>
- <div class="w-full flex flex-wrap sm:flex-nowrap gap-2 justify-center items-center">
+ <div class="w-full hidden sm:flex gap-2 justify-center items-center">
<DarkToggle />
- <LocaleSelector />
</div>
</div>
@@ -62,6 +58,11 @@
</ul>
</div>
- <iconify-icon noobserver icon="coinos:shield" class="flex justify-center" width="96"
- ></iconify-icon>
+ <div class="space-y-5">
+ <div class="flex justify-center">
+ <LocaleSelector />
+ </div>
+ <iconify-icon noobserver icon="coinos:shield" class="flex justify-center" width="96"
+ ></iconify-icon>
+ </div>
</footer>
diff --git a/src/components/LandingHero.svelte b/src/components/LandingHero.svelte
index dae0bf2b..44def229 100644
--- a/src/components/LandingHero.svelte
+++ b/src/components/LandingHero.svelte
@@ -27,11 +27,11 @@
{$t("landing.subheader")}
</h2>
- <div class="flex justify-center">
+ <div>
<a href="/register">
<button
use:focus
- class="btn !rounded-full bg-primary text-primary-content hover:bg-primary/80"
+ class="btn !rounded-full bg-primary text-primary-content hover:bg-primary/80 w-full text-2xl"
>
{$t("nav.startInSeconds")}
</button>
diff --git a/src/components/LocationsMap.svelte b/src/components/LocationsMap.svelte
index 185a6ae5..dbc56375 100644
--- a/src/components/LocationsMap.svelte
+++ b/src/components/LocationsMap.svelte
@@ -4,11 +4,12 @@
import Popup from "$comp/Popup.svelte";
import { back } from "$lib/utils";
- let { locations } = $props();
let map: any;
+ let maplibreRef: any;
+ let mapReady = $state(false);
let mapContainer: any = $state(),
mapWrapper: any = $state();
- let markers: any[] = [];
+ let markers: any[] = $state([]);
let search: string = $state("");
let clearSearch = (e: any) => (search = "");
@@ -20,13 +21,10 @@
if (mapWrapper.requestFullscreen) {
mapWrapper.requestFullscreen();
} else if (mapWrapper.mozRequestFullScreen) {
- /* Firefox */
mapWrapper.mozRequestFullScreen();
} else if (mapWrapper.webkitRequestFullscreen) {
- /* Chrome, Safari & Opera */
mapWrapper.webkitRequestFullscreen();
} else if (mapWrapper.msRequestFullscreen) {
- /* IE/Edge */
mapWrapper.msRequestFullscreen();
}
@@ -36,13 +34,10 @@
if (doc.exitFullscreen) {
doc.exitFullscreen();
} else if (doc.mozCancelFullScreen) {
- /* Firefox */
doc.mozCancelFullScreen();
} else if (doc.webkitExitFullscreen) {
- /* Chrome, Safari and Opera */
doc.webkitExitFullscreen();
} else if (doc.msExitFullscreen) {
- /* IE/Edge */
doc.msExitFullscreen();
}
}
@@ -84,16 +79,6 @@
await new Promise((r) => setTimeout(r, 2500));
if (!timeout) return;
- //
- // map.flyTo({
- // zoom: 10.5,
- // center: marker.getLngLat(),
- // essential: true,
- // easing: function (t) {
- // return 1 - Math.pow(1 - t, 3);
- // },
- // duration: 1000,
- // });
}
let toggle = () => {
@@ -159,8 +144,6 @@
for (let marker of markers) {
let isInView = bounds.contains(marker.getLngLat());
- let { tags } = marker;
-
if (isInView) {
inview.push(marker);
}
@@ -170,10 +153,98 @@
}, 200);
let locationMarkers: any = {};
+ let addedIds = new Set();
let popups: any = {};
let counter = 0;
let maplibrePromise: any;
+ function addMarkers(locations: any[]) {
+ let maplibre = maplibreRef;
+ if (!map || !maplibre) return;
+
+ locations.forEach((location) => {
+ if (location["deleted_at"]) return;
+ if (addedIds.has(location.id)) return;
+ addedIds.add(location.id);
+
+ let { lat, lon, tags } = location["osm_json"];
+ if (!(lat && lon)) return;
+
+ let popupContainer = document.createElement("div");
+
+ mount(Popup, {
+ target: popupContainer,
+ props: { tags },
+ });
+
+ let marker = new maplibre.Marker({ color: "#F7931A", scale: 0.65 })
+ .setLngLat([lon, lat])
+ .setPopup(new maplibre.Popup().setDOMContent(popupContainer))
+ .addTo(map);
+
+ marker.id = counter++;
+
+ marker.getElement().addEventListener("click", () => {
+ let p = marker.getPopup();
+ popups[marker.id] = p;
+ p.setDOMContent(popupContainer);
+ setTimeout(() => {
+ selected = marker;
+ setTimeout(scroll, 10);
+ }, 50);
+ p.on("close", () => {
+ selected = undefined;
+ delete popups[marker.id];
+ });
+ });
+
+ marker.tags = tags;
+ locationMarkers[location.id] = marker;
+
+ markers.push(marker);
+ });
+
+ updateLabelVisibility();
+ }
+
+ let fetchController: AbortController | null = null;
+
+ async function fetchNearby(lat: number, lon: number, radius: number) {
+ if (fetchController) fetchController.abort();
+ fetchController = new AbortController();
+
+ try {
+ let params = new URLSearchParams({
+ lat: String(lat),
+ lon: String(lon),
+ radius: String(Math.ceil(radius)),
+ count: "100",
+ });
+ let res = await fetch(`/locations?${params}`, { signal: fetchController.signal });
+ let data = await res.json();
+ if (data.locations) addMarkers(data.locations);
+ } catch (e: any) {
+ if (e.name !== "AbortError") console.log("fetch nearby failed", e);
+ }
+ }
+
+ function getViewportRadius() {
+ let bounds = map.getBounds();
+ let center = map.getCenter();
+ let ne = bounds.getNorthEast();
+
+ let dlat = ne.lat - center.lat;
+ let dlng = ne.lng - center.lng;
+ let km = Math.sqrt(dlat * dlat + dlng * dlng) * 111;
+ return Math.max(km, 10);
+ }
+
+ let fetchOnMove = debounce(() => {
+ if (!map) return;
+ let center = map.getCenter();
+ fetchNearby(center.lat, center.lng, getViewportRadius());
+ }, 500);
+
const loadMaplibre = async () => {
if ((window as any).maplibregl) return (window as any).maplibregl;
if (maplibrePromise) return maplibrePromise;
@@ -210,6 +281,8 @@
await tick();
if (!mapContainer) return;
loadMaplibre().then((maplibre) => {
+ maplibreRef = maplibre;
+
map = new maplibre.Map({
container: mapContainer,
style: "https://tiles.stadiamaps.com/styles/stamen_toner.json",
@@ -217,51 +290,26 @@
zoom: 10.5,
});
- map.on("load", () => {
- locations.forEach((location) => {
- if (location["deleted_at"]) return;
-
- let { lat, lon, tags } = location["osm_json"];
- if (!(lat && lon)) return;
-
- let element = document.createElement("div");
- element.className = "marker";
-
- let popupContainer = document.createElement("div");
+ map.scrollZoom.setWheelZoomRate(1 / 60);
+ map.scrollZoom.setZoomRate(1 / 30);
- mount(Popup, {
- target: popupContainer,
- props: { tags },
- });
+ map.on("load", () => {
+ mapReady = true;
- let marker = new maplibre.Marker({ color: "#F7931A", scale: 0.65 })
- .setLngLat([lon, lat])
- .setPopup(new maplibre.Popup().setDOMContent(popupContainer))
- .addTo(map);
+ // Initial fetch for default view
+ fetchNearby(49.26, -123.05, 50);
- marker.id = counter++;
+ // Fetch more on pan/zoom
+ map.on("moveend", fetchOnMove);
- marker.getElement().addEventListener("click", () => {
- let p = marker.getPopup();
- popups[marker.id] = p;
- p.setDOMContent(popupContainer);
- setTimeout(() => {
- selected = marker;
- setTimeout(scroll, 10);
- }, 50);
- p.on("close", () => {
- selected = undefined;
- delete popups[marker.id];
- });
+ // Center on user if available
+ if (navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition((position) => {
+ let { longitude: lng, latitude: lat } = position.coords;
+ map.flyTo({ center: [lng, lat], zoom: 11, duration: 1000 });
+ fetchNearby(lat, lng, 50);
});
-
- marker.tags = tags;
- locationMarkers[location.id] = marker;
-
- markers.push(marker);
- });
-
- updateLabelVisibility();
+ }
});
map.on("mousedown", stopFlying);