import { useEffect, useMemo, useState } from 'react' import { planCastle } from '../../src/shared/castle.ts' import { BotIcon, PersonIcon, SyncIcon } from './icons.tsx' import { navigate, signInHref, signOutHref } from './nav.ts' import type { BlockRow, BuildRow, PlayerRow } from './powersync.ts' import { World } from './World.tsx' const PREVIEW_WORLD = 'preview' const noop = () => {} /** A castle that builds itself in a loop: a local stand-in for an agent build streaming in. */ function usePreviewBlocks() { const plan = useMemo(() => planCastle(0, 0), []) const [count, setCount] = useState(0) useEffect(() => { const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches if (reduce) { setCount(plan.length) return } const t = setInterval(() => setCount((c) => (c >= plan.length + 60 ? 0 : c + 12)), 120) return () => clearInterval(t) }, [plan]) return useMemo>( () => plan.slice(0, Math.min(count, plan.length)).map((b) => ({ id: `${b.x}:${b.y}:${b.z}`, world_id: PREVIEW_WORLD, x: b.x, y: b.y, z: b.z, material: b.material, build_id: null, placed_by: null, seq: null, })), [plan, count], ) } export function Landing({ appBase }: { appBase: string }) { const blocks = usePreviewBlocks() const [me, setMe] = useState<{ signedIn: boolean; displayName?: string } | null>(null) const [creating, setCreating] = useState(false) const [error, setError] = useState(null) useEffect(() => { void fetch(`${appBase}/api/me`, { credentials: 'same-origin' }) .then((r) => r.json()) .then(setMe, () => setMe({ signedIn: false })) }, [appBase]) async function createRoom() { setCreating(true) setError(null) try { const res = await fetch(`${appBase}/api/rooms`, { method: 'POST', credentials: 'same-origin' }) const body = (await res.json().catch(() => ({}))) as { room?: { slug: string }; error?: string } if (!res.ok || !body.room) throw new Error(body.error ?? 'Could not create a room. Try again.') navigate(`/r/${body.room.slug}`) } catch (e) { setError(e instanceof Error ? e.message : 'Could not create a room. Try again.') setCreating(false) } } const signedOut = me !== null && !me.signedIn return (
World Builder
{me?.signedIn ? ( Sign out ) : signedOut ? ( Sign in to build ) : null}

Live multiplayer · humans + agents

Build a 3D world together, with your friends and your AI agents.

Open a room, share the link, and place blocks together. Then paste one prompt into Claude, ChatGPT or Grok and ask for a castle. Everyone in the room watches it go up block by block.

{signedOut ?

Sign in to build and create your own room, or watch the main world.

: null} {error ? (

{error}

) : null}
  • One-click rooms with a private share link
  • Bring your own agent over Kody MCP or the CLI
  • Live sync for every block, powered by PowerSync
) }