// Onboarding Tour - Interactive first-run walkthrough // Shows on first visit, can be re-triggered from command palette or help const STORAGE_KEY = 'ruview-onboarding-done'; export class Onboarding { constructor(app) { this.app = app; this.overlay = null; this.currentStep = 0; this.steps = []; this.active = false; } init() { this.defineSteps(); document.addEventListener('start-onboarding', () => this.start()); // Auto-start on first visit if (!this.isDone()) { // Delay to let the app render first setTimeout(() => this.start(), 800); } } defineSteps() { this.steps = [ { title: 'Welcome to RuView', text: 'WiFi-based human pose estimation that works through walls. Let\'s take a quick tour of the dashboard.', target: null, // No highlight, centered position: 'center' }, { title: 'System Status', text: 'Monitor your WiFi sensing hardware and API server status in real time. Green means everything is connected.', target: '.live-status-panel', position: 'bottom' }, { title: 'Live Demo', text: 'Switch to the Live Demo tab to see real-time pose detection. Connect an ESP32 sensor or use the built-in simulation.', target: '[data-tab="demo"]', position: 'bottom' }, { title: 'Sensing Visualization', text: 'The Sensing tab shows a 3D Gaussian splat visualization of WiFi signal fields, with real-time metrics.', target: '[data-tab="sensing"]', position: 'bottom' }, { title: 'Keyboard Shortcuts', text: 'Press ? for shortcuts, Ctrl+K for the command palette, or use number keys 1-8 to switch tabs quickly.', target: null, position: 'center' }, { title: 'You\'re all set!', text: 'Explore the dashboard, connect hardware, or start the demo. You can replay this tour anytime from the command palette.', target: null, position: 'center' } ]; } isDone() { try { return localStorage.getItem(STORAGE_KEY) === 'true'; } catch { return false; } } markDone() { try { localStorage.setItem(STORAGE_KEY, 'true'); } catch { /* noop */ } } start() { this.currentStep = 0; this.active = true; this.createOverlay(); this.showStep(); } createOverlay() { // Remove existing if any this.removeOverlay(); this.overlay = document.createElement('div'); this.overlay.className = 'onboarding-overlay'; this.overlay.setAttribute('role', 'dialog'); this.overlay.setAttribute('aria-label', 'Onboarding tour'); this.overlay.setAttribute('aria-modal', 'true'); document.body.appendChild(this.overlay); } showStep() { if (this.currentStep >= this.steps.length) { this.finish(); return; } const step = this.steps[this.currentStep]; const total = this.steps.length; const isFirst = this.currentStep === 0; const isLast = this.currentStep === total - 1; // Clear highlight document.querySelectorAll('.onboarding-highlight').forEach(el => el.classList.remove('onboarding-highlight')); // Highlight target let targetRect = null; if (step.target) { const targetEl = document.querySelector(step.target); if (targetEl) { targetEl.classList.add('onboarding-highlight'); targetRect = targetEl.getBoundingClientRect(); } } this.overlay.innerHTML = `
${step.text}