registry
@automindzsheets 01–77shadcn 4.x
SHEET 45

Globe

block / motion / marketing

A dotted globe of real coastline whose pinned cities are land dots of the sphere's own lattice, lit inside two rings, with great-circle arcs between them — spherical trigonometry in a server component, no WebGL and no canvas, and an opt-in client wrapper that lets you grab the planet and turn it.

type
block
files
4
registry deps
skeleton, @automindz/world-dots
npm deps
none

installs

  • lib/automindz/globe-arcs.ts
  • components/automindz/globe.tsx
  • components/automindz/globe-drag.tsx
  • styles/automindz-globe.css
  • Dots are real coastline projected onto the sphere — the shape is the continents, not a wireframe
  • Seven paths draw 1,800 dots; one element each would be 1,800 composited 3D layers
  • A marker is one of those dots, snapped onto the lattice and sized by its own depth
  • centerLon and tilt are a re-projection, never a CSS rotate — the far side is not drawn at all
  • GlobeDrag wires that to the pointer at 0.91ms a frame; Globe imported alone still ships no JS

Edit lib/automindz/world-dots.ts, not the component, and pass it — config has no default here (<Globe config={WORLD_MARKERS} />). Markers are { lat, lng, label?, size?, id? }; arcs address a marker by its id, or by its index if it has none. The stylesheet is not optional: add import "@/styles/automindz-globe.css" to your root layout once, or nothing traces. Skipping it is safe, never blank — every coordinate is inline, so the land, the horizon, the markers with their rings and every arc are still drawn end to end; that is also the prefers-reduced-motion path. **A marker is one of the globe's own dots, lit — never a pin dropped on top of them.** Its latitude and longitude are snapped to the nearest land dot of the sphere's lattice and that dot is left out of the bands, so the marker stands in the field's own rhythm; it is 1.1 land dots wide *at its own depth*, so it shrinks toward the limb with the dots around it, and two flat rings at 2.25 and 3.25 land-dot radii mark the place rather than its size. The snap never moves a marker more than a step and a half, so an island smaller than step or a point in mid-ocean is drawn where it really is; two cities inside one dot take neighbouring dots, in declaration order. **The markers are the one thing here that takes your brand colour.** They draw from --automindz-globe-marker, which falls back to your own --primary and then to currentColor, and one line moves them anywhere else: className="[--automindz-globe-marker:var(--color-emerald-500)]". The land, the sphere and the arcs stay currentColor, so text-primary retints the drawing. Nothing inside the component names a colour. **The globe does not *animate* a rotation, on purpose**: a dot is where its latitude and longitude project to, so turning the sphere re-projects every point sixty times a second, and a CSS rotate is an affine squash of a flat picture that slides the markers off their cities. **Choosing the angle is a different thing and it is free**: centerLon and tilt (degrees the north pole leans toward you, default 18, clamped to ±89) are a re-projection computed once at render, and the land, the markers, the arcs and the visibility test all come out of the same projection, so they agree at every combination. **Turning it by hand is a third question, and it lives in a second file you opt into**: import { GlobeDrag } from "@/components/automindz/globe-drag" gives you the same globe with the pointer wired to the projection — drag sideways to turn (longitude wraps, so you can keep going all the way round), drag up and down to tilt (clamped at ±89, and the clamp is on the state, so one degree back off the wall moves it one degree however long it was held there), and a diagonal does both in one gesture rather than locking to an axis. It forwards every Globe prop; centerLon and tilt become the view it *starts* at, and sensitivity (default 180) is the degrees a drag across the whole width turns it. **Importing Globe on its own still costs exactly nothing** — no "use client", no listener, no state, no client bundle; the boundary is in globe-drag.tsx alone, which is the entire point of the split. The cost you would not guess: re-projecting in the browser means lib/automindz/world-dots.ts, the coastline mask and both samplers, crosses into the client bundle with GlobeDrag. One re-projection is 0.91ms in V8 at the default step, coalesced to one per animation frame rather than one per pointermove, and the tilt clamp is **not** the expensive view — the busiest hemisphere is a middling tilt around 45°, and dead-on at either pole draws less — so there is no need for a coarser lattice while dragging. There is no inertia and no easing, so nothing keeps moving after you let go and there is nothing for prefers-reduced-motion to reduce; a gesture the reader is authoring is not unrequested motion, so the drag works under the preference unchanged. **A drag-only affordance does not exist for a keyboard user**, so the surface is a focus stop named by dragLabel ("Turn the globe") with an sr-only hint: the arrow keys turn it 6° a press in the *viewpoint* direction — the way a map or a scroll container moves, the opposite of a drag — and Home puts it back. Touch is touch-action: pan-y, so a vertical swipe still scrolls the page past a full-width square globe and a horizontal one turns it; pass className="touch-none" to let the globe own the whole gesture. loading renders the skeleton with the interaction off entirely — no focus stop and no grab cursor over a globe that has no view to turn. **The view is a design decision, not a default to skip past**: centerLon is −20, the Atlantic, so both Americas and Africa read at once — centre it on 5°E and the Americas collapse into a crescent at the limb, centre it on the Pacific and the sphere is nearly empty, because the dots are land. Only routes with **both** ends on the visible hemisphere are drawn; the rest land in resolveGlobe's skipped array with a reason (unknown-marker, self, duplicate, behind), so read it in development if a route you declared is missing. It is a square that fills its container, so give it a max-w-*. Pass label to name the marker and route lists, which are the accessible content of the item — the drawing itself is aria-hidden. **The traffic cadence is capped, not documented**: worldCycle() stretches the cycle until arcs finish at 1.5Hz or slower, so set it with the cycle prop and never by overriding --automindz-globe-cycle, which the component writes and the per-arc phases are already measured against. Labels drop to sr-only below 22rem of container width; that number is a literal in the component, not a dial, because a container query condition cannot read a custom property. step is degrees between land dots (default 2.5, about 1 800 dots and 25KB of path data — raise it to 3 or 4 if you render several on one page). Dials: --automindz-globe-body, --automindz-globe-limb, --automindz-globe-dot-near, --automindz-globe-dot-far, --automindz-globe-land-near, --automindz-globe-land-far, --automindz-globe-marker, --automindz-globe-ring (the ink in a marker's inner ring; the outer one is a fraction of it), --automindz-globe-rail.

Variations

one state per section

01

Eight regions, seven routes

The dots are Natural Earth's coastline sampled onto the sphere at the Atlantic view, so both Americas and Africa read at once — centre it on 5°E instead and the Americas collapse into a crescent at the limb. A marker is one of those dots, lit: it is snapped to the nearest land dot and that dot leaves the field, so the stipple runs unbroken through it. Singapore and Sydney are round the back and are not drawn at all, and the one route between them is skipped with them; both are still in the list underneath.

fluid
  • San Francisco
  • New York
  • São Paulo
  • London
  • Cape Town
  • Mumbai
  • Singapore
  • Sydney
  • San Francisco to New York
  • London to New York
  • New York to São Paulo
  • São Paulo to Cape Town
  • London to Cape Town
  • London to Mumbai
02

Finer land, tinted

step={2} lifts the dot count by half again, and text-primary retints the land, the horizon and the routes — the markers keep their own ink. A marker and its two rings are ratios of a land dot at their own depth, so the whole mark thins with the finer field and shrinks toward the limb with it.

fluid
  • San Francisco
  • New York
  • São Paulo
  • London
  • Cape Town
  • Mumbai
  • Singapore
  • Sydney
  • San Francisco to New York
  • London to New York
  • New York to São Paulo
  • São Paulo to Cape Town
  • London to Cape Town
  • London to Mumbai
03

Markers on a brand colour

The markers draw from --automindz-globe-marker, which falls back to the consumer's own --primary — so a client whose brand is orange gets this with no code change. Here the docs site points it at a colour from the outside; nothing inside the component names one. The lit dot is the size of the land dots beside it; the two flat rings are what mark the place.

fluid
  • San Francisco
  • New York
  • São Paulo
  • London
  • Cape Town
  • Mumbai
  • Singapore
  • Sydney
  • San Francisco to New York
  • London to New York
  • New York to São Paulo
  • São Paulo to Cape Town
  • London to Cape Town
  • London to Mumbai
04

Two tilts, one projection

tilt leans the north pole toward you and is a re-projection computed once at render, not a CSS rotation of the finished picture — so the land, the markers, the arcs and the horizon all agree at every angle. Dead-on at 0; a polar view at 45, where Europe and North America open out and the far hemisphere takes Sydney with it. It composes with centerLon.

fluid
  • San Francisco
  • New York
  • São Paulo
  • London
  • Cape Town
  • Mumbai
  • Singapore
  • Sydney
  • London to New York
  • New York to São Paulo
  • São Paulo to Cape Town
  • London to Cape Town
  • San Francisco
  • New York
  • São Paulo
  • London
  • Cape Town
  • Mumbai
  • Singapore
  • Sydney
  • San Francisco to New York
  • London to New York
  • New York to São Paulo
05

Turned by hand

GlobeDrag is a separate "use client" file wrapping the same server-safe component — grab it and drag: sideways turns, up and down tilts to ±89°. Every frame is a real re-projection at 0.95ms, coalesced to one per animation frame, so the markers stay on their cities and the far side keeps culling at every angle a CSS rotate would have sheared. Tab to it and the arrow keys turn it; Home puts it back. There is no inertia and no easing, so nothing keeps moving after you let go.

fluid
  • San Francisco
  • New York
  • São Paulo
  • London
  • Cape Town
  • Mumbai
  • Singapore
  • Sydney
  • San Francisco to New York
  • London to New York
  • New York to São Paulo
  • São Paulo to Cape Town
  • London to Cape Town
  • London to Mumbai

Use the arrow keys to turn the globe, or Home to put it back.

06

Re-centred on the Pacific

centerLon={-150} turns the view to the water hemisphere. Only the horizon ring and the disc are left in the middle, because the dots are land and there is hardly any there — the drawing being honest, not a bug, and the reason the default sits over the Atlantic.

fluid
  • San Francisco
  • New York
  • São Paulo
  • London
  • Cape Town
  • Mumbai
  • Singapore
  • Sydney
  • San Francisco to New York
07

A config with four mistakes in it

A duplicate id, a route declared twice, a route from a place to itself, a route to an id that does not exist, and a route to Sydney, which is round the back. One arc survives; the rest land in resolveGlobe's skipped array with a reason instead of being drawn wrong.

fluid
  • London
  • New York
  • São Paulo
  • Sydney
  • London to New York
08

One marker, no routes

The smallest useful config. size: 1.4 scales the whole mark — the lit dot, both rings and the ping — and the box reserves room for the ping from that number, so nothing is shaved at the rim.

fluid
  • London
09

Nothing pinned

An empty config still renders the planet — land, horizon and all — because a globe with nothing pinned to it is still a globe. No marker list and no route list are emitted at all rather than two empty ones.

fluid
10

Loading

The skeleton is the sphere's own circle and not the component's square, so the drawing does not appear to shrink when the data lands. The box is aria-busy and never moves.

fluid