// tourHints.js — secret continuation of /tour. The first time the user opens // a tool modal (after the welcome experience), surface a single "pro tip" // hint pointing out that modals can be snapped to the screen edge or // fullscreened by dragging the title bar. Shown once globally — once the // user has dismissed it (or it auto-hides), it never returns. const HINT_SEEN_KEY = 'odysseus-hint-drag-to-snap-seen'; // Allow-list of modals where the snap/fullscreen hint makes sense. // These are the full-window "tool" modals where users commonly want to // reposition or fullscreen the pane (email, calendar, cookbook, gallery, // library, brain memories, tasks, theme, compare). Transient modals // like settings, prompts, rename dialogs, custom-preset picker, etc. // are excluded — opening those is task-focused and the snap tip would // be noise. const SHOW_MODALS = new Set([ 'email-lib-modal', 'calendar-modal', 'compare-modal', // not currently a real id, defensive 'cookbook-modal', 'gallery-modal', 'doclib-modal', 'library-modal', // chat-history library (sessions.js) 'memory-modal', // brain / memories 'tasks-modal', 'theme-modal', ]); // Some modals have dynamic per-instance IDs (e.g. one window per opened // email). Match by prefix so any window from the same family qualifies. const SHOW_MODAL_PREFIXES = ['email-window-']; function _modalShouldShowHint(id) { if (!id) return false; if (SHOW_MODALS.has(id)) return true; return SHOW_MODAL_PREFIXES.some(p => id.startsWith(p)); } let _shown = false; let _initialized = false; function _hasSeen() { return localStorage.getItem(HINT_SEEN_KEY) === '1'; } function _markSeen() { try { localStorage.setItem(HINT_SEEN_KEY, '1'); } catch {} } function _isVisible(el) { if (!el || el.classList.contains('hidden')) return false; // Some modals set inline display:none rather than .hidden if (el.style.display === 'none') return false; const r = el.getBoundingClientRect(); return r.width > 0 && r.height > 0; } function _onModalOpened(modal) { if (_shown || _hasSeen()) return; const id = modal.id; if (!_modalShouldShowHint(id)) return; // Don't interrupt the welcome / tour itself if (document.body.classList.contains('tour-active')) return; if (document.getElementById('tour-tooltip')) return; // Mobile: skip — snapping isn't a desktop-only feature there if (window.innerWidth <= 768) return; _shown = true; // Give the modal a moment to settle (some open with their own animation). setTimeout(() => _show(modal), 380); } function _show(modal) { if (_hasSeen()) return; const content = modal.querySelector('.modal-content') || modal; const r = content.getBoundingClientRect(); const pop = document.createElement('div'); pop.className = 'tour-hint'; pop.innerHTML = `