Prism
← All primitives

NeonMap

Map

A real interactive vector map (pan/zoom/tilt) recolored into a neon nightscape, with a glowing arc and a pulsing pin. Tiles + style from OpenFreeMap — genuinely free, no API token or signup.

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.json
Dependencies:maplibre-gl
View raw manifest →

components/prism/NeonMap.tsx
"use client";

import { useEffect, useRef } 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. Tiles + style come from OpenFreeMap (free, no API token,
 * no signup). The base 'dark' style is recolored on load and a glowing great-circle
 * arc + a pulsing pin are drawn on top. Separate WebGL context from R3F, so render
 * it on its own (not stacked with the flight). The `.neon-pin` styles live in globals.css.
 */
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;
}

export function NeonMap({
  center = [-122.42, 37.77],
  zoom = 11,
}: {
  center?: [number, number];
  zoom?: number;
}) {
  const ref = useRef<HTMLDivElement>(null);
  const mapRef = useRef<maplibregl.Map | null>(null);

  useEffect(() => {
    if (!ref.current || mapRef.current) return;
    const map = new maplibregl.Map({
      container: ref.current,
      style: "https://tiles.openfreemap.org/styles/dark", // free, no token
      center,
      zoom,
      pitch: 55,
      bearing: -18,
      attributionControl: { compact: true },
    });
    mapRef.current = map;

    map.on("load", () => {
      const NEON = "#1fd4e6";
      const MAGENTA = "#ff3d81";
      try {
        map.setPaintProperty("background", "background-color", "#05060a");
      } catch {}
      for (const layer of map.getStyle().layers ?? []) {
        const id = layer.id;
        const type = layer.type;
        if (/water/.test(id) && type === "fill") {
          try {
            map.setPaintProperty(id, "fill-color", "#081018");
          } catch {}
        }
        if (/(road|street|highway|transportation)/.test(id) && type === "line") {
          try {
            map.setPaintProperty(id, "line-color", NEON);
          } catch {}
          try {
            map.setPaintProperty(id, "line-blur", 0.6);
          } catch {}
        }
        if (/building/.test(id) && (type === "fill" || type === "fill-extrusion")) {
          try {
            map.setPaintProperty(id, type === "fill" ? "fill-color" : "fill-extrusion-color", "#0c1320");
          } catch {}
        }
      }

      // glowing arc
      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 },
      });

      // pulsing pin
      const el = document.createElement("div");
      el.className = "neon-pin";
      new maplibregl.Marker({ element: el }).setLngLat(center).addTo(map);
    });

    // resize when the container becomes visible / changes size
    const ro = new ResizeObserver(() => map.resize());
    ro.observe(ref.current);

    return () => {
      ro.disconnect();
      map.remove();
      mapRef.current = null;
    };
  }, [center, zoom]);

  return <div ref={ref} className="h-full w-full" />;
}
Live demo — read-only. Every section is a real, copyable primitive.