diff --git a/server/shell.ts b/server/shell.ts
index 347e7d2..377b2dd 100644
--- a/server/shell.ts
+++ b/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 Cmd+K (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 passkeys \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 {