# Mercator — Animated Data Visualization for React

> A dotted world map as a live data surface: connection arcs launch with bright comet heads, land with a two-ring radar ripple, and dock event chips by hairline leader lines. Deterministic seeded dot grid (self-contained land bitmask), static events or live callback, chart-room palette. Canvas 2D, capped arc pool, offscreen-paused, reduced-motion-safe.

- Page: https://crucibleui.com/components/data/mercator
- Category: [data](https://crucibleui.com/components/data)
- Tier: pro (one-time purchase, see https://crucibleui.com/pricing)
- Engine: canvas

## Install

```bash
npx shadcn@latest add @crucible/mercator
```

Pro install requires adding the token-bearing `@crucible` registry to components.json (issued at https://crucibleui.com/account after purchase).

Import from `@/components/crucible/mercator`.

## Props (`MercatorProps`)

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `events?` | `MercatorEvent[]` | — | Static event roster — arcs launch from it cyclically, seeded jitter on timing. Omit to use the built-in seeded demo-event generator. |
| `getEvent?` | `() => MercatorEvent \| null \| undefined` | — | Live event source, polled once per launch slot; return null/undefined to skip that slot. Takes precedence over `events`. (Under reduced motion the static roster / generator is used instead — this is never polled.) |
| `arcFrequency?` | `number` | `0.45` | Mean arc launches per second (seeded jitter applied). |
| `arcSpeed?` | `number` | `1` | Arc travel-speed multiplier. 2 = twice as fast. |
| `arcCurvature?` | `number` | `1` | How high arcs bow off the great line, 0–2. |
| `cometIntensity?` | `number` | `1` | Comet head/tail brightness, 0–2. |
| `rippleIntensity?` | `number` | `1` | Landing radar-ripple strength, 0–2. |
| `renderChip?` | `(event: MercatorEvent) => React.ReactNode` | — | Replace the default event chip (leader line + anchor dot are kept). |
| `chipDwell?` | `number` | `4` | Seconds a chip stays docked before retiring. |
| `maxChips?` | `number` | `3` | Max chips shown at once — oldest retires when a new one lands. 0 disables chips. |
| `dotDensity?` | `number` | `160` | Dot columns across the full 360° of longitude. Clamped 40–320. |
| `dotSize?` | `number` | `1` | Dot radius multiplier relative to grid spacing. |
| `center?` | `{ lat: number; lng: number }` | `{ lat: 14, lng: 10 }` | Projection center. |
| `zoom?` | `number` | `1` | Projection zoom — 1 fits 360° across the container width. |
| `maskFalloff?` | `number` | `0.65` | Radial edge-fade strength for the dot grid, 0 (none) – 1. |
| `colors?` | `MercatorColors` | — | Palette: dots, arc paths, and the single accent. Hex strings (`#rgb` / `#rrggbb`) — the canvas parses them. |
| `seed?` | `number` | `7` | Seed for the dot jitter, launch jitter and demo-event generator. |
| `announceEvents?` | `boolean` | `false` | Mirror landed events to a visually-hidden polite live region. |
| `dpr?` | `number` | `2` | devicePixelRatio ceiling. Clamped 1–2. |
| `paused?` | `boolean` | `false` | Freeze at the static chart frame (dots + plotted routes + chips). |

Also accepts: `Omit<React.ComponentPropsWithoutRef<"div">, "children">`.

## Usage example

```tsx
import { Mercator, type MercatorEvent } from "@/components/crucible/mercator";

const EVENTS: MercatorEvent[] = [
  { from: "san-francisco", to: "berlin", label: "New signup — Berlin" },
  { from: "london", to: "singapore", label: "Deploy finished — Singapore" },
  { from: "tokyo", to: "sao-paulo", label: "Payment received — São Paulo" },
  { from: "berlin", to: "tokyo", label: "New signup — Tokyo" },
  { from: "sydney", to: "london", label: "API key created — London" },
  { from: "mumbai", to: "toronto", label: "Trial started — Toronto" },
  { from: "sao-paulo", to: "dubai", label: "Payment received — Dubai" },
  { from: "singapore", to: "nairobi", label: "New signup — Nairobi" },
];

export default function MercatorDemo() {
  return (
    <div className="relative isolate flex h-full min-h-[360px] w-full items-center justify-center overflow-hidden rounded-xl bg-neutral-950">
      <Mercator events={EVENTS} className="absolute inset-0" />
      <div className="relative z-10 w-full self-end pb-5 text-center">
        <p className="text-[11px] font-medium uppercase tracking-[0.2em] text-neutral-500">
          Watch the arcs land — a radar ripple, then an event chip
        </p>
      </div>
    </div>
  );
}
```

## Accessibility & performance

Honors `prefers-reduced-motion` with a designed static frame or fade (never a blank box) and passes `className` through. Pauses offscreen and on hidden tabs, clamps device pixel ratio, and tears down its GL/canvas context on unmount.
