mappo demos

Everything on one page, live — the same build you would install. Each example carries the exact markup that produces it, so you can copy a card and get that picture.

Lisbon Tokyo São Paulo Nairobi New York Moscow

Real coastlines, leaning, with your own labels

Drag it. The labels are ordinary anchors in your markup — mappo positions them every frame and publishes how far each has turned away, so they fade and hide at the horizon with plain CSS.

<mappo-world mode="globe" rotate-speed="6"
  tilt="18" roll="-14.3"
  figure="solid outline" figure-source="vector"
  graticule meridians="24" parallels="11">
  <a data-lat="38.7" data-lon="-9.1"><span>Lisbon</span></a>
  <a data-lat="35.7" data-lon="139.7"><span>Tokyo</span></a>
</mappo-world>

.pin > span { opacity: calc(.28 + .72*var(--mappo-depth,1)) }
.pin[data-mappo-behind] { visibility: hidden }

Right now — where the sun is, computed from your clock

The map is true right now, and it needs no data to be

The sun's position is fifteen lines of astronomy from Date.now() — no API, no key, nothing to go stale. Everything else is mappo: the night is a polygon plotted through projectNormalized(), and the city markers are your own DOM, positioned by data-lat/data-lon, told only whether the sun is up where they stand.

projectNormalized()figure-source="vector"bordersground-colordata-lat/data-loncols="300"
// where the sun is overhead, from the clock alone
const { dec, lon } = subsolarPoint(new Date());

// night is the cap of angular radius 90 + h about the antisolar point — ask
// it which latitudes it covers at each meridian, then close the polygon
const [ lo, hi ] = capSpan(lon, -dec, sunLon + 180, Math.cos((90 + h) * RAD));

// and each label only needs to know whether its own sky is dark
label.classList.toggle("is-night", solarElevation(lat, lon, dec, sunLon) < 0);

Four ways to flatten a sphere — equirectangular, Equal Earth, the poles, and your own

Projections →

The same Earth as the straight-lined equirectangular map and as Equal Earth, where areas are true and the corners are not part of the world. A Pacific-centred map with the seam moved and the coastlines re-cut. The lunar south pole in polar stereographic with the Artemis regions labelled, the Arctic as one ocean, Mars from above its north pole. And a sinusoidal projection written in eight lines, handed in as a plain object.

projection="equal-earth"projection="stereographic-south"center-lon="150"lat-max="-80"{ forward, inverse }graticule
<mappo-moon projection="stereographic-south" lat-max="-80" figure="solid" figure-source="vector" graticule>
  <a data-lat="-89.7" data-lon="129.2">Shackleton</a>   <!-- the pole is the centre -->
</mappo-moon>

Region: Earth — cloudflare.com's glass globe, rebuilt on four attributes

Region: Earth →

The see-through globe from cloudflare.com's home page, to the pixel where mappo can reach it: fourteen thousand tiles lying on the surface of a Fibonacci lattice, a perspective camera 2.4 radii out, fog that lets the far side show through, the pins, the captions and the arcs — with the original's exact points, copy and kinematics, and mappo's own coastline under it.

distribution="uniform"dot-shape="tile"distance="2.37"fog="-0.67 1.01"mappo/linksdata-lat/data-lon
<mappo-world mode="globe" cols="396" lat-min="-90" lat-max="90"
  distribution="uniform" dot-shape="tile" dot-size="0.38"
  distance="2.37" fog="-0.67 1.01" graticule meridians="24" parallels="23"></mappo-world>

Live Globe — a product's world, as it happens, on mappo/links

Live Globe →

The globe a product with users everywhere puts on its front page: ~12,000 dots on a lit indigo sphere under a blue rim, a blue spike wherever someone is sending right now, rising and sinking as the moment passes, a pink arc for every message on its way, drawing in, holding, erasing and landing with a ring, the hover card, the pause toggle and an opening view turned toward your part of the world. The feed is synthetic: two hundred and sixty messages between a hundred and sixty cities, weighted the way people are, so Asia and Africa carry most of it. The lights are three CSS gradients; the lines are mappo/links on a second, empty globe over them, so they stay unlit.

mappo/linksmap.addLayer()distribution="uniform"distance="8.8"locate().scaletwo globes, one orientation
import { links } from "mappo/links";
const arcs = links(sky.map, { color: "#f46bbe", width: 1.75, blend: "lighter" });
const arc = arcs.add({ points: bezier, range: [ 0, 0 ], data: message });
arc.range = [ 0, drawn / total ];        // the original's 600 index units a second
const hit = arcs.at(pointerEvent);       // the message under the pointer

Ten thousand satellites — the whole Starlink constellation, live

Starlink, right now →

Every satellite in the constellation and the ISS, where they actually are this second, on a full-screen globe you can spin — then the same positions on the flat map, where the 53° shell draws its own turnaround lines. The orbital elements come from CelesTrak and the positions from an SGP4 propagator running in the page: no API key, no tracking service, nothing to go stale but the elements themselves.

locate()mode="globe"figure="outline"figure-source="vector"10,725 points/frame
// mappo hands back the projection it just drew with, so your own
// canvas registers to the pixel — including things above the surface
const p = map.locate(lat, lon, 1 + altitudeKm / 6371);
if (p.front) ctx.fillRect(p.x, p.y, 2, 2);   // .depth fades it with the limb

