Skip to content

meleongg/Stamped

Repository files navigation

Stamped

A personal world travel map. Mark the countries you've been to, share your map with a link, and compare it side-by-side with a friend's. No login, no backend — your data stays in your browser.

Features

  • Click countries to mark them visited / planning / want-to-visit
  • Per-country notes and visit dates in a side panel
  • Shareable links with server-rendered OG image previews for iMessage, Twitter, Discord, etc.
  • Side-by-side comparison with a friend's map (overlap, gaps, "add their places to my list")
  • Country search with Cmd/Ctrl + K, focuses + zooms the map to the result
  • Light and dark mode, fully responsive
  • Local-first: everything is stored in localStorage; no account, no tracking beyond anonymous usage analytics

Quick start

npm install
npm run dev
# open http://localhost:3000

Scripts

Command Description
npm run dev Dev server with Turbopack
npm run build Production build
npm run start Serve the production build
npm run lint ESLint (flat config)
npm run format Format every file with Prettier
npm run format:check Check formatting without writing (CI mode)
npm run build:cities Rebuild city catalog from Natural Earth sources

Stack

How sharing works

When you share, Stamped stores a short snapshot of your map in Upstash Redis (via the Vercel Marketplace) and gives you a short link:

/m/k7x9m2

What's stored: your map name, country statuses, and city statuses (notes and visit dates are never included).

Retention: links expire after 90 days of inactivity; updating your map from the same browser refreshes the expiry and updates the same link.

No account: your browser keeps an anonymous edit token in localStorage so edits don't create a new link. No email, login, or personal profile is required.

The shared page:

  1. Renders a read-only map from the stored snapshot
  2. Generates a dynamic OG image via opengraph-image.tsx for iMessage, Twitter, Discord, etc.
  3. Offers Compare with my map/compare/[id] overlays the visitor's localStorage data with the shared map

Local setup: install the Upstash integration in Vercel, then vercel env pull .env.development.local (see .env.example).

Project structure

app/
├── components/      UI primitives + composed components (MapView, NoteSidebar, CountrySearch, ...)
├── compare/[them]/  Compare-with-a-friend route
├── m/[data]/        Read-only shared map viewer + dynamic OG image
├── hooks/           Custom hooks (useMapData)
├── utils/           Pure utilities (geo, share payload, stats, storage)
├── lib/             Server helpers (Redis share store)
├── api/share/       Create and update share links
├── constants/       Status palette, continents, dimensions
└── contexts/        Theme provider
components/ui/       shadcn-generated primitives
public/
├── world-atlas/     Bundled TopoJSON country boundaries (countries-110m.json)
└── cities/          Generated city catalog (populated-places.json)
scripts/
├── build-city-catalog.mjs   Builds public/cities/populated-places.json
└── sources/                 Natural Earth GeoJSON inputs (not required at runtime)

Attribution

  • Country boundaries and names from Natural Earth (Admin 0 – Countries), packaged as TopoJSON via world-atlas (ISC License © 2013-2019 Michael Bostock)
  • City names and locations from Natural Earth Populated Places (10m cultural vectors), filtered to capitals and major cities and bundled locally
  • Icons from Lucide, ISC License

Map boundaries, country labels, and city locations reflect Natural Earth’s cartographic choices, not a political position by Stamped.

Updating countries & cities

Map geometry and city search both come from bundled static files, not a live API. When Natural Earth or world-atlas releases updates (or you want to change which cities are included), refresh the data locally and commit the regenerated assets.

Countries (map polygons)

Source: world-atlas countries-110m (TopoJSON, ~177 countries at 110m resolution).

File: public/world-atlas/countries-110m.json

Steps:

  1. Download or build a new countries-110m.json from world-atlas (or convert from a newer Natural Earth Admin 0 shapefile using topojson).
  2. Replace public/world-atlas/countries-110m.json.
  3. If new ISO numeric codes appear, add them to the continent buckets in app/constants/continents.ts so stats (“continents visited”) stay correct.
  4. Run npm run build and smoke-test: click countries, search (Cmd/Ctrl+K), share links, compare view, OG image.

Note: The 110m map omits many small states and territories (e.g. Singapore, Monaco, Hong Kong as separate polygons). City pins for those places still work; only country-level clicking is limited.

Cities (search, pins, country names)

Sources (place in scripts/sources/):

Output: public/cities/populated-places.json (generated; do not edit by hand)

Steps:

  1. Download fresh GeoJSON from Natural Earth (or the natural-earth-vector repo) into scripts/sources/ with the filenames above.

  2. Run:

    npm run build:cities

    This rebuilds the catalog with:

    • Zero-padded ISO numeric countryCode values (aligned with the map)
    • countryName on each city and a top-level countryNames lookup (for places missing from the 110m map)
  3. Adjust filters in scripts/build-city-catalog.mjs if needed (default: Admin-0 capitals + primary cities SCALERANK <= 5 + secondary SCALERANK = 6).

  4. Commit both any source updates under scripts/sources/ (if you version them) and the regenerated public/cities/populated-places.json.

  5. Run npm run build and test: city search, stamp/unstamp, country sidebar city picker, share/compare with cities, zoom-to-pin.

Share links & stored user data

Change What to do
New cities / country name fixes only Regenerate catalog (build:cities); existing share links and localStorage maps keep working.
Country codes change (rare) Users’ saved maps may point at old codes; consider a one-time migration in app/utils/storage.ts or bump share format (below).
Breaking share payload Increment SHARE_FORMAT_VERSION in app/utils/share.ts and add a decode path for older versions if you still want old links to work. Old links without a decoder will show “unsupported version”.

User maps and notes live in the browser (localStorage); refreshing Natural Earth data does not migrate or delete user data automatically.

Quick checklist after any data refresh

  • npm run lint
  • npm run build
  • Spot-check a large country, a microstate city (e.g. Singapore), and a shared link
  • Confirm search shows country names, not numeric codes

About

Track your travels on an interactive world map, then share and compare maps with friends.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors