diff --git a/public/images/user-location.png b/public/images/user-location.png
new file mode 100644
index 0000000..a785ca7
Binary files /dev/null and b/public/images/user-location.png differ
diff --git a/src/components/SpotMap.tsx b/src/components/SpotMap.tsx
index 79c8acb..4584199 100644
--- a/src/components/SpotMap.tsx
+++ b/src/components/SpotMap.tsx
@@ -106,6 +106,47 @@ const createShopIcon = () => L.divIcon({
popupAnchor: [0, -36],
});
+// "You are here" user-location marker — SkateSpots logo in a circular badge
+// with a pulsing accuracy halo (Google-Maps-style, brand-flavored).
+const createUserLocationIcon = () => {
+ // BASE_URL resolves correctly for both web ('/') and the Capacitor APK ('./').
+ const logoUrl = `${import.meta.env.BASE_URL}images/user-location.png`;
+ return L.divIcon({
+ className: 'user-location-marker',
+ html: `
+
+ `,
+ iconSize: [64, 64],
+ iconAnchor: [32, 32], // center anchored (it marks a point, not a pin drop)
+ popupAnchor: [0, -20],
+ });
+};
+
// Dual-purpose marker (shop + spot)
const createDualIcon = (hasGear?: boolean, gearCount?: number) => L.divIcon({
className: 'dual-marker',
@@ -391,6 +432,8 @@ interface SpotMapProps {
showCrews?: boolean;
showBtcMap?: boolean;
flyTo?: { lat: number; lng: number; zoom?: number } | null;
+ /** The current user's location — renders a "you are here" pin when set. */
+ userLocation?: { lat: number; lng: number } | null;
}
function createPopupContent(
@@ -637,11 +680,13 @@ export function SpotMap({
showCrews = false,
showBtcMap = false,
flyTo = null,
+ userLocation = null,
}: SpotMapProps) {
const mapRef = useRef(null);
const mapInstanceRef = useRef(null);
const markersRef = useRef([]);
const btcMapMarkersRef = useRef([]);
+ const userMarkerRef = useRef(null);
// MarkerClusterGroup for spot/shop/gear/tour markers
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const clusterGroupRef = useRef(null);
@@ -859,6 +904,36 @@ export function SpotMap({
map.flyTo([flyTo.lat, flyTo.lng], flyTo.zoom ?? 14, { animate: true, duration: 1.2 });
}, [flyTo]);
+ // Render / update the "you are here" user-location marker.
+ useEffect(() => {
+ const map = mapInstanceRef.current;
+ if (!map) return;
+
+ if (!userLocation) {
+ // No location — remove any existing marker.
+ if (userMarkerRef.current) {
+ userMarkerRef.current.remove();
+ userMarkerRef.current = null;
+ }
+ return;
+ }
+
+ if (userMarkerRef.current) {
+ // Move the existing marker to the new position.
+ userMarkerRef.current.setLatLng([userLocation.lat, userLocation.lng]);
+ } else {
+ // Create it, on top of everything else.
+ userMarkerRef.current = L.marker([userLocation.lat, userLocation.lng], {
+ icon: createUserLocationIcon(),
+ zIndexOffset: 1000,
+ interactive: true,
+ keyboard: false,
+ })
+ .addTo(map)
+ .bindPopup('You are here');
+ }
+ }, [userLocation]);
+
// Update markers
useEffect(() => {
const map = mapInstanceRef.current;
diff --git a/src/index.css b/src/index.css
index 9ad5aee..507e826 100644
--- a/src/index.css
+++ b/src/index.css
@@ -285,6 +285,13 @@
50% { box-shadow: 0 0 0 8px hsl(var(--primary) / 0); }
}
+/* "You are here" user-location marker pulse halo */
+@keyframes ssPulse {
+ 0% { transform: scale(0.5); opacity: 0.8; }
+ 70% { transform: scale(1.6); opacity: 0; }
+ 100% { transform: scale(1.6); opacity: 0; }
+}
+
.animate-fadeIn {
animation: fadeIn 0.4s ease-out forwards;
}
diff --git a/src/pages/MapPage.tsx b/src/pages/MapPage.tsx
index bec10aa..d29c4f4 100644
--- a/src/pages/MapPage.tsx
+++ b/src/pages/MapPage.tsx
@@ -1592,6 +1592,7 @@ export default function MapPage() {
showCrews={showCrews}
showBtcMap={showBtcMap}
flyTo={flyTo}
+ userLocation={userLocation ? { lat: userLocation.lat, lng: userLocation.lon } : null}
/>
{/* Loading indicator — slim top edge bar */}