From 2ec502728598f8629e46422ee53ae5d35b318354 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Fri, 27 Feb 2026 17:50:30 -0800 Subject: [PATCH] feat: restyle rSpace.online about page to rApp theme + (you)rSpace CTA in space switcher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restyled website/index.html to use the standard rl-* rich landing CSS utilities matching all rApp module landing pages: rl-hero, rl-section, rl-card, rl-grid-3, rl-icon-box, rl-cta-primary/secondary. All original content preserved (EncryptID, Offline-First, Interoperable, Newsletter). Added (you)rSpace CTA button in the space switcher dropdown — shows "Sign in to create" or "Create (you)rSpace" when user has no owned space, with auto-provision flow on click. Co-Authored-By: Claude Opus 4.6 --- shared/components/rstack-space-switcher.ts | 98 ++- website/index.html | 764 +++++++++------------ 2 files changed, 432 insertions(+), 430 deletions(-) diff --git a/shared/components/rstack-space-switcher.ts b/shared/components/rstack-space-switcher.ts index 14e94be..353c332 100644 --- a/shared/components/rstack-space-switcher.ts +++ b/shared/components/rstack-space-switcher.ts @@ -9,7 +9,7 @@ * Passes auth token so the API returns private spaces the user can access. */ -import { isAuthenticated, getAccessToken } from "./rstack-identity"; +import { isAuthenticated, getAccessToken, getUsername } from "./rstack-identity"; import { rspaceNavUrl, getCurrentModule as getModule } from "../url-helpers"; interface SpaceInfo { @@ -117,26 +117,47 @@ export class RStackSpaceSwitcher extends HTMLElement { } #renderMenu(menu: HTMLElement, current: string) { + const auth = isAuthenticated(); + if (this.#spaces.length === 0) { + let cta = ""; + if (!auth) { + cta = this.#yourSpaceCTAhtml("Sign in to create →"); + } else { + cta = this.#yourSpaceCTAhtml("Create (you)rSpace →"); + } menu.innerHTML = ` + ${cta} +
+ Create new space `; + this.#attachYourSpaceCTA(menu); return; } const moduleId = this.#getCurrentModule(); - const auth = isAuthenticated(); // 3-section split const mySpaces = this.#spaces.filter((s) => s.role); const publicSpaces = this.#spaces.filter((s) => s.accessible !== false && !s.role); const discoverSpaces = this.#spaces.filter((s) => s.accessible === false); + const hasOwnedSpace = mySpaces.some((s) => s.relationship === "owner"); + let html = ""; + // ── Create (you)rSpace CTA — only if user has no owned spaces ── + if (!auth) { + html += this.#yourSpaceCTAhtml("Sign in to create →"); + html += `
`; + } else if (!hasOwnedSpace) { + html += this.#yourSpaceCTAhtml("Create (you)rSpace →"); + html += `
`; + } + // ── Your spaces ── if (mySpaces.length > 0) { html += ``; @@ -219,6 +240,62 @@ export class RStackSpaceSwitcher extends HTMLElement { this.#showEditSpaceModal(el.dataset.editSlug!, el.dataset.editName!); }); }); + + // Attach "(you)rSpace" CTA listener + this.#attachYourSpaceCTA(menu); + } + + #yourSpaceCTAhtml(buttonLabel: string): string { + return ` +
+ 🔒 + (you)rSpace + +
`; + } + + #attachYourSpaceCTA(menu: HTMLElement) { + const btn = menu.querySelector("#yourspace-cta"); + if (!btn) return; + + btn.addEventListener("click", (e) => { + e.stopPropagation(); + if (!isAuthenticated()) { + // Find on page and open auth modal + const identity = document.querySelector("rstack-identity") as any; + if (identity?.showAuthModal) { + identity.showAuthModal(); + } + } else { + // Authenticated but no owned space — auto-provision + this.#autoProvision(); + } + }); + } + + async #autoProvision() { + const token = getAccessToken(); + const username = getUsername(); + if (!token) return; + + try { + const res = await fetch("/api/spaces/auto-provision", { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }); + const data = await res.json(); + if (data.slug) { + window.location.href = `/${data.slug}/canvas`; + } else if (username) { + window.location.href = `/${username}/canvas`; + } + } catch { + // Fallback: redirect to username path + if (username) window.location.href = `/${username}/canvas`; + } } #showRequestAccessModal(slug: string, spaceName: string) { @@ -688,6 +765,21 @@ const STYLES = ` } .item--create:hover { background: rgba(6,182,212,0.08) !important; } +/* (you)rSpace CTA */ +.item--yourspace { + border-left-color: #f87171; padding: 12px 14px; +} +.item--yourspace .item-name { font-weight: 700; font-size: 0.9rem; } +:host-context([data-theme="light"]) .item--yourspace { background: #fff5f5; } +:host-context([data-theme="dark"]) .item--yourspace { background: rgba(248,113,113,0.06); } +.yourspace-btn { + margin-left: auto; padding: 5px 12px; border-radius: 6px; border: none; + font-size: 0.75rem; font-weight: 600; cursor: pointer; white-space: nowrap; + background: linear-gradient(135deg, #06b6d4, #7c3aed); color: white; + transition: opacity 0.15s, transform 0.15s; +} +.yourspace-btn:hover { opacity: 0.85; transform: translateY(-1px); } + .divider { height: 1px; margin: 4px 0; } :host-context([data-theme="light"]) .divider { background: rgba(0,0,0,0.08); } :host-context([data-theme="dark"]) .divider { background: rgba(255,255,255,0.08); } diff --git a/website/index.html b/website/index.html index dc523b9..c7be1b9 100644 --- a/website/index.html +++ b/website/index.html @@ -14,218 +14,125 @@ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); - color: white; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - padding-top: 56px; - } - - .hero { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - min-height: calc(100vh - 56px); - width: 100%; - } - - .container { - text-align: center; - max-width: 600px; - padding: 40px 20px; - } - - h1 { - font-size: 3rem; - margin-bottom: 1rem; - background: linear-gradient(135deg, #14b8a6, #22d3ee); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - } - - .tagline { - font-size: 1.25rem; - color: #94a3b8; - margin-bottom: 3rem; - } - - .cta-buttons { - display: flex; - gap: 1rem; - justify-content: center; - flex-wrap: wrap; - } - - .cta-primary { - display: inline-block; - padding: 14px 32px; - border-radius: 8px; - background: linear-gradient(135deg, #14b8a6, #0d9488); - color: white; - font-size: 1rem; - font-weight: 600; - text-decoration: none; - transition: transform 0.2s, box-shadow 0.2s; - } - - .cta-primary:hover { - transform: translateY(-2px); - box-shadow: 0 8px 20px rgba(20, 184, 166, 0.3); - } - - .cta-secondary { - display: inline-block; - padding: 14px 32px; - border-radius: 8px; - background: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 255, 255, 0.2); - color: #94a3b8; - font-size: 1rem; - font-weight: 600; - text-decoration: none; - transition: transform 0.2s, border-color 0.2s, color 0.2s; - } - - .cta-secondary:hover { - transform: translateY(-2px); - border-color: rgba(255, 255, 255, 0.4); - color: white; - } - - .features { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 1.5rem; - margin-top: 4rem; - } - - .feature { - text-align: center; - } - - .feature-icon { - font-size: 2rem; - margin-bottom: 0.5rem; - } - - .feature-title { - font-weight: 600; - margin-bottom: 0.25rem; - } - - .feature-desc { - font-size: 0.875rem; - color: #64748b; - } - - /* EncryptID & Ecosystem Sections */ - - .section { - width: 100%; - max-width: 760px; - padding: 5rem 20px; - text-align: center; - } - - .section + .section { - padding-top: 0; - } - - .section-divider { - width: 60px; - height: 2px; - background: linear-gradient(90deg, #14b8a6, #7c3aed); - margin: 0 auto 3rem; - border-radius: 2px; - } - - .section h2 { - font-size: 2rem; - margin-bottom: 0.75rem; - background: linear-gradient(90deg, #00d4ff, #7c3aed); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - } - - .section-subtitle { - font-size: 1.05rem; - color: #94a3b8; - margin-bottom: 2.5rem; - line-height: 1.7; - max-width: 560px; - margin-left: auto; - margin-right: auto; - } - - .identity-card { - background: rgba(255, 255, 255, 0.04); - border: 1px solid rgba(124, 58, 237, 0.2); - border-radius: 16px; - padding: 2rem; - text-align: left; - margin-bottom: 1.5rem; - } - - .identity-card h3 { - font-size: 1.15rem; - margin-bottom: 0.75rem; + background: #0f172a; color: #e2e8f0; + min-height: 100vh; + padding-top: 56px; + line-height: 1.6; } - .identity-card p { - color: #94a3b8; - line-height: 1.7; - font-size: 0.95rem; + a { color: inherit; } + + /* ── Rich Landing Page Utilities (from rApp theme) ── */ + .rl-section { + border-top: 1px solid rgba(255,255,255,0.06); + padding: 4rem 1.5rem; + } + .rl-section--alt { background: rgba(255,255,255,0.015); } + .rl-container { max-width: 1100px; margin: 0 auto; } + .rl-hero { + text-align: center; padding: 5rem 1.5rem 3rem; + max-width: 820px; margin: 0 auto; + } + .rl-tagline { + display: inline-block; font-size: 0.7rem; font-weight: 700; + letter-spacing: 0.12em; text-transform: uppercase; + color: #14b8a6; background: rgba(20,184,166,0.1); + border: 1px solid rgba(20,184,166,0.2); + padding: 0.35rem 1rem; border-radius: 9999px; margin-bottom: 1.5rem; + } + .rl-heading { + font-size: 2rem; font-weight: 700; line-height: 1.15; + margin-bottom: 0.75rem; letter-spacing: -0.01em; + background: linear-gradient(135deg, #14b8a6, #22d3ee); + -webkit-background-clip: text; -webkit-text-fill-color: transparent; + background-clip: text; + } + .rl-hero .rl-heading { font-size: 2.5rem; } + @media (min-width: 640px) { .rl-hero .rl-heading { font-size: 3rem; } } + .rl-subtitle { + font-size: 1.25rem; font-weight: 500; color: #cbd5e1; + margin-bottom: 1rem; letter-spacing: -0.005em; + } + .rl-hero .rl-subtitle { font-size: 1.35rem; } + @media (min-width: 640px) { .rl-hero .rl-subtitle { font-size: 1.5rem; } } + .rl-subtext { + font-size: 1.05rem; color: #94a3b8; line-height: 1.65; + max-width: 640px; margin: 0 auto 2rem; + } + .rl-hero .rl-subtext { font-size: 1.15rem; } + + /* Grids */ + .rl-grid-2 { display: grid; grid-template-columns: 1fr; gap: 1.25rem; } + .rl-grid-3 { display: grid; grid-template-columns: 1fr; gap: 1.25rem; } + .rl-grid-4 { display: grid; grid-template-columns: 1fr; gap: 1.25rem; } + @media (min-width: 640px) { + .rl-grid-2 { grid-template-columns: repeat(2, 1fr); } + .rl-grid-3 { grid-template-columns: repeat(3, 1fr); } + .rl-grid-4 { grid-template-columns: repeat(2, 1fr); } + } + @media (min-width: 1024px) { + .rl-grid-4 { grid-template-columns: repeat(4, 1fr); } } - .identity-card .hl { - color: #00d4ff; - font-weight: 600; + /* Card */ + .rl-card { + background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.06); + border-radius: 1rem; padding: 1.75rem; + transition: border-color 0.2s; } + .rl-card:hover { border-color: rgba(20,184,166,0.3); } + .rl-card h3 { font-size: 0.95rem; font-weight: 600; color: #e2e8f0; margin-bottom: 0.5rem; } + .rl-card p { font-size: 0.875rem; color: #94a3b8; line-height: 1.6; } + .rl-card--center { text-align: center; } - .pillars { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 1rem; - margin-bottom: 2rem; + /* CTA row */ + .rl-cta-row { + display: flex; gap: 0.75rem; justify-content: center; flex-wrap: wrap; + margin-top: 2rem; } - - .pillar { - background: rgba(255, 255, 255, 0.04); - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 12px; - padding: 1.25rem 1rem; - text-align: center; + .rl-cta-primary { + display: inline-block; padding: 0.8rem 2rem; border-radius: 0.5rem; + background: linear-gradient(135deg, #14b8a6, #0d9488); + color: white; font-size: 0.95rem; font-weight: 600; + text-decoration: none; transition: transform 0.2s, box-shadow 0.2s; } - - .pillar-icon { - font-size: 1.75rem; - margin-bottom: 0.5rem; + .rl-cta-primary:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(20,184,166,0.3); } + .rl-cta-secondary { + display: inline-block; padding: 0.8rem 2rem; border-radius: 0.5rem; + background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.15); + color: #94a3b8; font-size: 0.95rem; font-weight: 600; + text-decoration: none; transition: transform 0.2s, border-color 0.2s, color 0.2s; } + .rl-cta-secondary:hover { transform: translateY(-2px); border-color: rgba(255,255,255,0.35); color: white; } - .pillar-title { - font-weight: 600; - font-size: 0.9rem; - margin-bottom: 0.25rem; + /* Icon box */ + .rl-icon-box { + width: 3rem; height: 3rem; border-radius: 0.75rem; + background: rgba(20,184,166,0.12); color: #14b8a6; + display: flex; align-items: center; justify-content: center; + font-size: 1.5rem; margin-bottom: 1rem; } + .rl-card--center .rl-icon-box { margin: 0 auto 1rem; } - .pillar-desc { - font-size: 0.8rem; - color: #64748b; - line-height: 1.4; - } + /* Highlight text */ + .hl { color: #14b8a6; font-weight: 600; } + /* ── Section-specific accents ── */ + + /* EncryptID / identity accent */ + .rl-card--accent-purple { border-color: rgba(124, 58, 237, 0.2); } + .rl-card--accent-purple:hover { border-color: rgba(124, 58, 237, 0.4); } + .rl-icon-box--purple { background: rgba(124, 58, 237, 0.12); color: #a78bfa; } + + /* ── Flow diagram ── */ .flow-diagram { - background: rgba(255, 255, 255, 0.03); - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 16px; + background: rgba(255,255,255,0.02); + border: 1px solid rgba(255,255,255,0.06); + border-radius: 1rem; padding: 1.75rem; - margin-bottom: 2rem; + margin-top: 1.5rem; } - .flow-diagram pre { font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; font-size: 0.78rem; @@ -236,21 +143,21 @@ white-space: pre; } + /* ── Ecosystem app pills ── */ .ecosystem-apps { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.75rem; - margin-top: 1.5rem; + margin-top: 2rem; } - .ecosystem-app { display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.5rem 1rem; - background: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 255, 255, 0.1); + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 8px; color: #94a3b8; text-decoration: none; @@ -258,12 +165,12 @@ font-weight: 500; transition: border-color 0.2s, color 0.2s; } - .ecosystem-app:hover { - border-color: rgba(0, 212, 255, 0.4); - color: #00d4ff; + border-color: rgba(20, 184, 166, 0.4); + color: #14b8a6; } + /* ── EncryptID link ── */ .encryptid-link { display: inline-flex; align-items: center; @@ -279,23 +186,20 @@ margin-top: 2rem; transition: transform 0.2s, border-color 0.2s; } - .encryptid-link:hover { transform: translateY(-2px); border-color: rgba(124, 58, 237, 0.6); } - /* Newsletter */ + /* ── Newsletter ── */ .newsletter-form { max-width: 440px; margin: 0 auto; } - .newsletter-row { display: flex; gap: 0.75rem; } - .newsletter-input { flex: 1; padding: 12px 16px; @@ -307,53 +211,48 @@ outline: none; transition: border-color 0.2s; } - - .newsletter-input:focus { - border-color: #14b8a6; - } - - .newsletter-input::placeholder { - color: #64748b; - } - + .newsletter-input:focus { border-color: #14b8a6; } + .newsletter-input::placeholder { color: #64748b; } .newsletter-btn { padding: 12px 24px; white-space: nowrap; + border-radius: 8px; + border: none; + background: linear-gradient(135deg, #14b8a6, #0d9488); + color: white; + font-size: 0.95rem; + font-weight: 600; + cursor: pointer; + transition: transform 0.2s, box-shadow 0.2s; + } + .newsletter-btn:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(20, 184, 166, 0.3); + } + .newsletter-btn:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; } - .newsletter-status { font-size: 0.875rem; margin-top: 0.75rem; min-height: 1.25em; } - - .newsletter-status.success { - color: #22c55e; - } - - .newsletter-status.error { - color: #ef4444; - } - + .newsletter-status.success { color: #22c55e; } + .newsletter-status.error { color: #ef4444; } .newsletter-privacy { font-size: 0.8rem; color: #64748b; margin-top: 0.5rem; } + /* ── Responsive ── */ @media (max-width: 600px) { - .newsletter-row { - flex-direction: column; - } - .pillars { - grid-template-columns: 1fr; - } - .features { - grid-template-columns: repeat(2, 1fr); - } - .section h2 { - font-size: 1.5rem; - } + .rl-hero { padding: 3rem 1rem 2rem; } + .rl-hero .rl-heading { font-size: 2rem; } + .rl-section { padding: 2.5rem 1rem; } + .newsletter-row { flex-direction: column; } } @@ -374,149 +273,156 @@ -
-
-

rSpace

-

Collaborative community spaces powered by FolkJS

-
- Create a Space - Try the Demo + +
+ Local-First · Zero-Knowledge · Community-Owned +

(you)rSpace

+

Collaborative community spaces powered by FolkJS

+

+ Build digital spaces for your community with an infinite collaborative canvas, + real-time CRDT sync, and zero-knowledge identity. All open source. +

+ + + +
+
+
🎨
+

Spatial Canvas

+

Infinite collaborative workspace

- -
-
-
🎨
-
Spatial Canvas
-
Infinite collaborative workspace
-
-
-
🔄
-
Real-time Sync
-
Powered by Automerge CRDT
-
-
-
📡
-
Offline-First
-
Works without internet, merges on reconnect
-
-
-
🌐
-
Your Subdomain
-
community.rspace.online
-
+
+
🔄
+

Real-time Sync

+

Powered by Automerge CRDT

+
+
+
📡
+

Offline-First

+

Works without internet, merges on reconnect

+
+
+
🌐
+

Your Subdomain

+

community.rspace.online

- -
-
-

EncryptID

-

- One secure, local-first identity across every tool in your community's rSpace. - No passwords. No cloud accounts. Your keys never leave your device. -

- -
-
-
🔑
-
Passkey Login
-
Hardware-backed biometric auth. Phishing-resistant by design.
-
-
-
🏠
-
Local-First
-
Cryptographic keys are derived and stored on your device, never uploaded.
-
-
-
🔗
-
One Login, All Apps
-
Authenticate once and access every r-Ecosystem tool seamlessly.
-
-
- -
-

Secure by default, not by opt-in

-

- EncryptID uses WebAuthn passkeys as the root of trust — - the same standard behind Face ID and fingerprint unlock. Your identity is bound to - your device's secure hardware, so there are no passwords to leak, phish, or forget. - End-to-end encryption keys are derived locally via HKDF, - meaning the server never sees your private keys. If you lose your device, - social recovery lets trusted guardians help you regain access - without seed phrases or centralized reset flows. + +

+
+

EncryptID

+

+ One secure, local-first identity across every tool in your community's rSpace. + No passwords. No cloud accounts. Your keys never leave your device.

-
-
-

A common login for your community's toolkit

-

- Every community rSpace comes with a full suite of interoperable tools — - voting, budgets, maps, files, notes, and more — all sharing the same - EncryptID session. Sign in once on rSpace and you're - already authenticated on rVote, rFunds, rFiles, and every other tool your - community uses. No separate accounts, no OAuth redirects, no third-party identity - providers. Your community, your identity, your data. -

+
+
+
🔑
+

Passkey Login

+

Hardware-backed biometric auth. Phishing-resistant by design.

+
+
+
🏠
+

Local-First

+

Cryptographic keys are derived and stored on your device, never uploaded.

+
+
+
🔗
+

One Login, All Apps

+

Authenticate once and access every r-Ecosystem tool seamlessly.

+
+
+ +
+

Secure by default, not by opt-in

+

+ EncryptID uses WebAuthn passkeys as the root of trust — + the same standard behind Face ID and fingerprint unlock. Your identity is bound to + your device's secure hardware, so there are no passwords to leak, phish, or forget. + End-to-end encryption keys are derived locally via HKDF, + meaning the server never sees your private keys. If you lose your device, + social recovery lets trusted guardians help you regain access + without seed phrases or centralized reset flows. +

+
+ +
+

A common login for your community's toolkit

+

+ Every community rSpace comes with a full suite of interoperable tools — + voting, budgets, maps, files, notes, and more — all sharing the same + EncryptID session. Sign in once on rSpace and you're + already authenticated on rVote, rFunds, rFiles, and every other tool your + community uses. No separate accounts, no OAuth redirects, no third-party identity + providers. Your community, your identity, your data. +

+
- -
-
-

Offline-First, Always Available

-

- rSpace works without an internet connection. Edit your canvas on a plane, - in a field, or underground — your changes merge automatically when - you're back online. No sync buttons, no conflict dialogs. -

- -
-
-
💾
-
Local Persistence
-
Your canvas is cached in the browser. Refresh the page — it loads instantly, even offline.
-
-
-
🔀
-
Auto-Merge
-
Automerge CRDTs resolve conflicts automatically. Multiple people can edit the same canvas offline and merge without data loss.
-
-
-
-
Incremental Sync
-
Only new changes are transferred on reconnect — not the whole document. Fast even on slow connections.
-
-
- -
-

How it works

-

- Every rSpace canvas is an Automerge CRDT document stored - locally in your browser's IndexedDB. When you open a canvas, it renders from the - local cache first — no waiting for the server. Edits you make are saved locally - and synced to the server via WebSocket when a connection is available. If you go offline, - the app keeps working: a Service Worker serves the app shell - from cache, and your changes accumulate in the local CRDT document. When connectivity - returns, Automerge's incremental sync protocol reconciles - your changes with everyone else's — conflict-free, automatically. No manual - merge, no "which version do you want to keep?" dialogs. + +

+
+

Offline-First, Always Available

+

+ rSpace works without an internet connection. Edit your canvas on a plane, + in a field, or underground — your changes merge automatically when + you're back online. No sync buttons, no conflict dialogs.

+ +
+
+
💾
+

Local Persistence

+

Your canvas is cached in the browser. Refresh the page — it loads instantly, even offline.

+
+
+
🔀
+

Auto-Merge

+

Automerge CRDTs resolve conflicts automatically. Multiple people can edit the same canvas offline and merge without data loss.

+
+
+
+

Incremental Sync

+

Only new changes are transferred on reconnect — not the whole document. Fast even on slow connections.

+
+
+ +
+

How it works

+

+ Every rSpace canvas is an Automerge CRDT document stored + locally in your browser's IndexedDB. When you open a canvas, it renders from the + local cache first — no waiting for the server. Edits you make are saved locally + and synced to the server via WebSocket when a connection is available. If you go offline, + the app keeps working: a Service Worker serves the app shell + from cache, and your changes accumulate in the local CRDT document. When connectivity + returns, Automerge's incremental sync protocol reconciles + your changes with everyone else's — conflict-free, automatically. No manual + merge, no "which version do you want to keep?" dialogs. +

+
- -
-
-

Interoperable by Design

-

- Data flows between your community's tools because they share a common - foundation: the same identity, the same real-time sync layer, and the same - local-first architecture. -

+ +
+
+

Interoperable by Design

+

+ Data flows between your community's tools because they share a common + foundation: the same identity, the same real-time sync layer, and the same + local-first architecture. +

-
+
   EncryptID (identity)
        |
@@ -532,76 +438,80 @@
        v
   Your device (keys & data stay here)
 
-
+
-
-

Your data, connected across tools

-

- A budget created in rFunds can reference a vote - from rVote. A map pin in rMaps - can link to files in rFiles and notes in - rNotes. Because all r-Ecosystem tools share the same - Automerge CRDT sync layer, data is interoperable - without import/export steps or API integrations. Changes propagate in real-time - across every tool and every collaborator — conflict-free. -

-
+
+
+

Your data, connected across tools

+

+ A budget created in rFunds can reference a vote + from rVote. A map pin in rMaps + can link to files in rFiles and notes in + rNotes. Because all r-Ecosystem tools share the same + Automerge CRDT sync layer, data is interoperable + without import/export steps or API integrations. Changes propagate in real-time + across every tool and every collaborator — conflict-free. +

+
+
+

No vendor lock-in, no data silos

+

+ Every piece of community data is stored as a local-first CRDT document + that your community owns. There's no central server gating access and no + proprietary format trapping your data. Export everything. Fork your community. + Move between hosts. The r-Ecosystem is designed + so that the community — not the platform — controls the data. +

+
+
-
-

No vendor lock-in, no data silos

-

- Every piece of community data is stored as a local-first CRDT document - that your community owns. There's no central server gating access and no - proprietary format trapping your data. Export everything. Fork your community. - Move between hosts. The r-Ecosystem is designed - so that the community — not the platform — controls the data. -

-
+ - - - - 🔐 Learn more about EncryptID -
-
-
-

Stay Connected

-

- Get updates on rSpace development, new ecosystem modules, and community features. -

+ +
+
+

Stay Connected

+

+ Get updates on rSpace development, new ecosystem modules, and community features. +

- + +