diff --git a/server/index.ts b/server/index.ts
index 60bee06..a3504f9 100644
--- a/server/index.ts
+++ b/server/index.ts
@@ -65,6 +65,7 @@ import { photosModule } from "../modules/photos/mod";
import { socialsModule } from "../modules/rsocials/mod";
import { spaces } from "./spaces";
import { renderShell, renderModuleLanding } from "./shell";
+import { renderMainLanding } from "./landing";
import { fetchLandingPage } from "./landing-proxy";
import { syncServer } from "./sync-instance";
import { loadAllDocs } from "./local-first/doc-persistence";
@@ -808,18 +809,9 @@ for (const mod of getAllModules()) {
// ── Page routes ──
-// Landing page: rspace.online/ → serve original rSpace-website landing
-app.get("/", async (c) => {
- const landing = Bun.file(resolve(DIST_DIR, "landing.html"));
- if (await landing.exists()) {
- return new Response(landing, { headers: { "Content-Type": "text/html" } });
- }
- // Fallback to index.html
- const file = Bun.file(resolve(DIST_DIR, "index.html"));
- if (await file.exists()) {
- return new Response(file, { headers: { "Content-Type": "text/html" } });
- }
- return c.text("rSpace", 200);
+// Landing page: rspace.online/ → server-rendered main landing
+app.get("/", (c) => {
+ return c.html(renderMainLanding(getModuleInfoList()));
});
// About/info page (full landing content)
diff --git a/server/landing.ts b/server/landing.ts
new file mode 100644
index 0000000..5d65c36
--- /dev/null
+++ b/server/landing.ts
@@ -0,0 +1,263 @@
+/**
+ * Main landing page for rspace.online/
+ *
+ * Server-rendered using the same shell (header, CSS, theme) as all module
+ * landing pages. Content is ported from the old static Next.js export and
+ * adapted to use the shared rl-* utility classes.
+ */
+
+import type { ModuleInfo } from "../shared/module";
+import { escapeHtml, escapeAttr, MODULE_LANDING_CSS, RICH_LANDING_CSS } from "./shell";
+
+export function renderMainLanding(modules: ModuleInfo[]): string {
+ const moduleListJSON = JSON.stringify(modules);
+ const demoUrl = "https://demo.rspace.online/rspace";
+
+ // Build the ecosystem grid dynamically from registered modules
+ const ecosystemCards = modules
+ .map(
+ (m) => `
+
+
+ ${escapeHtml(m.description)}${escapeHtml(m.name)}
+
Remember back when the internet was cool?
++ A collaborative, local-first platform with ${modules.length}+ interoperable tools. + Own your data, run your community, connect your world — no landlords required. +
++ Each tool works on its own, and they all work together. +
+Budget rivers, revenue splits, and enoughness thresholds. Transparent finances with quadratic funding built in.
+Real-time chat, threaded discussions, and async forums. All synced across devices with CRDT magic.
+Upload, organize, and share with memory cards. Public links, folder trees, and metadata that travels with content.
+One passwordless login for the entire ecosystem. EncryptID uses WebAuthn — no passwords to leak, no accounts to hack.
+Community analytics, voting results, spatial canvases. See everything at a glance and drill into what matters.
+Your data lives on your device first. End-to-end encrypted sync, per-document keys, zero-knowledge architecture.
++ Sign in once with your fingerprint or device PIN. Your passkey works + across every rApp — no passwords, no email verification loops, + no third-party auth providers. +
+Touch your fingerprint sensor, tap your security key, or use your device PIN. That’s it.
++ Your data lives on your device. Changes sync when you’re online, + merge automatically when you’re not. +
+Every document is stored in encrypted IndexedDB on your device. Works without internet.
+Automerge CRDTs resolve conflicts automatically. No “someone else is editing” lockouts.
+Only changed bytes travel over the wire. Reconnect after days offline and catch up in seconds.
++ No algorithms deciding what you see. No ads. No data harvesting. + Just tools that work for you, run by you, owned by you. + rSpace is infrastructure for communities who refuse to rent their digital commons. +
++ Each app is independent and composable. Use one, use all, mix and match. +
+