// Home screen + app shell with routing, Tweaks integration. const { useState: useStateA, useEffect: useEffectA, useRef: useRefA, useMemo: useMemoA } = React; // Live countdown hook — returns ms remaining, re-renders each second function useCountdown() { const [ms, setMs] = useStateA(window.PLANETALIN.msUntilNextWindow()); useEffectA(() => { const iv = setInterval(() => setMs(window.PLANETALIN.msUntilNextWindow()), 1000); return () => clearInterval(iv); }, []); return ms; } function formatCountdown(ms) { if (ms <= 0) return '00:00:00'; const s = Math.floor(ms / 1000); const h = String(Math.floor(s / 3600)).padStart(2, '0'); const m = String(Math.floor((s % 3600) / 60)).padStart(2, '0'); const sec = String(s % 60).padStart(2, '0'); return `${h}:${m}:${sec}`; } function poeticWindow(ms) { const h = ms / 3600000; if (h > 20) return 'The sky has just closed.'; if (h > 16) return 'Stars are still settling.'; if (h > 10) return 'The cosmos is breathing.'; if (h > 5) return 'The window is narrowing to open.'; if (h > 1) return 'Soon. The light is gathering.'; if (h > 0.1) return 'Moments now. Listen for the bell.'; return 'The window opens.'; } // ============ Home ============ function HomeHero({ starfieldDensity, animIntensity }) { const canvasRef = useRefA(null); const cooldownMs = useCountdown(); const canCreate = cooldownMs === 0; const seal = useMemoA(() => window.PLANETALIN.getSeal(), []); const myWorlds = window.PLANETALIN.allPlanets().length; useEffectA(() => { if (!canvasRef.current) return; const seeds = window.PLANETALIN_HERO_SEEDS; const spec = seeds[Math.floor(Math.random()*seeds.length)]; const scene = window.PLANETALIN.createPlanetScene(canvasRef.current, { cameraDistance: 3.4, starfieldDensity, animIntensity, }); scene.setSpec(spec); return () => scene.dispose(); }, []); return (
PL·01 · constellation lab

PLANETALIN

Compose an atmosphere from its elements.
Seal a world that time will carry forward.

{!canCreate && (
{poeticWindow(cooldownMs)}
Next window opens in {formatCountdown(cooldownMs)}
You may shape one world per day. The rest is rest.
)}
{seal.glyph} your seal {seal.name} worlds shaped {myWorlds}
); } // ============ Catalogue import/export ============ function CatalogueActions() { const fileRef = useRefA(null); const [flash, setFlash] = useStateA(''); function toast(msg) { setFlash(msg); setTimeout(() => setFlash(''), 2000); } function exportAll() { const data = window.PLANETALIN.exportCatalogueJson(); if (!data.planets.length) { toast('Nothing to export yet'); return; } const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `planetalin-catalogue-${data.seal?.name||'anon'}-${Date.now()}.json`; a.click(); setTimeout(() => URL.revokeObjectURL(url), 1000); toast(`Exported ${data.planets.length} world${data.planets.length===1?'':'s'}`); } function triggerImport() { fileRef.current?.click(); } async function onFile(e) { const file = e.target.files?.[0]; if (!file) return; try { const text = await file.text(); const data = JSON.parse(text); if (data.format !== 'planetalin.catalogue.v1') throw new Error('Not a PLANETALIN catalogue'); let n = 0; for (const p of (data.planets||[])) { if (window.PLANETALIN.importCataloguePlanet(p)) n++; } toast(`Imported ${n} world${n===1?'':'s'}`); setTimeout(() => window.location.reload(), 900); } catch(err) { toast('Import failed: ' + err.message); } finally { e.target.value = ''; } } function seedSolarSystem() { const n = window.PLANETALIN.seedSolarSystem(); if (n === 0) { toast('Solar system already catalogued'); return; } toast(`Catalogued ${n} real planet${n===1?'':'s'} from our solar system`); setTimeout(() => window.location.reload(), 900); } return (
· · {flash && {flash}}
); } // ============ Passkey Gate ============ // Renders before the wizard on every create attempt. // Checks for a valid JWT first — if present, calls onAuthorized immediately. // If not, prompts the user to register (first time) or sign in (returning). // // The passkey identifies every world created in this session and lets the // user's constellation persist across devices and time. function PasskeyGate({ onAuthorized }) { const seal = useMemoA(() => window.PLANETALIN.getSeal(), []); const isReturning = window.PLANETALIN.hasRegisteredPasskey?.() || false; const supported = window.PLANETALIN.webAuthnSupported(); // 'check' | 'register' | 'login' | 'busy' | 'error' const [phase, setPhase] = useStateA('check'); const [error, setError] = useStateA(''); useEffectA(() => { const token = window.PLANETALIN.getToken?.(); if (window.PLANETALIN.isTokenValid?.(token)) { onAuthorized(token); return; } setPhase(isReturning ? 'login' : 'register'); }, []); async function doRegister() { setPhase('busy'); setError(''); try { const result = await window.PLANETALIN.performRegistration(seal); onAuthorized(result.token); } catch(e) { if (e.name === 'NotAllowedError') { setError('Passkey was dismissed. Try again when you\'re ready.'); } else { setError(e.message || 'Passkey creation failed'); } setPhase('register'); } } async function doLogin() { setPhase('busy'); setError(''); try { const result = await window.PLANETALIN.performLogin(null); // discoverable onAuthorized(result.token); } catch(e) { if (e.name === 'NotAllowedError') { setError('Sign-in was dismissed.'); } else { setError(e.message || 'Sign in failed'); } setPhase('login'); } } if (phase === 'check') return null; return (
{seal.glyph}

