Visual foundation for the map (ideas #2, #3, #4, #6 — features #5/#7 intentionally deferred to a later pass): #2 Crisp pins, not blobs: - Reworked createSkateSpotIcon — solid neon teardrop with a TIGHT glow halo (inset dark ring + focused shadow) instead of the soft box-shadow that read as a fuzzy cloud on light tiles. Cluster badges given the same crisp treatment. #3 Marker = spot type (a readable language): - Category emoji inside each pin: 🛹 street / 🏞️ park / 🔧 DIY / 🏛️ plaza / 🌊 transition. - Covered/indoor spots get a cyan roof "cap" so dry spots stand out at a glance — the payoff of the shelter data living on the map, not just a badge. - createSkateSpotIcon now takes an options object (weatherColor/weatherIcon/ category/shelter); all call sites updated. Weather-layer colouring unchanged. #4 Less scrim, floating loading: - Suspense map-load fallback dimming reduced (bg-black/50 → /20). - "Finding spots" pill moved from dead-centre (top 46%) to a slim top pill so it never blocks the map where the spots are. #6 Micro-interactions (CSS, GPU-friendly, reduced-motion aware): - New pins drop in with a spring; hover/focus/tap lifts the pin and pulses its neon halo once. Build clean, no console errors. No tile/theme change — the "og" light map is untouched per request. Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
88 lines
3.5 KiB
TypeScript
88 lines
3.5 KiB
TypeScript
/**
|
|
* MapSkeletonPins
|
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
* A tasteful "spots are loading" overlay shown ONLY on a genuinely cold, empty
|
|
* map (no cached spots yet + still fetching). Instead of a blank map that reads
|
|
* as broken, we scatter a few softly-pulsing ghost pins in the SkateSpots pin
|
|
* shape, so the moment reads as "finding spots near you…".
|
|
*
|
|
* It's purely decorative and non-interactive (`pointer-events-none`), sits above
|
|
* the tiles but below every real control, and fully respects reduced-motion.
|
|
* The instant real spots arrive the parent unmounts this — no layout shift.
|
|
*/
|
|
|
|
interface Ghost {
|
|
/** left/top as viewport percentages so it scales on any screen. */
|
|
left: number;
|
|
top: number;
|
|
/** teardrop scale + animation delay for an organic, staggered feel. */
|
|
scale: number;
|
|
delay: number;
|
|
}
|
|
|
|
// Hand-placed so they feel scattered-but-balanced (not a rigid grid).
|
|
const GHOSTS: Ghost[] = [
|
|
{ left: 22, top: 34, scale: 1.0, delay: 0 },
|
|
{ left: 48, top: 22, scale: 0.82, delay: 0.35 },
|
|
{ left: 71, top: 38, scale: 1.1, delay: 0.15 },
|
|
{ left: 34, top: 58, scale: 0.9, delay: 0.6 },
|
|
{ left: 62, top: 62, scale: 1.0, delay: 0.45 },
|
|
{ left: 82, top: 66, scale: 0.78, delay: 0.8 },
|
|
];
|
|
|
|
export function MapSkeletonPins() {
|
|
return (
|
|
<div
|
|
className="pointer-events-none absolute inset-0 z-[900] overflow-hidden"
|
|
aria-hidden="true"
|
|
>
|
|
{GHOSTS.map((g, i) => (
|
|
<div
|
|
key={i}
|
|
className="absolute motion-safe:animate-ghost-pin"
|
|
style={{
|
|
left: `${g.left}%`,
|
|
top: `${g.top}%`,
|
|
animationDelay: `${g.delay}s`,
|
|
}}
|
|
>
|
|
{/* Soft ground shadow / radar ping under each pin */}
|
|
<span
|
|
className="absolute left-1/2 top-full h-2 w-8 -translate-x-1/2 -translate-y-1 rounded-[50%] bg-[#ff0040]/20 blur-[3px] motion-safe:animate-ghost-ping"
|
|
style={{ animationDelay: `${g.delay}s` }}
|
|
/>
|
|
{/* SkateSpots teardrop pin outline */}
|
|
<div
|
|
style={{
|
|
width: 30 * g.scale,
|
|
height: 30 * g.scale,
|
|
borderRadius: '50% 50% 50% 0',
|
|
transform: 'rotate(-45deg)',
|
|
background:
|
|
'linear-gradient(135deg, rgba(255,0,64,0.18), rgba(255,102,0,0.14))',
|
|
border: '2px solid rgba(255,255,255,0.14)',
|
|
boxShadow: '0 4px 14px rgba(255,0,64,0.15)',
|
|
backdropFilter: 'blur(1px)',
|
|
}}
|
|
/>
|
|
</div>
|
|
))}
|
|
|
|
{/* Quiet status label — slim pill near the TOP so it never blocks the
|
|
map's center where the spots are. */}
|
|
<div className="absolute left-1/2 top-3 -translate-x-1/2">
|
|
<div className="flex items-center gap-2 rounded-full bg-black/55 px-3.5 py-1.5 backdrop-blur-md ring-1 ring-white/10 shadow-lg">
|
|
<span className="relative flex h-2 w-2">
|
|
<span className="absolute inline-flex h-full w-full rounded-full bg-[#ff0040] opacity-70 motion-safe:animate-ping" />
|
|
<span className="relative inline-flex h-2 w-2 rounded-full bg-[#ff0040]" />
|
|
</span>
|
|
<span className="font-mono text-[10px] uppercase tracking-[0.22em] text-white/70">
|
|
Finding spots
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default MapSkeletonPins;
|