Where the Earth is — the orbit and the rotation, at any moment

Where the Earth is →

Kepler's equation solved in the page, the orbit drawn to scale with the Sun at its focus, and the planet shown as the Sun sees it — so the edge of the disc is the terminator and the pole leaning toward you is the season. Scrub a year either way and watch the Earth run faster in January than in July, because it does.

focustiltfigure-source="vector"locate()Kepler + sidereal time
// aim the globe at the subsolar point and the disc IS the daylit half
earth.setAttribute("focus", `${dec},${subLon}`);
earth.setAttribute("tilt", dec);   // focus is live now, so this re-aims

The Moon and Mars — the same renderers, another body

Other worlds →

Lunar maria and Martian lowlands drawn by the very same flat and globe renderers, from opt-in body packs that ship apart from the bundle. Landing sites from each body's own gazetteer, and all three worlds at their true relative diameters.

body="moon"<mappo-mars>places="Apollo 11"registerBody()

Earth in orbit — a fixed 23.44° axis, carried around a sun

axial tilt 23.44° — fixed in space, never counter-rotated

The carrier only translates

That is the whole trick: mappo spins the planet on its own axis, and the container walks an ellipse. Because the container never counter-rotates, the tilt keeps pointing the same way all year — which is what gives a planet seasons.

<div class="carrier">
  <mappo-world mode="globe" tilt="23.44" rotate-speed="45"
    figure="solid outline" figure-source="vector"></mappo-world>
</div>

const phase = (t % YEAR_MS) / YEAR_MS, a = phase * Math.PI * 2;
carrier.style.transform =
  `translate3d(${Math.cos(a)*330}px, ${Math.sin(a)*125}px, 0)`;

Figure styles — one map, every option, updated live

Every combination is one attribute

figure is a space-separated token list, so combinations read the way you say them. The same names mean the same thing on the flat map and the globe.

Attributes are live — the buttons above just set them, and the map re-renders.

Globe — roll, tilt, and how they compose

Upright

No lean, no tilt — the reference frame.

roll="0" tilt="0"

Roll — the lean

Turns the finished disc in the plane of the screen. Watch the equator.

roll="-14.3"

Tilt — the axis

Leans the axis away from you. A foreshortening, not a rotation.

tilt="30" graticule

Both, composed

Different gestures; they stack.

roll="-14.3" tilt="25"

Country borders

Every national boundary, on the sphere.

figure="solid" figure-source="vector" borders

Dots + dense graticule

24 meridians, 23 parallels — one lands on 0° and is dropped, so the equator is never double-drawn.

graticule meridians="24" parallels="23"

Flat — grid detail versus real geography

The dot field

What mappo is named for. Resolution is a runtime parameter.

<mappo-world cols="120"></mappo-world>

Grid contours

Traced from the bitmask. Blocky by design, follows cols, free.

figure="outline"

Vector coastlines

Natural Earth at 1/32°. Smooth at any size, independent of cols. Earth's rings are their own module, mappo/bodies/earth-vector, 22 KB gzipped.

figure="outline" figure-source="vector"

Everything

Filled, coasted, bordered.

figure="solid outline" figure-source="vector" borders

Highlight a region

You supply the rings; mappo ships no boundary dataset for this on purpose.

highlight-polygon='[[[42.1,-8.2],…]]'
highlight-color="#c2410c"

Overlays, flat

Identical attributes and CSS hooks as the globe — written once per build instead of per frame.

<a data-lat="38.7" data-lon="-9.1"><span>Lisbon</span></a>

One caveat, stated plainly: on the GLOBE a filled figure style draws from the 512×256 mask, not the vector outlines — clipping a vector polygon to the visible hemisphere needs an algorithm mappo does not have yet, and every shortcut for it was measured painting open ocean. Fill and coast therefore both come from the grid there, where they agree by construction. figure="outline" with figure-source="vector" is exact on the globe, and the flat map is exact in every combination.

Every option

body            "earth" (default) | any registered body ("moon", "mars", yours)
figure          "dots" | "solid" | "outline" | "solid outline"
figure-source   "grid" (traced from the body) | "vector" (the body's outlines)
figure-color    the figure: dots, or the fill
figure-stroke   the edge       → falls back to figure-color
figure-stroke-width
ground-color    everything that is not figure, as filler dots
borders         region boundaries (Earth: countries)
borders-color / borders-width / borders-opacity
places          gazetteer names of the body · markers "Name@lat,lon"
highlight-polygon / highlight-color
mode            "flat" | "globe"
tilt            axial tilt, degrees (23.44 = Earth)
roll            lean in the screen plane, degrees
rotate-speed    deg/sec · drag to spin, flick for momentum
graticule       meridians / parallels
                graticule-color / equator-color
                graticule-opacity / equator-opacity
cols            grid resolution · lat-min / lat-max framing
overlays        position data-lat/data-lon children
animation       wave | noise | ripple | sweep | sparkle
max-dpr         cap the globe backing store (default 2)

Colours accept var(--x) and re-resolve when your theme changes — the toggle at the top of this page repaints every map above without a line of JavaScript. Full-screen versions: orbit · land styles · graticule & overlays · roll & tilt · Moon & Mars · performance · landing page.