/** * 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, versionAssetUrls, getSpaceShellMeta } 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)}

${m.standaloneDomain ? `${escapeHtml(m.standaloneDomain)}` : ""}

${escapeHtml(m.description)}

`, ) .join("\n"); return versionAssetUrls(` rSpace — Own Your Community Infrastructure
Try Demo
Community Platform

(you)rSpace

Remember back when the internet was cool?

We may not have MySpace anymore, but we have (you)rSpace.

Build digital spaces to collaborate on improving your physical world. Local-first, zero-knowledge privacy, outside the walls of big tech.

Start (you)rSpace Create a Space
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

Secure by default, not by opt-in.

Sign in once with your fingerprint or device PIN. Your passkey works across every rApp — no passwords, no email loops, no third-party auth providers watching over your shoulder.

  • WebAuthn passkeys — phishing-resistant, device-bound credentials
  • Guardian recovery — 2-of-3 trusted contacts restore access
  • Device linking — scan a QR to add your phone or tablet
  • One RP ID — works across all r*.online domains

Touch. Tap. Done.

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.

Under the hood: Automerge CRDTs model every document as a mergeable data structure. IndexedDB persists the full state locally so you never lose work. A Service Worker caches the app shell for instant loads — even without a network connection.

Interoperable by Design

Every rApp reads and writes to the same sync layer. No import/export rituals — data flows between tools automatically.

  rDocs   rCal   rTasks   rMaps   rWallet  ...
    |       |       |       |       |
    v       v       v       v       v
 +--------------------------------------+
 |     Automerge  Sync  Layer           |
 |   (encrypted, per-document keys)     |
 +--------------------------------------+
    |       |       |       |       |
    v       v       v       v       v
  Your    Your    Your    Your    Your
 Device  Phone  Laptop  Server  Backup

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 from landlords who read the mail, count the footsteps, and sell the maps.

Your space. Your community. Your rules.

${modules.length} rApps and Growing

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

${ecosystemCards}
`); } // ── Space Dashboard ── export function renderSpaceDashboard(space: string, modules: ModuleInfo[]): string { // Filter modules by space's enabledModules const enabledModules = getSpaceShellMeta(space).enabledModules; const visibleModules = enabledModules ? modules.filter(m => m.id === "rspace" || enabledModules.includes(m.id)) : modules; const moduleListJSON = JSON.stringify(visibleModules); const displayName = space === "demo" ? "Demo Space" : space; const subtitle = space === "demo" ? "Explore the rSpace ecosystem — click any rApp to try it live with sample data." : `${visibleModules.length} rApps available in this space.`; const appCards = visibleModules .map((m) => { return `
${m.icon}

${escapeHtml(m.name)}

${escapeHtml(m.description)}

`; }) .join("\n"); return versionAssetUrls(` ${escapeHtml(displayName)} | rSpace

${escapeHtml(displayName)}

${subtitle}

${appCards}
${space === "demo" ? ` ` : ""} `); } const SPACE_DASHBOARD_CSS = ` .sd-hero { text-align: center; padding: 100px 1.5rem 2rem; } .sd-hero__title { font-size: 2.25rem; font-weight: 700; margin: 0 0 0.5rem; background: var(--rs-gradient-brand); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } .sd-hero__subtitle { font-size: 1.05rem; color: var(--rs-text-secondary); margin: 0; max-width: 520px; margin: 0 auto; line-height: 1.5; } .sd-container { max-width: 1100px; margin: 0 auto; padding: 1rem 1.5rem 3rem; } .sd-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0.75rem; } .sd-card { display: flex; align-items: flex-start; gap: 1rem; padding: 1rem 1.25rem; background: var(--rs-card-bg); border: 1px solid var(--rs-card-border); border-radius: 0.75rem; text-decoration: none; color: inherit; transition: border-color 0.2s, background 0.2s, transform 0.15s; cursor: pointer; } .sd-card:hover { border-color: rgba(20,184,166,0.35); background: rgba(20,184,166,0.04); transform: translateY(-1px); } .sd-card__icon { font-size: 1.75rem; flex-shrink: 0; width: 2.5rem; height: 2.5rem; display: flex; align-items: center; justify-content: center; } .sd-card__body { min-width: 0; } .sd-card__name { font-size: 0.9rem; font-weight: 600; color: var(--rs-text-primary); margin: 0 0 0.2rem; } .sd-card__desc { font-size: 0.78rem; color: var(--rs-text-muted); margin: 0; line-height: 1.45; } .sd-footer { text-align: center; padding: 2rem 1.5rem 3rem; border-top: 1px solid var(--rs-border-subtle); } .sd-footer p { color: var(--rs-text-muted); font-size: 0.9rem; margin: 0; } .sd-footer a { color: var(--rs-accent); text-decoration: none; font-weight: 600; } .sd-footer a:hover { text-decoration: underline; } @media (max-width: 480px) { .sd-grid { grid-template-columns: 1fr; } .sd-hero__title { font-size: 1.75rem; } .sd-hero { padding-top: 80px; } } `; const MAIN_LANDING_CSS = ` /* ── Main landing page extras (on top of rl-* utilities) ── */ /* Background + diagonal stripe overlay */ body { background: linear-gradient( 170deg, #0a0f1e 0%, #0f172a 25%, #131b2e 45%, #0e1628 65%, #0d1424 85%, #080d19 100% ); background-attachment: fixed; } body::before { content: ""; position: fixed; inset: 0; background: repeating-linear-gradient( -45deg, transparent, transparent 60px, rgba(20,184,166,0.015) 60px, rgba(20,184,166,0.015) 61px ); pointer-events: none; z-index: 0; } [data-theme="light"] body { background: linear-gradient(170deg, #f8fafc 0%, #f1f5f9 50%, #e2e8f0 100%); } [data-theme="light"] body::before { background: repeating-linear-gradient( -45deg, transparent, transparent 60px, rgba(20,184,166,0.03) 60px, rgba(20,184,166,0.03) 61px ); } /* ── Hero ── */ .hero-glow-wrap { position: relative; overflow: hidden; } .hero-glow { position: absolute; top: 50%; left: 50%; width: 600px; height: 600px; transform: translate(-50%, -60%); border-radius: 50%; background: radial-gradient(circle, rgba(20,184,166,0.15) 0%, transparent 70%); animation: hero-pulse 4s ease-in-out infinite; pointer-events: none; z-index: 0; } [data-theme="light"] .hero-glow { background: radial-gradient(circle, rgba(20,184,166,0.1) 0%, transparent 70%); } @keyframes hero-pulse { 0%, 100% { opacity: 0.6; transform: translate(-50%, -60%) scale(1); } 50% { opacity: 1; transform: translate(-50%, -60%) scale(1.08); } } .rl-hero > * { position: relative; z-index: 1; } .main-wordmark { font-size: 3.5rem; text-shadow: 0 0 40px rgba(20,184,166,0.2); } @media (min-width: 640px) { .main-wordmark { font-size: 4.5rem; } } .main-wordmark__accent { background: var(--rs-gradient-brand); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } .accent-cool { color: var(--rs-accent); font-weight: 700; -webkit-text-fill-color: var(--rs-accent); } /* Badge bar with star separators */ .main-badges { display: flex; gap: 0.5rem; justify-content: center; flex-wrap: wrap; align-items: center; margin-top: 1.5rem; } .main-badges .rl-badge { font-weight: 800; letter-spacing: 0.04em; } .badge-sep { color: var(--rs-accent); font-size: 0.5rem; opacity: 0.6; } /* ── Feature Cards (SVG icons, border glow) ── */ .feat-card { background: var(--rs-card-bg); border: 2px solid var(--rs-card-border); border-radius: 1rem; padding: 1.75rem; text-align: center; transition: border-color 0.25s, box-shadow 0.25s, transform 0.2s; } .feat-card:hover { transform: translateY(-2px); } .feat-card--teal:hover { border-color: rgba(20,184,166,0.5); box-shadow: 0 0 20px rgba(20,184,166,0.12); } .feat-card--indigo:hover { border-color: rgba(99,102,241,0.5); box-shadow: 0 0 20px rgba(99,102,241,0.12); } .feat-card__icon { width: 3rem; height: 3rem; border-radius: 0.75rem; display: flex; align-items: center; justify-content: center; margin: 0 auto 1rem; font-size: 1.5rem; } .feat-card--teal .feat-card__icon { background: rgba(20,184,166,0.12); color: #14b8a6; } .feat-card--indigo .feat-card__icon { background: rgba(99,102,241,0.12); color: #6366f1; } .feat-card h3 { font-size: 0.95rem; font-weight: 600; color: var(--rs-text-primary); margin-bottom: 0.5rem; } .feat-card p { font-size: 0.875rem; color: var(--rs-text-secondary); line-height: 1.6; } /* ── EncryptID Visual ── */ .encryptid-visual { text-align: center; } .encryptid-shield { width: 140px; height: 160px; color: var(--rs-accent); filter: drop-shadow(0 0 20px rgba(20,184,166,0.2)); } .encryptid-visual__label { font-size: 0.9rem; font-weight: 600; color: var(--rs-text-secondary); margin-top: 1rem; } /* ── Offline-First Explainer ── */ .offline-explain { text-align: center; font-size: 0.85rem; color: var(--rs-text-muted); line-height: 1.65; max-width: 640px; margin: 2rem auto 0; border-top: 1px solid var(--rs-border-subtle); padding-top: 1.5rem; } .offline-explain strong { color: var(--rs-text-secondary); font-weight: 600; } /* ── Interop Diagram ── */ .interop-diagram { margin-top: 2rem; } .interop-pre { display: inline-block; text-align: left; font-family: "SF Mono", "Fira Code", "Cascadia Code", monospace; font-size: 0.78rem; line-height: 1.6; color: var(--rs-text-secondary); background: var(--rs-card-bg); border: 1px solid var(--rs-card-border); border-radius: 0.75rem; padding: 1.5rem 2rem; overflow-x: auto; max-width: 100%; box-shadow: 0 2px 12px rgba(0,0,0,0.15); } [data-theme="light"] .interop-pre { box-shadow: 0 2px 12px rgba(0,0,0,0.06); } /* ── Philosophy Punch ── */ .philosophy-punch { font-size: 1.3rem; font-weight: 700; background: var(--rs-gradient-brand); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; margin: 1.5rem 0 0; letter-spacing: -0.01em; } /* ── Ecosystem Cards ── */ .eco-card { background: var(--rs-card-bg); border: 2px solid var(--rs-card-border); border-radius: 1rem; padding: 1.5rem; text-align: center; transition: border-color 0.25s, box-shadow 0.25s, transform 0.2s; display: flex; flex-direction: column; align-items: center; } .eco-card:hover { border-color: rgba(20,184,166,0.4); box-shadow: 0 0 16px rgba(20,184,166,0.1); transform: translateY(-2px); } .eco-card__icon { font-size: 1.75rem; margin-bottom: 0.5rem; } .eco-card__name { font-size: 0.9rem; font-weight: 600; color: var(--rs-text-primary); margin: 0 0 0.15rem; } .eco-card__domain { font-size: 0.65rem; font-weight: 600; color: var(--rs-accent); opacity: 0.75; letter-spacing: 0.02em; margin-bottom: 0.4rem; display: block; } .eco-card__desc { font-size: 0.78rem; color: var(--rs-text-secondary); line-height: 1.5; margin: 0; } /* ── Footer ── */ .main-footer { border-top: 1px solid var(--rs-border-subtle); padding: 2.5rem 1.5rem 2rem; } .footer-quicklinks { display: flex; gap: 1.25rem; justify-content: center; flex-wrap: wrap; margin-bottom: 1.25rem; } .footer-quicklinks a { font-size: 0.75rem; font-weight: 600; color: var(--rs-text-muted); text-decoration: none; transition: color 0.2s; } .footer-quicklinks a:hover { color: var(--rs-accent); } .footer-tagline { font-size: 0.8rem; font-weight: 700; color: var(--rs-accent); letter-spacing: 0.06em; text-transform: uppercase; margin: 0 0 1rem; opacity: 0.7; } .footer-links { display: flex; gap: 1.5rem; justify-content: center; flex-wrap: wrap; margin-bottom: 1rem; } .footer-links a { color: #94a3b8; font-size: 0.8rem; text-decoration: none; transition: color 0.2s; } .footer-links a:hover { color: var(--rs-text-primary); } .footer-tech { color: #64748b; font-size: 0.75rem; margin: 0; } /* ── Retro Shadow Utility ── */ .retro-shadow { box-shadow: 2px 2px 0 var(--rs-card-border); } /* ── Responsive ── */ @media (max-width: 480px) { .main-wordmark { font-size: 2.75rem; } .hero-glow { width: 350px; height: 350px; } .interop-pre { font-size: 0.65rem; padding: 1rem; } .philosophy-punch { font-size: 1.1rem; } .footer-quicklinks { gap: 0.75rem; } } `;