{phase === 'login' ? 'Welcome back, creator' : 'Identify your creation'}

{phase === 'register' || phase === 'busy' && !isReturning ? ( <> A passkey saves your progress and signs every world you create — so your constellation stays yours, across devices and across time.

Your private key never leaves your device. ) : ( <> Use your passkey to continue. Your worlds and seal will be ready. )}

{!supported && (
This browser does not support passkeys. Try Safari, Chrome, or Firefox on a modern device.
)} {error &&
{error}
} {phase === 'busy' && (
authorizing…
)} {phase === 'register' && supported && (
{isReturning && ( )}
)} {phase === 'login' && supported && (
)}

Sealed with {seal.name}

); } // ============ Create wizard host ============ function CreateFlow({ forkFromId, starfieldDensity, animIntensity }) { const { defaultSpec, WIZ_STEPS, StepAtmosphere, StepSurface, StepMoons, StepRings, StepStar, StepSeal } = window.PLANETALINWizard; // Token state — null means gate not yet passed const [token, setToken] = useStateA(() => { const t = window.PLANETALIN.getToken?.(); return window.PLANETALIN.isTokenValid?.(t) ? t : null; }); const initial = useMemoA(() => { if (forkFromId) { const src = window.PLANETALIN.findById(forkFromId); if (src) { return { ...JSON.parse(JSON.stringify(src)), name: '', discoverer: '', id: undefined, createdAt: undefined }; } } return defaultSpec(); }, [forkFromId]); const [spec, setSpec] = useStateA(initial); const [stepIdx, setStepIdx] = useStateA(0); const [sealState, setSealState] = useStateA('idle'); // idle | sealing | done | blocked | error const canvasRef = useRefA(null); const sceneRef = useRefA(null); useEffectA(() => { if (!canvasRef.current || !token) return; // don't init canvas until past gate const scene = window.PLANETALIN.createPlanetScene(canvasRef.current, { cameraDistance: 3.3, starfieldDensity, animIntensity, }); sceneRef.current = scene; scene.setSpec(spec); return () => scene.dispose(); }, [token]); // re-run when token is set (gate cleared) useEffectA(() => { if (sceneRef.current) sceneRef.current.setSpec(spec); }, [spec]); useEffectA(() => { if (sceneRef.current) sceneRef.current.update({ starfieldDensity, animIntensity }); }, [starfieldDensity, animIntensity]); // ── Gate: show PasskeyGate until token is obtained ────────────────────── if (!token) { return setToken(tok)} />; } // ── Seal: call server to persist planet ────────────────────────────────── async function onSeal() { if (sealState === 'sealing' || sealState === 'done') return; setSealState('sealing'); try { const seal = window.PLANETALIN.getSeal(); const specWithMeta = { ...spec, createdAt: new Date().toISOString(), seal, }; const result = await window.PLANETALIN.createPlanet(specWithMeta); // result.spec has the server-stamped canonical PL-X##-YYY id const savedSpec = typeof result.spec === 'string' ? JSON.parse(result.spec) : result.spec; window.PLANETALIN.saveOne(savedSpec); // mirror locally for offline viewer access window.PLANETALIN.markCreated(); // also update local cooldown for UI setSealState('done'); setTimeout(() => window.PLANETALIN.go(`planet/${result.id}`), 1200); } catch(e) { if (e.code === 'COOLDOWN_ACTIVE') { setSealState('blocked'); setTimeout(() => setSealState('idle'), 2600); } else if (e.status === 401) { // JWT expired mid-session — send back through the gate window.PLANETALIN.clearToken?.(); setToken(null); setSealState('idle'); } else { setSealState('error'); setTimeout(() => setSealState('idle'), 2600); } } } const step = WIZ_STEPS[stepIdx]; const palette = window.PLANETALIN.derivePalette(spec.atmosphere || []); return (
{(sealState === 'sealing' || sealState === 'done') && (
SEALED · IMMUTABLE · CATALOGUED · ETERNAL ·
{sealState==='sealing' && 'sealing the specification…'} {sealState==='done' && 'world catalogued.'}
)}
{WIZ_STEPS.map((s, i) => ( ))}
{forkFromId &&
forked from {forkFromId}
}
{step.id === 'atmosphere' && } {step.id === 'surface' && } {step.id === 'moons' && } {step.id === 'rings' && } {step.id === 'star' && } {step.id === 'seal' && }
{WIZ_STEPS.map((_,i) => )}
); } // ============ Tweaks panel ============ function TweaksPanel({ tweaks, setTweaks, onSave, visible }) { if (!visible) return null; return (
Tweaks
{ const v=+e.target.value; setTweaks({...tweaks, starfieldDensity:v}); onSave({starfieldDensity:v}); }} /> {tweaks.starfieldDensity.toFixed(1)}×
{ const v=+e.target.value; setTweaks({...tweaks, animIntensity:v}); onSave({animIntensity:v}); }} /> {tweaks.animIntensity.toFixed(1)}×
); } // ============ Dev panel (?dev=1) ============ function DevPanel() { const [open, setOpen] = useStateA(true); const [flash, setFlash] = useStateA(''); function toast(msg) { setFlash(msg); setTimeout(() => setFlash(''), 1800); } return (
{open && (
dev tools ?dev=1
Cooldown
Storage
Auth
Seed
{flash &&
{flash}
}
)}
); } // ============ Root ============ const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "starfieldDensity": 1.0, "animIntensity": 1.0 }/*EDITMODE-END*/; function App() { const [route, setRoute] = useStateA(window.PLANETALIN.parseHash()); const [tweaks, setTweaks] = useStateA(TWEAK_DEFAULTS); const [tweaksVisible, setTweaksVisible] = useStateA(false); useEffectA(() => { const onHash = () => setRoute(window.PLANETALIN.parseHash()); window.addEventListener('hashchange', onHash); const onMsg = (e) => { if (e.data?.type === '__activate_edit_mode') setTweaksVisible(true); if (e.data?.type === '__deactivate_edit_mode') setTweaksVisible(false); }; window.addEventListener('message', onMsg); try { window.parent.postMessage({type:'__edit_mode_available'}, '*'); } catch(e){} return () => { window.removeEventListener('hashchange', onHash); window.removeEventListener('message', onMsg); }; }, []); function saveTweaks(edits) { try { window.parent.postMessage({type:'__edit_mode_set_keys', edits}, '*'); } catch(e){} } const common = { starfieldDensity: tweaks.starfieldDensity, animIntensity: tweaks.animIntensity }; return ( <> {route.route === 'home' && } {route.route === 'create' && } {route.route === 'planet' && (() => { const p = window.PLANETALIN.findById(route.id); if (!p) return ; return ; })()} {route.route === 'permalink' && (() => { try { const p = window.PLANETALIN.decodeSpec(route.encoded); return ; } catch(e) { return ; } })()} {route.route === 'constellation' && } {window.PLANETALIN.isDevMode() && } ); } function NotFound() { return (
404 · uncharted

No such world

This planet is not in the catalogue.

); } ReactDOM.createRoot(document.getElementById('root')).render();