From c59b7d85a1d241ab0a300abed1deeb40edbc00a4 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Sat, 28 Feb 2026 03:48:02 +0000 Subject: [PATCH] feat: replace static landing page with server-rendered main landing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the 177KB static Next.js landing with a server-rendered page using the same shell, theme, and rl-* CSS utilities as all module landings. Fixes the 354KB logo.png → 1.6KB favicon.png glitch across all pages. Dynamic ecosystem grid is always in sync with registered modules. Co-Authored-By: Claude Opus 4.6 --- server/index.ts | 16 +-- server/landing.ts | 263 ++++++++++++++++++++++++++++++++++++++++++++++ server/shell.ts | 8 +- 3 files changed, 271 insertions(+), 16 deletions(-) create mode 100644 server/landing.ts 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) => ` + +
${m.icon}
+

${escapeHtml(m.name)}

+

${escapeHtml(m.description)}

+
`, + ) + .join("\n"); + + return ` + + + + + + rSpace — Community Platform + + + + + + + + +
+
+ + +
+
+ +
+
+ Try Demo + +
+
+ + +
+ Community Platform +

(ou)rSpace

+

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. +

+ +
+ Local-First + Self-Hosted + Open Source + Offline-Ready +
+
+ + +
+
+

Everything Your Community Needs

+

+ Each tool works on its own, and they all work together. +

+
+
+
💰
+

Community Funds

+

Budget rivers, revenue splits, and enoughness thresholds. Transparent finances with quadratic funding built in.

+
+
+
💬
+

Messaging & Forum

+

Real-time chat, threaded discussions, and async forums. All synced across devices with CRDT magic.

+
+
+
📁
+

Files & Media

+

Upload, organize, and share with memory cards. Public links, folder trees, and metadata that travels with content.

+
+
+
🔐
+

Passkey Identity

+

One passwordless login for the entire ecosystem. EncryptID uses WebAuthn — no passwords to leak, no accounts to hack.

+
+
+
📊
+

Dashboards & Data

+

Community analytics, voting results, spatial canvases. See everything at a glance and drill into what matters.

+
+
+
🛡
+

Privacy by Design

+

Your data lives on your device first. End-to-end encrypted sync, per-document keys, zero-knowledge architecture.

+
+
+
+
+ + +
+
+
+
+ EncryptID +

One Passkey for Everything

+

+ 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. +

+
    +
  • WebAuthn passkeys — phishing-resistant by default
  • +
  • Guardian recovery — 2-of-3 trusted contacts restore access
  • +
  • Device linking — QR scan adds your phone or tablet
  • +
  • One RP ID — shared across all r*.online domains
  • +
+
+
+
+
🔒
+

Passwordless Login

+

Touch your fingerprint sensor, tap your security key, or use your device PIN. That’s it.

+
+
+
+
+
+ + +
+
+

Offline-First, Always

+

+ Your data lives on your device. Changes sync when you’re online, + merge automatically when you’re not. +

+
+
+
1
+

Local Persistence

+

Every document is stored in encrypted IndexedDB on your device. Works without internet.

+
+
+
2
+

Auto-Merge CRDT

+

Automerge CRDTs resolve conflicts automatically. No “someone else is editing” lockouts.

+
+
+
3
+

Incremental Sync

+

Only changed bytes travel over the wire. Reconnect after days offline and catch up in seconds.

+
+
+
+
+ + +
+
+

The Internet as It Was Always Meant to Be

+

+ 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. +

+ +
+
+ + +
+
+

${modules.length} rApps and Growing

+

+ Each app is independent and composable. Use one, use all, mix and match. +

+
+ ${ecosystemCards} +
+
+
+ + + + + + +`; +} + +const MAIN_LANDING_CSS = ` +/* Main landing page extras (on top of rl-* utilities) */ +.main-wordmark { + font-size: 3.5rem; +} +@media (min-width: 640px) { .main-wordmark { font-size: 4.5rem; } } +.main-wordmark__accent { + background: linear-gradient(135deg, #14b8a6, #22d3ee); + -webkit-background-clip: text; -webkit-text-fill-color: transparent; + background-clip: text; +} +.main-badges { + display: flex; gap: 0.5rem; justify-content: center; flex-wrap: wrap; + margin-top: 1.5rem; +} +.main-footer { + border-top: 1px solid rgba(255,255,255,0.06); + padding: 2.5rem 1.5rem; +} +`; diff --git a/server/shell.ts b/server/shell.ts index 8dee908..f237939 100644 --- a/server/shell.ts +++ b/server/shell.ts @@ -72,7 +72,7 @@ export function renderShell(opts: ShellOptions): string {
- +
@@ -468,7 +468,7 @@ export function renderModuleLanding(opts: ModuleLandingOptions): string {
- + Try Demo
@@ -502,7 +502,7 @@ export function renderModuleLanding(opts: ModuleLandingOptions): string { `; } -const MODULE_LANDING_CSS = ` +export const MODULE_LANDING_CSS = ` body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); @@ -543,7 +543,7 @@ body { @media (max-width: 600px) { .ml-name { font-size: 2rem; } .ml-icon { font-size: 3rem; } } `; -const RICH_LANDING_CSS = ` +export const RICH_LANDING_CSS = ` /* ── Rich Landing Page Utilities ── */ .rl-section { border-top: 1px solid rgba(255,255,255,0.06);