/** * AppForge Documentation v2.0.2 * Enhanced interactive documentation with smooth animations and modern UX */ // ═══════════════════════════════════════════════════════════ // THEME MANAGEMENT // ═══════════════════════════════════════════════════════════ function toggleTheme() { const current = document.documentElement.getAttribute('data-theme'); const next = current === 'dark' ? 'light' : 'dark'; document.documentElement.setAttribute('data-theme', next); localStorage.setItem('docs-theme', next); } function initTheme() { const stored = localStorage.getItem('docs-theme'); if (stored) { document.documentElement.setAttribute('data-theme', stored); } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) { document.documentElement.setAttribute('data-theme', 'dark'); } } // ═══════════════════════════════════════════════════════════ // SIDEBAR SEARCH / FILTER // ═══════════════════════════════════════════════════════════ function filterSidebar() { const input = document.getElementById('sidebarSearch'); if (!input) return; const query = input.value.toLowerCase().trim(); const sections = document.querySelectorAll('.sidebar-section'); sections.forEach(section => { const links = section.querySelectorAll('.sidebar-link'); let anyVisible = false; links.forEach(link => { const text = link.textContent.toLowerCase(); const match = !query || text.includes(query); link.parentElement.style.display = match ? '' : 'none'; if (match) anyVisible = true; }); const title = section.querySelector('.sidebar-title'); if (title) { section.style.display = anyVisible || !query ? '' : 'none'; } }); } // ═══════════════════════════════════════════════════════════ // SIDEBAR MANAGEMENT // ═══════════════════════════════════════════════════════════ function toggleSidebar() { const sidebar = document.getElementById('sidebar'); const overlay = document.getElementById('sidebarOverlay'); if (sidebar) sidebar.classList.toggle('open'); if (overlay) overlay.classList.toggle('open'); } function closeSidebar() { const sidebar = document.getElementById('sidebar'); const overlay = document.getElementById('sidebarOverlay'); if (sidebar) sidebar.classList.remove('open'); if (overlay) overlay.classList.remove('open'); } // ═══════════════════════════════════════════════════════════ // COLLAPSIBLE SIDEBAR SECTIONS // ═══════════════════════════════════════════════════════════ function toggleSidebarSection(el) { const section = el.closest('.sidebar-section'); if (section) section.classList.toggle('collapsed'); } // ═══════════════════════════════════════════════════════════ // FAQ ACCORDION // ═══════════════════════════════════════════════════════════ function toggleFAQ(element) { const answer = element.nextElementSibling; const arrow = element.querySelector('.faq-arrow'); if (answer) { answer.classList.toggle('open'); } if (arrow) { arrow.style.transform = answer && answer.classList.contains('open') ? 'rotate(180deg)' : 'rotate(0)'; } } // ═══════════════════════════════════════════════════════════ // FAQ SEARCH / FILTER // ═══════════════════════════════════════════════════════════ function filterFAQs() { const searchInput = document.getElementById('searchInput'); if (!searchInput) return; const query = searchInput.value.toLowerCase().trim(); const faqItems = document.querySelectorAll('.faq-item'); const faqSections = document.querySelectorAll('#help-center .card'); faqItems.forEach(item => { const text = item.textContent.toLowerCase(); item.style.display = text.includes(query) ? '' : 'none'; }); // Hide sections where all items are hidden faqSections.forEach(section => { const items = section.querySelectorAll('.faq-item'); if (items.length === 0) return; const allHidden = Array.from(items).every(i => i.style.display === 'none'); if (section.querySelector('.faq-list')) { section.style.display = allHidden ? 'none' : ''; } }); } // ═══════════════════════════════════════════════════════════ // SMOOTH SCROLL // ═══════════════════════════════════════════════════════════ function scrollToSection(id) { const element = document.getElementById(id); if (element) { element.scrollIntoView({ behavior: 'smooth' }); } } // ═══════════════════════════════════════════════════════════ // COPY CODE TO CLIPBOARD // ═══════════════════════════════════════════════════════════ function copyCode(button) { const codeBlock = button.closest('.code-block'); const code = codeBlock ? codeBlock.querySelector('code') : null; if (code) { navigator.clipboard.writeText(code.textContent).then(() => { const originalText = button.textContent; button.textContent = '✓ Copied!'; button.classList.add('copied'); setTimeout(() => { button.textContent = originalText; button.classList.remove('copied'); }, 2000); }).catch(err => { console.error('Failed to copy:', err); }); } } // ═══════════════════════════════════════════════════════════ // TAB NAVIGATION // ═══════════════════════════════════════════════════════════ function switchTab(tabGroup, tabId) { const group = document.querySelector(`[data-tab-group="${tabGroup}"]`); if (!group) return; group.querySelectorAll('.tab-btn').forEach(btn => { btn.classList.toggle('active', btn.dataset.tab === tabId); }); group.querySelectorAll('.tab-content').forEach(content => { content.classList.toggle('active', content.id === tabId); }); } // ═══════════════════════════════════════════════════════════ // READING PROGRESS BAR // ═══════════════════════════════════════════════════════════ function updateReadingProgress() { const bar = document.querySelector('.reading-progress'); if (!bar) return; const scrollTop = window.scrollY; const docHeight = document.documentElement.scrollHeight - window.innerHeight; const progress = docHeight > 0 ? (scrollTop / docHeight) * 100 : 0; bar.style.width = Math.min(progress, 100) + '%'; } // ═══════════════════════════════════════════════════════════ // ACTIVE SIDEBAR LINK (SCROLL SPY) // ═══════════════════════════════════════════════════════════ function updateActiveSidebarLink() { const sections = document.querySelectorAll('section[id], div[id]'); const sidebarLinks = document.querySelectorAll('.sidebar-link, .sidebar-link-sub'); const navLinks = document.querySelectorAll('.nav-center a'); let currentSection = ''; const offset = 120; sections.forEach(section => { const rect = section.getBoundingClientRect(); if (rect.top <= offset) { currentSection = section.id; } }); // Update sidebar links sidebarLinks.forEach(link => { const href = link.getAttribute('href'); if (href && href === '#' + currentSection) { link.classList.add('active'); } else if (href && currentSection && href.includes('#')) { // Check if this is a parent section const section = document.getElementById(currentSection); const linkTarget = href.substring(1); if (section && section.closest('[id="' + linkTarget + '"]')) { link.classList.add('active'); } else { link.classList.remove('active'); } } else { link.classList.remove('active'); } }); // Update nav links const mainSections = ['getting-started', 'api-reference', 'native-features', 'deployment']; navLinks.forEach(link => { const href = link.getAttribute('href'); if (!href) return; const target = href.substring(1); const isActive = mainSections.some(s => { if (target === s) { const el = document.getElementById(s); if (!el) return false; const rect = el.getBoundingClientRect(); return rect.top <= offset && rect.bottom > 0; } return false; }); link.classList.toggle('active', currentSection === target || isActive); }); } // ═══════════════════════════════════════════════════════════ // BACK TO TOP BUTTON // ═══════════════════════════════════════════════════════════ function updateBackToTop() { const btn = document.querySelector('.back-to-top'); if (!btn) return; btn.classList.toggle('visible', window.scrollY > 400); } // ═══════════════════════════════════════════════════════════ // NAV SCROLL EFFECT // ═══════════════════════════════════════════════════════════ function updateNavEffect() { const nav = document.querySelector('.nav'); if (!nav) return; nav.classList.toggle('scrolled', window.scrollY > 10); } // ═══════════════════════════════════════════════════════════ // SCROLL ANIMATIONS (Intersection Observer) // ═══════════════════════════════════════════════════════════ function initScrollAnimations() { const observer = new IntersectionObserver( (entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('animate-in'); observer.unobserve(entry.target); } }); }, { threshold: 0.1, rootMargin: '0px 0px -40px 0px' } ); document.querySelectorAll('.card, .callout, .table-wrapper').forEach(el => { el.style.opacity = '0'; el.style.transform = 'translateY(20px)'; observer.observe(el); }); } // ═══════════════════════════════════════════════════════════ // AUTO-WRAP CODE BLOCKS WITH COPY BUTTON // ═══════════════════════════════════════════════════════════ function initCodeBlocks() { document.querySelectorAll('pre').forEach(pre => { // Skip if already wrapped if (pre.parentElement.classList.contains('code-block')) return; const wrapper = document.createElement('div'); wrapper.className = 'code-block'; const btn = document.createElement('button'); btn.className = 'copy-btn'; btn.textContent = 'Copy'; btn.onclick = function() { copyCode(this); }; pre.parentNode.insertBefore(wrapper, pre); wrapper.appendChild(pre); wrapper.appendChild(btn); }); } // ═══════════════════════════════════════════════════════════ // DEBOUNCE UTILITY // ═══════════════════════════════════════════════════════════ function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // ═══════════════════════════════════════════════════════════ // INITIALIZATION // ═══════════════════════════════════════════════════════════ document.addEventListener('DOMContentLoaded', () => { // Scroll handlers (debounced) const onScroll = () => { updateReadingProgress(); updateActiveSidebarLink(); updateBackToTop(); updateNavEffect(); }; window.addEventListener('scroll', debounce(onScroll, 16), { passive: true }); onScroll(); // Initial call // Initialize code blocks with copy buttons initCodeBlocks(); // Initialize scroll animations initScrollAnimations(); // Keyboard shortcuts document.addEventListener('keydown', (e) => { // Ctrl/Cmd + K to focus search if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); const searchInput = document.getElementById('searchInput') || document.querySelector('.nav-search-input'); if (searchInput) searchInput.focus(); } // Escape to close sidebar if (e.key === 'Escape') { closeSidebar(); } }); // Close sidebar when clicking a link (mobile) document.querySelectorAll('.sidebar-link, .sidebar-link-sub').forEach(link => { link.addEventListener('click', () => { if (window.innerWidth <= 900) closeSidebar(); }); }); }); // Run theme initialization immediately (before DOM ready) initTheme();