An interactive globe that detects a visitor's timezone, language and location — so your app greets them right from the first second.
npm i @planetlogin/planetlogin Why PlanetLogin
One pick on a real 3D globe and you know who just arrived — no IP lookups, no consent banners, no third-party SDK.
Orthographic projection via d3-geo — proper hemisphere clipping, drag with inertia, wheel zoom, click a country.
Returns IANA timezone, BCP-47 language and ISO country from a single pick. ~150 countries mapped to a language.
Keyless geocoding (Open-Meteo + OSM Nominatim). No analytics, no cookies beyond your own. AGPL-3.0.
Ships as a standard Web Component, a class and a factory. React, Vue, Svelte, Angular or plain HTML. ~17 kB gzip.
Keyboard-controllable globe (arrows, +/-, Enter), focus ring, ARIA labels, and respects prefers-reduced-motion.
Typed API, ESM + UMD builds, and a tiny readable codebase you can fork and own.
Quickstart
Listen for one locale event and wire it into your i18n and date handling.
import '@planetlogin/planetlogin'; <!-- anywhere in your HTML --> <planet-login accent="#f6a13c"></planet-login> const el = document.querySelector('planet-login'); el.addEventListener('locale', (e) => { const { language, timezone, country } = e.detail; i18n.setLanguage(language); });
import { useEffect, useRef } from 'react'; import { createPlanetLogin } from '@planetlogin/planetlogin'; function GlobeLogin({ onLocale }) { const ref = useRef(null); useEffect(() => { const g = createPlanetLogin(ref.current, { onLocale }); return () => g.destroy(); }, []); return <div ref={ref} style={{ height: 440 }} />; }
import { onMounted, onBeforeUnmount, ref } from 'vue'; import { createPlanetLogin } from '@planetlogin/planetlogin'; const el = ref(); let g; onMounted(() => { g = createPlanetLogin(el.value, { onLocale: l => emit('locale', l) }); }); onBeforeUnmount(() => g?.destroy());
import { onDestroy } from 'svelte'; import { createPlanetLogin } from '@planetlogin/planetlogin'; let el, g; $: if (el && !g) g = createPlanetLogin(el, { onLocale }); onDestroy(() => g?.destroy());
The payload
Delivered three ways: the locale DOM event, the on('locale', …) listener, or the onLocale option.
interface PlanetLocale { lat: number; lon: number; country: string; // ISO 3166-1 alpha-2 timezone: string; // "Europe/Madrid" or "UTC±N" language: string; // "es" label: string; // "Barcelona, Spain" }
More than a globe
The globe is the face. Behind it, @planetlogin/core is a framework-agnostic
login service: password, OAuth/OIDC, magic links, passkeys, TOTP and anonymous sessions. It signs an
asymmetric JWT (EdDSA) and publishes a JWKS. PlanetLogin keeps no database of its own — it reads and
writes your store over REST (or in-process), so your app stays the source of truth. Need zero backend?
Run anonymous guest sessions with no store at all. The stores below are optional batteries.
// A stateless login — no session table, no DB inside PlanetLogin. import { passwordLogin, verifyPassword, signSession } from '@planetlogin/core'; const result = await passwordLogin( { downstream, verifyPassword, signSession }, { identifier: email, password }, ); if (result.ok) setCookie('session', result.token); // signed JWT (EdDSA)
// Your app verifies the token offline — no shared secret, just the JWKS. import { createRemoteJWKSet, jwtVerify } from 'jose'; const jwks = createRemoteJWKSet(new URL( 'https://auth.acme.com/auth/.well-known/jwks.json')); const { payload } = await jwtVerify(token, jwks); const userId = payload.sub; // authenticated, anywhere, offline
// Batteries-included persistence — accounts with zero downstream code. import { postgresStore } from '@planetlogin/store-postgres'; // or store-sqlite const downstream = postgresStore(pool); await downstream.ensureSchema(); await downstream.createUser({ email, password }); // That's the whole identity layer — passwordLogin() now just works.
@planetlogin/corethe auth core
@planetlogin/planetloginthe globe component
@planetlogin/store-sqliteaccounts in one file
@planetlogin/store-postgresaccounts on Postgres
Ship it as a flavor (a ready portal for your framework — SvelteKit today, more coming) or embed the core directly. Read the whitepaper →
Open source
AGPL-3.0 with a small attribution term. Free for any use, including commercial. Issues and PRs welcome.