From 46fbb0df43176082b9ad06a6398bcc57c1b6d606 Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Mon, 14 Sep 2026 09:54:07 +0700 Subject: [PATCH] Map loading: replace fuzzy ghost-pin blobs with a clean top bar + pill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cold-start loading state scattered fuzzy pink "ghost pins" across the map (the same blob look that was disliked on real markers) which read as broken. Removed MapSkeletonPins entirely. New loading state, per the agreed plan (keep the map instant, never cover it): - A slim animated top progress bar ("ss-loadbar" sweep on a faint track) that sits above the map while spots stream in. - During a genuine cold start (no markers yet), a small labeled "Loading spots" pill with a spinner near the top — honest about what's happening (querying relays), not fake pins on the ocean. - Reduced-motion respected (bar becomes static). The map itself is untouched and still renders immediately; only the placeholder changed. Dropped the now-unused MapSkeletonPins import. Build clean, no console errors. Co-authored-by: shakespeare.diy --- src/index.css | 14 ++++++++++++++ src/pages/MapPage.tsx | 31 +++++++++++++++++++++---------- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/index.css b/src/index.css index b2e925b..f5d1c3a 100644 --- a/src/index.css +++ b/src/index.css @@ -395,6 +395,20 @@ } } +/* Indeterminate top loading bar for the map ("Loading spots"). A short segment + sweeps left→right on a faint track, so it reads as active work without + pretending to know real progress. Disabled under reduced-motion. */ +@keyframes ssLoadBar { + 0% { transform: translateX(-120%); } + 100% { transform: translateX(420%); } +} +.ss-loadbar { + animation: ssLoadBar 1.1s cubic-bezier(0.4, 0, 0.2, 1) infinite; +} +@media (prefers-reduced-motion: reduce) { + .ss-loadbar { animation: none; width: 100%; opacity: 0.7; } +} + .animate-fadeIn { animation: fadeIn 0.4s ease-out forwards; } diff --git a/src/pages/MapPage.tsx b/src/pages/MapPage.tsx index 50f0e6f..639c7ef 100644 --- a/src/pages/MapPage.tsx +++ b/src/pages/MapPage.tsx @@ -85,7 +85,6 @@ import { useToast } from '@/hooks/useToast'; import { cn } from '@/lib/utils'; import { getCurrentPosition, GeolocationError, isNativeGeolocation } from '@/lib/geolocation'; import { getLastLocation } from '@/lib/lastLocation'; -import { MapSkeletonPins } from '@/components/MapSkeletonPins'; type ViewMode = 'map' | 'list'; @@ -1615,18 +1614,30 @@ export default function MapPage() { userLocation={userLocation ? { lat: userLocation.lat, lng: userLocation.lon } : null} /> - {/* Loading indicator — slim top edge bar */} + {/* Loading indicator — a slim, honest top bar. The map stays + visible underneath and spots stream in; we NEVER cover the map + with fake placeholder pins. An animated sweep sits on top of a + faint track so it reads as "working", plus a small labeled pill + during a genuine cold start (no markers yet) so the user knows + we're talking to the relays. */} {isLoading && ( -
-
-
+ <> +
+
+
+ {mapSpots.length === 0 && ( +
+
+ + + Loading spots + +
+
+ )} + )} - {/* Cold-start skeleton: only while genuinely loading with NO - markers on the map yet. The instant real (or cached) spots - render, mapSpots.length > 0 and this disappears — no blank map. */} - {isLoading && mapSpots.length === 0 && } - {/* Map attribution — bottom-left, sits ABOVE the mobile bottom