Event and webinar registrations
Registration pages have one job and a hard deadline. The thing that moves the number is not another bullet point about the agenda: it is evidence that other people are going. A map that visibly fills up over the two weeks before the event does that better than a counter alone, because it also answers "will anyone be awake in my time zone".
The code
"use client";
import { useEffect, useRef } from "react";
import { Globe } from "canvas-globe/react";
export function RegistrationGlobe({ attendees, total }) {
const globe = useRef(null);
useEffect(() => {
const id = setInterval(async () => {
const { cities, total } = await fetch("/api/event/registrations").then((r) => r.json());
globe.current?.setMarkers(cities);
globe.current?.setOptions({ counter: { value: total, label: "registered" } });
}, 60000);
return () => clearInterval(id);
}, []);
return (
<Globe
ref={globe}
preset="midnight"
rotateSpeed={0.05}
markers={attendees}
markerStyle="bubble"
cluster
counter={{ value: total, label: "registered" }}
showViewer={{ label: "You", live: true }}
tooltip={(m) => `${m.city}: ${m.count} attending`}
/>
);
}
The time-zone problem
For a live webinar, "where are the attendees" and "what time is this for them" are the same question. Turn the terminator on and the map answers it without a table of conversions:
{
terminator: true,
time: new Date("2024-05-16T15:00:00Z"), // the event start, not now
}
Anyone in the shaded half is being asked to attend at night. If that is a large share of your registrations, consider running a second session for those time zones.
See Overlays for day/night shading.
Replaying the run-up
playTimeline reveals markers as their date arrives, which turns a registration list into a
fourteen-second clip you can post the day before:
const markers = registrations.map((r) => ({ ...r, date: r.registeredAt }));
globe.setMarkers(markers);
globe.playTimeline({ duration: 6000, loop: true });
With no from/to it derives the range from the data. Record it and you have the reminder post:
const { promise } = globe.record({ duration: 7000, filename: "registrations.webm" });
globe.playTimeline({ duration: 6000 });
await promise;
During the event
The same globe does a second job on the day. Swap the counter to attendance and ping questions as they come in:
socket.on("question", ({ name, city, lat, lon }) => {
globe.ping({ lat, lon, emoji: "🙋", label: `${name} from ${city} asked a question` });
});
On a shared screen between sessions, the map can show the geographic reach of the audience.
After the event
The same map can be reused for the next event:
globe.setOptions({
title: { text: "1,284 people joined from 47 countries", subtitle: "Thanks for coming" },
watermark: { image: "/logo.svg", text: "acme.com/events" },
counter: null,
});
const png = globe.exportImage({ preset: "og" });
That image goes on the recording page, in the follow-up email, and at the top of next quarter's registration page: where it is the social proof this whole page is about.
Community meetups
Same shape, different data. For a user group or a conference roadshow, mark the cities you are visiting and let people click through:
{
markers: cities.map((c) => ({ ...c, label: `${c.name} · ${c.date}`, live: c.next })),
labels: "markers",
onClick: (m) => m && router.push(`/events/${m.slug}`),
}
live: true on the next stop makes it pulse: one pulsing dot on a still map is a very strong
"click here".
Next
- Overlays: timeline, counter, terminator
- Live signup social proof: the always-on version
- Social share card generator: the wrap-up graphic