Merge branch 'dev'
This commit is contained in:
commit
6d616e3710
286
server/shell.ts
286
server/shell.ts
|
|
@ -450,17 +450,138 @@ export function renderShell(opts: ShellOptions): string {
|
|||
document.addEventListener('auth-change', update);
|
||||
})();
|
||||
|
||||
// ── Welcome overlay (first visit to demo) ──
|
||||
// ── Welcome tour (guided feature walkthrough for first-time visitors) ──
|
||||
(function() {
|
||||
var currentSpace = '${escapeAttr(spaceSlug)}';
|
||||
if (currentSpace !== 'demo') return;
|
||||
if (localStorage.getItem('rspace_welcomed')) return;
|
||||
var el = document.getElementById('rspace-welcome');
|
||||
if (el) el.style.display = 'flex';
|
||||
if (localStorage.getItem('rspace_tour_done')) return;
|
||||
|
||||
var TOUR_STEPS = [
|
||||
{
|
||||
target: 'rstack-app-switcher',
|
||||
title: 'Welcome to rSpace',
|
||||
msg: 'This is your collaborative workspace with 25+ interoperable tools called rApps. Use this switcher to jump between modules like Notes, Maps, Voting, Wallet, and more.'
|
||||
},
|
||||
{
|
||||
target: 'rstack-mi',
|
||||
title: 'Meet mi \u2014 Your AI Guide',
|
||||
msg: 'Press <strong>Cmd+K</strong> (or Ctrl+K) to open mi, your mycelial intelligence assistant. mi can create content, set up spaces, and help you build across all rApps.'
|
||||
},
|
||||
{
|
||||
target: 'rstack-space-switcher',
|
||||
title: 'Spaces',
|
||||
msg: 'Each space is a self-contained community with its own data, members, and encryption. Switch between spaces or create a new one here.'
|
||||
},
|
||||
{
|
||||
target: 'rstack-identity',
|
||||
title: 'Passwordless Identity',
|
||||
msg: 'Sign in with <strong>passkeys</strong> \u2014 no passwords or seed phrases. Your identity is cryptographic and portable across all spaces.'
|
||||
},
|
||||
{
|
||||
target: '#app',
|
||||
title: 'The Canvas',
|
||||
msg: 'This is your infinite canvas. Drag to pan, scroll to zoom, and use the toolbar to add shapes. Everything syncs in real-time using local-first CRDTs.'
|
||||
},
|
||||
{
|
||||
target: '.rstack-tab-row',
|
||||
title: 'rApp Tabs',
|
||||
msg: 'Each module has its own views accessible via tabs. Switch between canvas, list, and detail views depending on the active rApp.'
|
||||
},
|
||||
];
|
||||
|
||||
var step = 0;
|
||||
var tourEl = document.getElementById('rspace-tour');
|
||||
var backdrop = document.getElementById('rspace-tour-backdrop');
|
||||
var spotlight = document.getElementById('rspace-tour-spotlight');
|
||||
var tooltip = document.getElementById('rspace-tour-tooltip');
|
||||
var titleEl = document.getElementById('rspace-tour-title');
|
||||
var msgEl = document.getElementById('rspace-tour-msg');
|
||||
var stepEl = document.getElementById('rspace-tour-step');
|
||||
var prevBtn = document.getElementById('rspace-tour-prev');
|
||||
var nextBtn = document.getElementById('rspace-tour-next');
|
||||
var skipBtn = document.getElementById('rspace-tour-skip');
|
||||
|
||||
function endTour() {
|
||||
localStorage.setItem('rspace_tour_done', '1');
|
||||
if (tourEl) tourEl.style.display = 'none';
|
||||
}
|
||||
|
||||
function showStep() {
|
||||
if (!tourEl || !backdrop || !spotlight || !tooltip) return;
|
||||
var s = TOUR_STEPS[step];
|
||||
var target = document.querySelector(s.target);
|
||||
var rect = target ? target.getBoundingClientRect() : { left: window.innerWidth / 2 - 60, top: 8, width: 120, height: 40 };
|
||||
|
||||
// Spotlight
|
||||
var pad = 6;
|
||||
var sx = rect.left - pad, sy = rect.top - pad;
|
||||
var sw = rect.width + pad * 2, sh = rect.height + pad * 2;
|
||||
spotlight.style.left = sx + 'px';
|
||||
spotlight.style.top = sy + 'px';
|
||||
spotlight.style.width = sw + 'px';
|
||||
spotlight.style.height = sh + 'px';
|
||||
|
||||
// Backdrop clip-path (cut hole for spotlight)
|
||||
backdrop.style.clipPath = 'polygon(0% 0%, 0% 100%, ' +
|
||||
sx + 'px 100%, ' + sx + 'px ' + sy + 'px, ' +
|
||||
(sx + sw) + 'px ' + sy + 'px, ' +
|
||||
(sx + sw) + 'px ' + (sy + sh) + 'px, ' +
|
||||
sx + 'px ' + (sy + sh) + 'px, ' +
|
||||
sx + 'px 100%, 100% 100%, 100% 0%)';
|
||||
|
||||
// Tooltip position: below target, clamped to viewport
|
||||
var ttTop = rect.top + rect.height + 16;
|
||||
var ttLeft = Math.max(16, Math.min(rect.left, window.innerWidth - 380));
|
||||
// If tooltip would go off bottom, put it above
|
||||
if (ttTop + 200 > window.innerHeight) {
|
||||
ttTop = Math.max(16, rect.top - 220);
|
||||
}
|
||||
tooltip.style.top = ttTop + 'px';
|
||||
tooltip.style.left = ttLeft + 'px';
|
||||
|
||||
// Content
|
||||
stepEl.textContent = (step + 1) + ' / ' + TOUR_STEPS.length;
|
||||
titleEl.textContent = s.title;
|
||||
msgEl.innerHTML = s.msg;
|
||||
|
||||
// Buttons
|
||||
prevBtn.style.display = step > 0 ? '' : 'none';
|
||||
nextBtn.textContent = step === TOUR_STEPS.length - 1 ? 'Get Started' : 'Next';
|
||||
}
|
||||
|
||||
// Wire buttons
|
||||
if (nextBtn) nextBtn.addEventListener('click', function() {
|
||||
step++;
|
||||
if (step >= TOUR_STEPS.length) { endTour(); return; }
|
||||
showStep();
|
||||
});
|
||||
if (prevBtn) prevBtn.addEventListener('click', function() {
|
||||
if (step > 0) { step--; showStep(); }
|
||||
});
|
||||
if (skipBtn) skipBtn.addEventListener('click', endTour);
|
||||
if (backdrop) backdrop.addEventListener('click', endTour);
|
||||
|
||||
// Keyboard: Escape to skip, arrows to navigate
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (!tourEl || tourEl.style.display === 'none') return;
|
||||
if (e.key === 'Escape') { endTour(); e.preventDefault(); }
|
||||
if (e.key === 'ArrowRight' || e.key === 'Enter') {
|
||||
step++;
|
||||
if (step >= TOUR_STEPS.length) { endTour(); return; }
|
||||
showStep(); e.preventDefault();
|
||||
}
|
||||
if (e.key === 'ArrowLeft' && step > 0) { step--; showStep(); e.preventDefault(); }
|
||||
});
|
||||
|
||||
// Start tour after a brief delay
|
||||
setTimeout(function() {
|
||||
if (tourEl) { tourEl.style.display = ''; showStep(); }
|
||||
}, 800);
|
||||
})();
|
||||
// Legacy compat
|
||||
window.__rspaceDismissWelcome = function() {
|
||||
localStorage.setItem('rspace_welcomed', '1');
|
||||
var el = document.getElementById('rspace-welcome');
|
||||
localStorage.setItem('rspace_tour_done', '1');
|
||||
var el = document.getElementById('rspace-tour');
|
||||
if (el) el.style.display = 'none';
|
||||
};
|
||||
|
||||
|
|
@ -1270,32 +1391,21 @@ export function renderExternalAppShell(opts: ExternalAppShellOptions): string {
|
|||
</html>`);
|
||||
}
|
||||
|
||||
// ── Welcome overlay (quarter-screen popup for first-time visitors on demo) ──
|
||||
// ── Welcome tour (guided feature walkthrough for first-time visitors) ──
|
||||
|
||||
function renderWelcomeOverlay(): string {
|
||||
return `
|
||||
<div id="rspace-welcome" class="rspace-welcome" style="display:none">
|
||||
<div class="rspace-welcome__popup">
|
||||
<button class="rspace-welcome__close" onclick="window.__rspaceDismissWelcome()">×</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> — 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">·</span>
|
||||
<a href="/rids" class="rspace-welcome__link">EncryptID</a>
|
||||
<div id="rspace-tour" style="display:none">
|
||||
<div class="rspace-tour__backdrop" id="rspace-tour-backdrop"></div>
|
||||
<div class="rspace-tour__spotlight" id="rspace-tour-spotlight"></div>
|
||||
<div class="rspace-tour__tooltip" id="rspace-tour-tooltip">
|
||||
<div class="rspace-tour__step" id="rspace-tour-step"></div>
|
||||
<div class="rspace-tour__title" id="rspace-tour-title"></div>
|
||||
<div class="rspace-tour__msg" id="rspace-tour-msg"></div>
|
||||
<div class="rspace-tour__nav">
|
||||
<button class="rspace-tour__btn rspace-tour__btn--prev" id="rspace-tour-prev" style="display:none">Back</button>
|
||||
<button class="rspace-tour__btn rspace-tour__btn--next" id="rspace-tour-next">Next</button>
|
||||
<button class="rspace-tour__btn rspace-tour__btn--skip" id="rspace-tour-skip">Skip tour</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
|
@ -1327,73 +1437,85 @@ const ACCESS_GATE_CSS = `
|
|||
`;
|
||||
|
||||
const WELCOME_CSS = `
|
||||
.rspace-welcome {
|
||||
position: fixed; bottom: 20px; right: 20px; z-index: 10000;
|
||||
display: none; align-items: flex-end; justify-content: flex-end;
|
||||
#rspace-tour { position: fixed; inset: 0; z-index: 10000; pointer-events: none; }
|
||||
.rspace-tour__backdrop {
|
||||
position: fixed; inset: 0;
|
||||
background: rgba(0,0,0,0.55);
|
||||
pointer-events: auto;
|
||||
transition: clip-path 0.3s ease;
|
||||
}
|
||||
.rspace-welcome__popup {
|
||||
position: relative;
|
||||
width: min(380px, 44vw); max-height: 50vh;
|
||||
background: var(--rs-bg-surface); border: 1px solid var(--rs-border);
|
||||
border-radius: 16px; padding: 24px 24px 18px;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.5); color: var(--rs-text-primary);
|
||||
overflow-y: auto; animation: rspace-welcome-in 0.3s ease-out;
|
||||
.rspace-tour__spotlight {
|
||||
position: fixed;
|
||||
border: 2px solid var(--rs-primary, #06b6d4);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 0 4px rgba(6,182,212,0.25), 0 0 20px rgba(6,182,212,0.15);
|
||||
pointer-events: none;
|
||||
transition: all 0.35s ease;
|
||||
}
|
||||
@keyframes rspace-welcome-in {
|
||||
from { opacity: 0; transform: translateY(20px) scale(0.95); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
.rspace-tour__tooltip {
|
||||
position: fixed;
|
||||
width: min(360px, calc(100vw - 32px));
|
||||
background: var(--rs-bg-surface, #1e293b);
|
||||
border: 1px solid var(--rs-border, #334155);
|
||||
border-radius: 14px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 16px 48px rgba(0,0,0,0.5);
|
||||
color: var(--rs-text-primary, #f1f5f9);
|
||||
pointer-events: auto;
|
||||
animation: rspace-tour-pop 0.25s ease-out;
|
||||
transition: top 0.35s ease, left 0.35s ease;
|
||||
}
|
||||
.rspace-welcome__close {
|
||||
position: absolute; top: 10px; right: 12px;
|
||||
background: none; border: none; color: var(--rs-text-muted);
|
||||
font-size: 1.4rem; cursor: pointer; line-height: 1;
|
||||
padding: 4px; border-radius: 4px;
|
||||
@keyframes rspace-tour-pop {
|
||||
from { opacity: 0; transform: translateY(8px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.rspace-welcome__close:hover { color: var(--rs-text-primary); background: var(--rs-bg-hover); }
|
||||
.rspace-welcome__title {
|
||||
font-size: 1.35rem; margin: 0 0 8px;
|
||||
background: var(--rs-gradient-brand);
|
||||
.rspace-tour__step {
|
||||
font-size: 0.7rem;
|
||||
color: var(--rs-text-muted, #64748b);
|
||||
margin-bottom: 6px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
.rspace-tour__title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
background: linear-gradient(135deg, #06b6d4, #7c3aed);
|
||||
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
.rspace-welcome__text {
|
||||
font-size: 0.85rem; color: var(--rs-text-secondary); margin: 0 0 14px; line-height: 1.55;
|
||||
.rspace-tour__msg {
|
||||
font-size: 0.85rem;
|
||||
color: var(--rs-text-secondary, #94a3b8);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.rspace-welcome__text strong { color: var(--rs-text-primary); }
|
||||
.rspace-welcome__grid {
|
||||
display: grid; grid-template-columns: 1fr 1fr;
|
||||
gap: 5px; margin-bottom: 14px; font-size: 0.8rem; color: var(--rs-text-primary);
|
||||
.rspace-tour__nav {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
}
|
||||
.rspace-welcome__grid span { padding: 3px 0; }
|
||||
.rspace-welcome__actions {
|
||||
display: flex; gap: 8px; margin-bottom: 12px;
|
||||
.rspace-tour__btn {
|
||||
padding: 7px 16px; border-radius: 8px;
|
||||
font-size: 0.8rem; font-weight: 600;
|
||||
cursor: pointer; border: none;
|
||||
transition: background 0.15s, transform 0.1s;
|
||||
font-family: inherit;
|
||||
}
|
||||
.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-tour__btn:hover { transform: translateY(-1px); }
|
||||
.rspace-tour__btn--next {
|
||||
background: linear-gradient(135deg, #06b6d4, #7c3aed); color: white;
|
||||
}
|
||||
.rspace-welcome__btn:hover { transform: translateY(-1px); }
|
||||
.rspace-welcome__btn--primary {
|
||||
background: var(--rs-gradient-cta); color: white;
|
||||
box-shadow: 0 2px 8px rgba(20,184,166,0.3);
|
||||
.rspace-tour__btn--prev {
|
||||
background: var(--rs-btn-secondary-bg, #334155);
|
||||
color: var(--rs-text-secondary, #94a3b8);
|
||||
}
|
||||
.rspace-welcome__btn--secondary {
|
||||
background: var(--rs-btn-secondary-bg); color: var(--rs-text-secondary);
|
||||
.rspace-tour__btn--skip {
|
||||
background: none;
|
||||
color: var(--rs-text-muted, #64748b);
|
||||
margin-left: auto;
|
||||
}
|
||||
.rspace-welcome__btn--secondary:hover { color: var(--rs-text-primary); }
|
||||
.rspace-welcome__footer {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
}
|
||||
.rspace-welcome__link {
|
||||
font-size: 0.72rem; color: var(--rs-text-muted); text-decoration: none;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
.rspace-welcome__link:hover { color: #c4b5fd; }
|
||||
.rspace-welcome__dot { color: var(--rs-text-muted); font-size: 0.6rem; }
|
||||
.rspace-tour__btn--skip:hover { color: var(--rs-text-primary, #f1f5f9); }
|
||||
@media (max-width: 600px) {
|
||||
.rspace-welcome { bottom: 12px; right: 12px; left: 12px; }
|
||||
.rspace-welcome__popup { width: 100%; max-width: none; }
|
||||
.rspace-tour__tooltip { width: calc(100vw - 24px); left: 12px !important; }
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@
|
|||
html.rspace-embedded .rstack-tab-row { display: none !important; }
|
||||
html.rspace-embedded #toolbar { top: 16px !important; }
|
||||
html.rspace-embedded #community-info { display: none !important; }
|
||||
html.rspace-embedded #people-online-badge { top: 12px !important; }
|
||||
html.rspace-embedded #people-panel { top: 48px !important; }
|
||||
</style>
|
||||
<script>if (window.self !== window.parent) document.documentElement.classList.add('rspace-embedded');</script>
|
||||
<style>
|
||||
|
|
@ -576,6 +578,14 @@
|
|||
z-index: 1000;
|
||||
}
|
||||
|
||||
/* Collapsed state: hide everything except the toggle + feed */
|
||||
#canvas-corner-tools.collapsed #zoom-in,
|
||||
#canvas-corner-tools.collapsed #zoom-out,
|
||||
#canvas-corner-tools.collapsed #reset-view,
|
||||
#canvas-corner-tools.collapsed .corner-sep {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.corner-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -612,6 +622,14 @@
|
|||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Zoom toggle icon rotates when expanded */
|
||||
#corner-zoom-toggle svg {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
#canvas-corner-tools:not(.collapsed) #corner-zoom-toggle svg {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
/* ── Header history button ── */
|
||||
.canvas-header-history {
|
||||
display: flex;
|
||||
|
|
@ -944,7 +962,7 @@
|
|||
/* ── People Online badge ── */
|
||||
#people-online-badge {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
top: 68px;
|
||||
right: 16px;
|
||||
padding: 6px 12px;
|
||||
background: var(--rs-bg-surface);
|
||||
|
|
@ -981,7 +999,7 @@
|
|||
/* ── People Panel ── */
|
||||
#people-panel {
|
||||
position: fixed;
|
||||
bottom: 56px;
|
||||
top: 104px;
|
||||
right: 16px;
|
||||
width: 280px;
|
||||
max-height: calc(100vh - 120px);
|
||||
|
|
@ -1225,8 +1243,8 @@
|
|||
/* ── People panel mobile ── */
|
||||
@media (max-width: 640px) {
|
||||
#people-online-badge {
|
||||
right: 16px;
|
||||
bottom: 12px;
|
||||
right: 12px;
|
||||
top: 64px;
|
||||
}
|
||||
#people-panel {
|
||||
max-width: calc(100vw - 32px);
|
||||
|
|
@ -1268,6 +1286,7 @@
|
|||
display: none !important;
|
||||
}
|
||||
/* Keep corner tools but hide zoom, show only feed toggle */
|
||||
body:has(#canvas.feed-mode) #canvas-corner-tools #corner-zoom-toggle,
|
||||
body:has(#canvas.feed-mode) #canvas-corner-tools #zoom-in,
|
||||
body:has(#canvas.feed-mode) #canvas-corner-tools #zoom-out,
|
||||
body:has(#canvas.feed-mode) #canvas-corner-tools #reset-view,
|
||||
|
|
@ -1868,73 +1887,48 @@
|
|||
</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-tour { position: fixed; inset: 0; z-index: 10000; pointer-events: none; }
|
||||
.rspace-tour__backdrop {
|
||||
position: fixed; inset: 0;
|
||||
background: rgba(0,0,0,0.55);
|
||||
pointer-events: auto;
|
||||
transition: clip-path 0.3s ease;
|
||||
}
|
||||
.rspace-welcome__popup {
|
||||
position: relative;
|
||||
width: min(380px, 44vw); max-height: 50vh;
|
||||
.rspace-tour__spotlight {
|
||||
position: fixed;
|
||||
border: 2px solid #06b6d4;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 0 4px rgba(6,182,212,0.25), 0 0 20px rgba(6,182,212,0.15);
|
||||
pointer-events: none;
|
||||
transition: all 0.35s ease;
|
||||
}
|
||||
.rspace-tour__tooltip {
|
||||
position: fixed;
|
||||
width: min(360px, calc(100vw - 32px));
|
||||
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;
|
||||
border-radius: 14px; padding: 20px;
|
||||
box-shadow: 0 16px 48px rgba(0,0,0,0.5);
|
||||
color: #f1f5f9;
|
||||
pointer-events: auto;
|
||||
animation: rspace-tour-pop 0.25s ease-out;
|
||||
transition: top 0.35s ease, left 0.35s ease;
|
||||
}
|
||||
@keyframes rspace-welcome-in {
|
||||
from { opacity: 0; transform: translateY(20px) scale(0.95); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
@keyframes rspace-tour-pop {
|
||||
from { opacity: 0; transform: translateY(8px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.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; }
|
||||
.rspace-tour__step { font-size: 0.7rem; color: #64748b; margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.05em; }
|
||||
.rspace-tour__title { font-size: 1.1rem; font-weight: 700; margin-bottom: 8px; background: linear-gradient(135deg, #06b6d4, #7c3aed); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
||||
.rspace-tour__msg { font-size: 0.85rem; color: #94a3b8; line-height: 1.6; margin-bottom: 14px; }
|
||||
.rspace-tour__nav { display: flex; align-items: center; gap: 8px; }
|
||||
.rspace-tour__btn { padding: 7px 16px; border-radius: 8px; font-size: 0.8rem; font-weight: 600; cursor: pointer; border: none; transition: background 0.15s, transform 0.1s; font-family: inherit; }
|
||||
.rspace-tour__btn:hover { transform: translateY(-1px); }
|
||||
.rspace-tour__btn--next { background: linear-gradient(135deg, #06b6d4, #7c3aed); color: white; }
|
||||
.rspace-tour__btn--prev { background: rgba(255,255,255,0.08); color: #94a3b8; }
|
||||
.rspace-tour__btn--skip { background: none; color: #64748b; margin-left: auto; }
|
||||
.rspace-tour__btn--skip:hover { color: #f1f5f9; }
|
||||
@media (max-width: 600px) {
|
||||
.rspace-welcome { bottom: 12px; right: 12px; left: 12px; }
|
||||
.rspace-welcome__popup { width: 100%; max-width: none; }
|
||||
.rspace-tour__tooltip { width: calc(100vw - 24px); left: 12px !important; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
|
@ -1967,29 +1961,18 @@
|
|||
<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()">×</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> — 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">·</span>
|
||||
<a href="/rids" class="rspace-welcome__link">EncryptID</a>
|
||||
<!-- Welcome tour (guided feature walkthrough for first-time visitors) -->
|
||||
<div id="rspace-tour" style="display:none">
|
||||
<div class="rspace-tour__backdrop" id="rspace-tour-backdrop"></div>
|
||||
<div class="rspace-tour__spotlight" id="rspace-tour-spotlight"></div>
|
||||
<div class="rspace-tour__tooltip" id="rspace-tour-tooltip">
|
||||
<div class="rspace-tour__step" id="rspace-tour-step"></div>
|
||||
<div class="rspace-tour__title" id="rspace-tour-title"></div>
|
||||
<div class="rspace-tour__msg" id="rspace-tour-msg"></div>
|
||||
<div class="rspace-tour__nav">
|
||||
<button class="rspace-tour__btn rspace-tour__btn--prev" id="rspace-tour-prev" style="display:none">Back</button>
|
||||
<button class="rspace-tour__btn rspace-tour__btn--next" id="rspace-tour-next">Next</button>
|
||||
<button class="rspace-tour__btn rspace-tour__btn--skip" id="rspace-tour-skip">Skip tour</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -2213,7 +2196,10 @@
|
|||
</div>
|
||||
|
||||
<!-- Corner tools: zoom + feed (bottom-left) -->
|
||||
<div id="canvas-corner-tools">
|
||||
<div id="canvas-corner-tools" class="collapsed">
|
||||
<button id="corner-zoom-toggle" title="Zoom Controls" class="corner-btn">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg>
|
||||
</button>
|
||||
<button id="zoom-in" title="Zoom In" class="corner-btn">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg>
|
||||
</button>
|
||||
|
|
@ -2918,18 +2904,46 @@
|
|||
}
|
||||
})();
|
||||
|
||||
// ── Welcome overlay (first visit to demo) ──
|
||||
// ── Welcome tour (guided feature walkthrough) ──
|
||||
(function() {
|
||||
if (communitySlug !== 'demo') return;
|
||||
if (localStorage.getItem('rspace_welcomed')) return;
|
||||
var el = document.getElementById('rspace-welcome');
|
||||
if (el) el.style.display = 'flex';
|
||||
if (localStorage.getItem('rspace_tour_done')) return;
|
||||
var TOUR_STEPS = [
|
||||
{ target: '#toolbar', title: 'Welcome to rSpace', msg: 'This is your infinite canvas with 25+ interoperable tools. Use the toolbar to add Notes, Chats, AI Prompts, Embeds, Voting, and more.' },
|
||||
{ target: 'rstack-mi', title: 'Meet mi \u2014 Your AI Guide', msg: 'Press <strong>Cmd+K</strong> (or Ctrl+K) to open mi, your mycelial intelligence assistant. mi can create content, set up spaces, and help you build across all rApps.' },
|
||||
{ target: 'rstack-space-switcher', title: 'Spaces', msg: 'Each space is a self-contained community with its own data, members, and encryption. Switch between spaces or create a new one here.' },
|
||||
{ target: 'rstack-identity', title: 'Passwordless Identity', msg: 'Sign in with <strong>passkeys</strong> \u2014 no passwords or seed phrases. Your identity is cryptographic and portable across all spaces.' },
|
||||
{ target: '#canvas', title: 'The Canvas', msg: 'Drag to pan, scroll to zoom. Click any shape to interact with it. Connect shapes with arrows by dragging between ports. Everything syncs in real-time.' },
|
||||
{ target: '#bottom-toolbar', title: 'Drawing Tools', msg: 'Select, draw, and erase on the canvas. Switch between tools here, or use keyboard shortcuts (V for select, P for pen, E for eraser).' },
|
||||
];
|
||||
var step = 0, tourEl = document.getElementById('rspace-tour'), backdrop = document.getElementById('rspace-tour-backdrop'), spotlight = document.getElementById('rspace-tour-spotlight'), tooltip = document.getElementById('rspace-tour-tooltip'), titleEl = document.getElementById('rspace-tour-title'), msgEl = document.getElementById('rspace-tour-msg'), stepEl = document.getElementById('rspace-tour-step'), prevBtn = document.getElementById('rspace-tour-prev'), nextBtn = document.getElementById('rspace-tour-next'), skipBtn = document.getElementById('rspace-tour-skip');
|
||||
function endTour() { localStorage.setItem('rspace_tour_done', '1'); if (tourEl) tourEl.style.display = 'none'; }
|
||||
function showStep() {
|
||||
if (!tourEl || !backdrop || !spotlight || !tooltip) return;
|
||||
var s = TOUR_STEPS[step], target = document.querySelector(s.target);
|
||||
var rect = target ? target.getBoundingClientRect() : { left: window.innerWidth / 2 - 60, top: 8, width: 120, height: 40 };
|
||||
var pad = 6, sx = rect.left - pad, sy = rect.top - pad, sw = rect.width + pad * 2, sh = rect.height + pad * 2;
|
||||
spotlight.style.left = sx + 'px'; spotlight.style.top = sy + 'px'; spotlight.style.width = sw + 'px'; spotlight.style.height = sh + 'px';
|
||||
backdrop.style.clipPath = 'polygon(0% 0%, 0% 100%, ' + sx + 'px 100%, ' + sx + 'px ' + sy + 'px, ' + (sx + sw) + 'px ' + sy + 'px, ' + (sx + sw) + 'px ' + (sy + sh) + 'px, ' + sx + 'px ' + (sy + sh) + 'px, ' + sx + 'px 100%, 100% 100%, 100% 0%)';
|
||||
var ttTop = rect.top + rect.height + 16, ttLeft = Math.max(16, Math.min(rect.left, window.innerWidth - 380));
|
||||
if (ttTop + 200 > window.innerHeight) ttTop = Math.max(16, rect.top - 220);
|
||||
tooltip.style.top = ttTop + 'px'; tooltip.style.left = ttLeft + 'px';
|
||||
stepEl.textContent = (step + 1) + ' / ' + TOUR_STEPS.length; titleEl.textContent = s.title; msgEl.innerHTML = s.msg;
|
||||
prevBtn.style.display = step > 0 ? '' : 'none'; nextBtn.textContent = step === TOUR_STEPS.length - 1 ? 'Get Started' : 'Next';
|
||||
}
|
||||
if (nextBtn) nextBtn.addEventListener('click', function() { step++; if (step >= TOUR_STEPS.length) { endTour(); return; } showStep(); });
|
||||
if (prevBtn) prevBtn.addEventListener('click', function() { if (step > 0) { step--; showStep(); } });
|
||||
if (skipBtn) skipBtn.addEventListener('click', endTour);
|
||||
if (backdrop) backdrop.addEventListener('click', endTour);
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (!tourEl || tourEl.style.display === 'none') return;
|
||||
if (e.key === 'Escape') { endTour(); e.preventDefault(); }
|
||||
if (e.key === 'ArrowRight' || e.key === 'Enter') { step++; if (step >= TOUR_STEPS.length) { endTour(); return; } showStep(); e.preventDefault(); }
|
||||
if (e.key === 'ArrowLeft' && step > 0) { step--; showStep(); e.preventDefault(); }
|
||||
});
|
||||
setTimeout(function() { if (tourEl) { tourEl.style.display = ''; showStep(); } }, 800);
|
||||
})();
|
||||
window.__rspaceDismissWelcome = function() {
|
||||
localStorage.setItem('rspace_welcomed', '1');
|
||||
var el = document.getElementById('rspace-welcome');
|
||||
if (el) el.style.display = 'none';
|
||||
};
|
||||
window.__rspaceDismissWelcome = function() { localStorage.setItem('rspace_tour_done', '1'); var el = document.getElementById('rspace-tour'); if (el) el.style.display = 'none'; };
|
||||
|
||||
const canvas = document.getElementById("canvas");
|
||||
const canvasContent = document.getElementById("canvas-content");
|
||||
|
|
@ -5815,6 +5829,11 @@
|
|||
updateCanvasTransform();
|
||||
});
|
||||
|
||||
// Corner zoom toggle — expand/collapse zoom controls
|
||||
document.getElementById("corner-zoom-toggle").addEventListener("click", () => {
|
||||
document.getElementById("canvas-corner-tools").classList.toggle("collapsed");
|
||||
});
|
||||
|
||||
// Mobile toolbar toggle — collapse behavior same as desktop
|
||||
const mobileMenuBtn = document.getElementById("mobile-menu");
|
||||
const toolbarEl = document.getElementById("toolbar");
|
||||
|
|
|
|||
Loading…
Reference in New Issue