/** * 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
The rStack of rApps

(you)rSpace

One platform. Every tool your community needs. All talking to each other.

Data flows between apps automatically — no import/export rituals. Local-first, encrypted, and yours.

Start (you)rSpace Explore the rApps
Local-First Multiplayer Encrypted Open Source

One rStack. Every Tool Connected.

rApps talk to each other through one shared sync layer. Here’s what that looks like.

📅 rCal 📋 rTasks 💬 rChats

Schedule a meeting, auto-generates a task, notifies your space.

☑ rChoices 💰 rWallet

Vote passes, budget allocation releases automatically.

🗺 rMaps 📝 rDocs

Pin a community location, shared doc created for that place.

⏱ rTime 📊 rData

Log commitments, analytics update in real-time.

${modules.length} rApps and Growing

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

${ecosystemCards}

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.

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.

Built for Communities, Not Corporations

No algorithms deciding what you see. No ads. No data harvesting. Just tools that work for you, run by you, owned by you.

Your space. Your community. Your rules.

`); } // ── 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 ── */ .rl-hero { padding-top: 2rem; } .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; } /* 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; } /* ── Flow Cards (interop demos) ── */ .flow-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1rem; margin-top: 1.5rem; } .flow-card { background: var(--rs-card-bg); border: 2px solid var(--rs-card-border); border-radius: 1rem; padding: 1.5rem; transition: border-color 0.25s, box-shadow 0.25s, transform 0.2s; } .flow-card:hover { border-color: rgba(20,184,166,0.4); box-shadow: 0 0 16px rgba(20,184,166,0.1); transform: translateY(-2px); } .flow-card__pills { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.75rem; } .flow-pill { font-size: 0.8rem; font-weight: 600; color: var(--rs-text-primary); background: rgba(20,184,166,0.08); border: 1px solid rgba(20,184,166,0.2); border-radius: 2rem; padding: 0.3rem 0.75rem; white-space: nowrap; } .flow-arrow { color: var(--rs-accent); font-size: 1rem; opacity: 0.7; } .flow-card__desc { font-size: 0.85rem; color: var(--rs-text-secondary); line-height: 1.5; margin: 0; } /* ── 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; } /* ── 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; } /* ── Responsive ── */ @media (max-width: 480px) { .main-wordmark { font-size: 2.75rem; } .hero-glow { width: 350px; height: 350px; } .flow-cards { grid-template-columns: 1fr; } .philosophy-punch { font-size: 1.1rem; } .footer-quicklinks { gap: 0.75rem; } } `;