← All primitives
NeonMap
MapA real interactive vector map (pan/zoom/tilt) recolored into a neon nightscape, with three tunable looks — Streets, Geography, Attractions — plus a glowing arc and pulsing pin. Tiles + style from OpenFreeMap — genuinely free, no API token.
Requires the .neon-pin / neon-pulse styles from globals.css and the maplibre-gl CSS (imported in the component). Give it a sized container; render via next/dynamic ssr:false. Separate WebGL context from R3F — don't stack with heavy 3D scenes.
$
npx shadcn@latest add https://prism.icglabs.co/r/neon-map.jsonDependencies:maplibre-gl
View raw manifest →components/prism/NeonMap.tsx
"use client";
import { useEffect, useRef, useState } from "react";
import maplibregl from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";
/**
* NeonMap — a REAL interactive vector map (actual streets, pan/zoom/tilt) styled
* into a neon nightscape, with three tunable looks: Streets (glowing roads),
* Geography (roads off, glowing water + land), and Attractions (dim roads, bright
* points of interest). Tiles + style from OpenFreeMap (free, no API token). The
* `.neon-pin` styles live in globals.css. Separate WebGL context from R3F.
*/
const NEON = "#1fd4e6";
const MAGENTA = "#ff3d81";
type Mode = "streets" | "geography" | "attractions";
const MODES: { id: Mode; label: string }[] = [
{ id: "streets", label: "Streets" },
{ id: "geography", label: "Geography" },
{ id: "attractions", label: "Attractions" },
];
function archedLine(a: [number, number], b: [number, number], n = 48, lift = 9) {
const pts: [number, number][] = [];
for (let i = 0; i <= n; i++) {
const t = i / n;
pts.push([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t + Math.sin(Math.PI * t) * lift]);
}
return pts;
}
function applyMode(map: maplibregl.Map, mode: Mode) {
const layers = map.getStyle().layers ?? [];
for (const layer of layers) {
const id = layer.id;
const type = layer.type;
const isRoad = /(road|street|highway|transportation|bridge|tunnel|rail)/.test(id);
const isWater = /water|ocean|river|lake/.test(id);
const isLand = /(landcover|landuse|park|forest|wood|grass|nature|sand|glacier)/.test(id);
const isBuilding = /building/.test(id);
const isPoi = /poi/.test(id);
const isLabel = type === "symbol";
const show = (v: boolean) => {
try {
map.setLayoutProperty(id, "visibility", v ? "visible" : "none");
} catch {}
};
const paint = (prop: string, val: unknown) => {
try {
map.setPaintProperty(id, prop, val);
} catch {}
};
if (mode === "streets") {
if (isRoad && type === "line") {
show(true);
paint("line-color", NEON);
paint("line-blur", 0.6);
}
if (isWater && type === "fill") {
show(true);
paint("fill-color", "#081018");
}
if (isLand && type === "fill") {
show(true);
paint("fill-color", "#0a0f18");
}
if (isBuilding) {
show(true);
paint(type === "fill-extrusion" ? "fill-extrusion-color" : "fill-color", "#0c1320");
}
if (isPoi) show(false);
if (isLabel) show(true);
} else if (mode === "geography") {
if (isRoad) show(false); // less streets
if (isWater && type === "fill") {
show(true);
paint("fill-color", "#06263a"); // glowing water
}
if (isLand && type === "fill") {
show(true);
paint("fill-color", "#0f3a2e"); // glowing land / parks
}
if (isBuilding) show(false);
if (isPoi) show(false);
if (isLabel && !/place|country|state|continent/.test(id)) show(false); // keep only big place names
} else {
// attractions
if (isRoad && type === "line") {
show(true);
paint("line-color", "#163240"); // dim roads
paint("line-blur", 0.3);
}
if (isWater && type === "fill") {
show(true);
paint("fill-color", "#081018");
}
if (isLand && type === "fill") {
show(true);
paint("fill-color", "#0a0f18");
}
if (isBuilding) {
show(true);
paint(type === "fill-extrusion" ? "fill-extrusion-color" : "fill-color", "#0f1726");
}
if (isPoi) {
show(true);
if (type === "symbol") paint("text-color", "#ffb24d");
if (type === "circle") paint("circle-color", "#ffb24d");
}
if (isLabel) show(true);
}
}
}
export function NeonMap({
center = [-122.42, 37.77],
zoom = 12,
}: {
center?: [number, number];
zoom?: number;
}) {
const ref = useRef<HTMLDivElement>(null);
const mapRef = useRef<maplibregl.Map | null>(null);
const loadedRef = useRef(false);
const [mode, setMode] = useState<Mode>("streets");
const modeRef = useRef<Mode>(mode);
modeRef.current = mode;
useEffect(() => {
if (!ref.current || mapRef.current) return;
const map = new maplibregl.Map({
container: ref.current,
style: "https://tiles.openfreemap.org/styles/dark",
center,
zoom,
pitch: 55,
bearing: -18,
attributionControl: { compact: true },
});
mapRef.current = map;
map.on("load", () => {
try {
map.setPaintProperty("background", "background-color", "#05060a");
} catch {}
const arc: GeoJSON.Feature = {
type: "Feature",
properties: {},
geometry: { type: "LineString", coordinates: archedLine(center, [center[0] + 0.06, center[1] + 0.05]) },
};
map.addSource("arc", { type: "geojson", data: arc });
map.addLayer({
id: "arc-glow",
type: "line",
source: "arc",
paint: { "line-color": MAGENTA, "line-width": 4, "line-blur": 6, "line-opacity": 0.9 },
});
map.addLayer({
id: "arc-core",
type: "line",
source: "arc",
paint: { "line-color": "#ffffff", "line-width": 1.2 },
});
const el = document.createElement("div");
el.className = "neon-pin";
new maplibregl.Marker({ element: el }).setLngLat(center).addTo(map);
loadedRef.current = true;
applyMode(map, modeRef.current);
});
const ro = new ResizeObserver(() => map.resize());
ro.observe(ref.current);
return () => {
ro.disconnect();
map.remove();
mapRef.current = null;
loadedRef.current = false;
};
}, [center, zoom]);
useEffect(() => {
const map = mapRef.current;
if (map && loadedRef.current) applyMode(map, mode);
}, [mode]);
return (
// data-lenis-prevent: stop the page's Lenis smooth-scroll from also scrolling
// when you wheel/zoom over the map.
<div className="relative h-full w-full" data-lenis-prevent>
<div ref={ref} className="h-full w-full" />
<div className="absolute left-4 top-4 z-10 flex gap-2">
{MODES.map((m) => (
<button
key={m.id}
onClick={() => setMode(m.id)}
className={`rounded-full border px-3 py-1.5 text-xs backdrop-blur transition-colors ${
mode === m.id
? "border-cyan/50 bg-cyan/10 text-ink"
: "border-line bg-obsidian/50 text-mist hover:text-ink"
}`}
>
{m.label}
</button>
))}
</div>
</div>
);
}