Merge branch 'dev'

This commit is contained in:
Jeff Emmett 2026-02-27 17:09:39 -08:00
commit 6f80f7abeb
1 changed files with 165 additions and 0 deletions

View File

@ -5,6 +5,14 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/png" href="/favicon.png" />
<title>rSpace Canvas</title>
<style>
/* When loaded inside an iframe, hide shell chrome */
html.rspace-embedded .rstack-header { display: none !important; }
html.rspace-embedded .rstack-tab-row { display: none !important; }
html.rspace-embedded #toolbar { top: 16px !important; }
html.rspace-embedded #community-info { display: none !important; }
</style>
<script>if (window.self !== window.parent) document.documentElement.classList.add('rspace-embedded');</script>
<style>
* {
margin: 0;
@ -672,6 +680,76 @@
}
</style>
<link rel="stylesheet" href="/shell.css">
<style>
.rspace-welcome {
position: fixed; bottom: 20px; right: 20px; z-index: 10000;
display: none; align-items: flex-end; justify-content: flex-end;
}
.rspace-welcome__popup {
position: relative;
width: min(380px, 44vw); max-height: 50vh;
background: #1e293b; border: 1px solid rgba(255,255,255,0.12);
border-radius: 16px; padding: 24px 24px 18px;
box-shadow: 0 20px 60px rgba(0,0,0,0.5); color: #e2e8f0;
overflow-y: auto; animation: rspace-welcome-in 0.3s ease-out;
}
@keyframes rspace-welcome-in {
from { opacity: 0; transform: translateY(20px) scale(0.95); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
.rspace-welcome__close {
position: absolute; top: 10px; right: 12px;
background: none; border: none; color: #64748b;
font-size: 1.4rem; cursor: pointer; line-height: 1;
padding: 4px; border-radius: 4px;
}
.rspace-welcome__close:hover { color: #e2e8f0; background: rgba(255,255,255,0.08); }
.rspace-welcome__title {
font-size: 1.35rem; margin: 0 0 8px;
background: linear-gradient(135deg, #14b8a6, #22d3ee);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text;
}
.rspace-welcome__text {
font-size: 0.85rem; color: #94a3b8; margin: 0 0 14px; line-height: 1.55;
}
.rspace-welcome__text strong { color: #e2e8f0; }
.rspace-welcome__grid {
display: grid; grid-template-columns: 1fr 1fr;
gap: 5px; margin-bottom: 14px; font-size: 0.8rem; color: #cbd5e1;
}
.rspace-welcome__grid span { padding: 3px 0; }
.rspace-welcome__actions {
display: flex; gap: 8px; margin-bottom: 12px;
}
.rspace-welcome__btn {
padding: 8px 16px; border-radius: 8px; font-size: 0.82rem;
font-weight: 600; text-decoration: none; cursor: pointer; border: none;
transition: transform 0.15s, box-shadow 0.15s;
}
.rspace-welcome__btn:hover { transform: translateY(-1px); }
.rspace-welcome__btn--primary {
background: linear-gradient(135deg, #14b8a6, #0d9488); color: white;
box-shadow: 0 2px 8px rgba(20,184,166,0.3);
}
.rspace-welcome__btn--secondary {
background: rgba(255,255,255,0.08); color: #94a3b8;
}
.rspace-welcome__btn--secondary:hover { color: #e2e8f0; }
.rspace-welcome__footer {
display: flex; align-items: center; gap: 6px;
}
.rspace-welcome__link {
font-size: 0.72rem; color: #64748b; text-decoration: none;
transition: color 0.15s;
}
.rspace-welcome__link:hover { color: #c4b5fd; }
.rspace-welcome__dot { color: #475569; font-size: 0.6rem; }
@media (max-width: 600px) {
.rspace-welcome { bottom: 12px; right: 12px; left: 12px; }
.rspace-welcome__popup { width: 100%; max-width: none; }
}
</style>
</head>
<body data-theme="light">
<header class="rstack-header" data-theme="light">
@ -683,6 +761,7 @@
<rstack-mi></rstack-mi>
</div>
<div class="rstack-header__right">
<a class="rstack-header__demo-btn" href="https://demo.rspace.online/rspace">Try Demo</a>
<rstack-identity></rstack-identity>
</div>
</header>
@ -694,6 +773,33 @@
<p id="community-slug"></p>
</div>
<!-- Welcome overlay (first-time demo visitors) -->
<div id="rspace-welcome" class="rspace-welcome" style="display:none">
<div class="rspace-welcome__popup">
<button class="rspace-welcome__close" onclick="window.__rspaceDismissWelcome()">&times;</button>
<h2 class="rspace-welcome__title">Welcome to rSpace</h2>
<p class="rspace-welcome__text">
A collaborative, local-first community platform with 22+ interoperable tools.
You're viewing the <strong>demo space</strong> &mdash; sign in to access your own.
</p>
<div class="rspace-welcome__grid">
<span>🎨 Canvas</span><span>📝 Notes</span>
<span>🗳 Voting</span><span>💸 Funds</span>
<span>🗺 Maps</span><span>📁 Files</span>
<span>🔐 Passkeys</span><span>📡 Offline-First</span>
</div>
<div class="rspace-welcome__actions">
<a href="/create-space" class="rspace-welcome__btn rspace-welcome__btn--primary">Create a Space</a>
<button onclick="window.__rspaceDismissWelcome()" class="rspace-welcome__btn rspace-welcome__btn--secondary">Explore Demo</button>
</div>
<div class="rspace-welcome__footer">
<a href="/about" class="rspace-welcome__link">Learn more about rSpace</a>
<span class="rspace-welcome__dot">&middot;</span>
<a href="https://ridentity.online" class="rspace-welcome__link">EncryptID</a>
</div>
</div>
</div>
<button id="mobile-menu" title="Tools"></button>
<div id="mobile-zoom">
<button id="mz-out" title="Zoom Out"></button>
@ -894,13 +1000,24 @@
import { RStackAppSwitcher } from "@shared/components/rstack-app-switcher";
import { RStackSpaceSwitcher } from "@shared/components/rstack-space-switcher";
import { RStackTabBar } from "@shared/components/rstack-tab-bar";
import { RStackMi } from "@shared/components/rstack-mi";
import { rspaceNavUrl } from "@shared/url-helpers";
// Expose URL helper globally (matches shell.js)
window.__rspaceNavUrl = rspaceNavUrl;
// Register shell header components
RStackIdentity.define();
RStackAppSwitcher.define();
RStackSpaceSwitcher.define();
RStackTabBar.define();
RStackMi.define();
// Reload space list when user signs in/out
document.addEventListener("auth-change", () => {
const sw = document.querySelector("rstack-space-switcher");
sw?.reload?.();
});
// Load module list for app switcher
fetch("/api/modules").then(r => r.json()).then(data => {
@ -1038,6 +1155,54 @@
tabBar.setAttribute("space", communitySlug);
}
// ── "Try Demo" button visibility ──
// Hide on demo space, show on bare domain
(function() {
var btn = document.querySelector('.rstack-header__demo-btn');
if (!btn) return;
if (communitySlug === 'demo') btn.setAttribute('data-hide', '');
var host = window.location.host.split(':')[0];
if (host === 'rspace.online' || host === 'www.rspace.online') {
btn.removeAttribute('data-hide');
}
})();
// ── Auto-space resolution (logged-in users on demo → personal space) ──
(function() {
try {
var raw = localStorage.getItem('encryptid_session');
if (!raw) return;
var session = JSON.parse(raw);
if (!session || !session.claims || !session.claims.username) return;
if (communitySlug !== 'demo') return;
fetch('/api/spaces/auto-provision', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + session.accessToken,
'Content-Type': 'application/json'
}
}).then(function(r) { return r.json(); })
.then(function(data) {
if (data.slug) {
window.location.replace(rspaceNavUrl(data.slug, 'rspace'));
}
}).catch(function() {});
} catch(e) {}
})();
// ── Welcome overlay (first visit to demo) ──
(function() {
if (communitySlug !== 'demo') return;
if (localStorage.getItem('rspace_welcomed')) return;
var el = document.getElementById('rspace-welcome');
if (el) el.style.display = 'flex';
})();
window.__rspaceDismissWelcome = function() {
localStorage.setItem('rspace_welcomed', '1');
var el = document.getElementById('rspace-welcome');
if (el) el.style.display = 'none';
};
const canvas = document.getElementById("canvas");
const canvasContent = document.getElementById("canvas-content");
const status = document.getElementById("status");