Skip to main content
Glama
script.js14.2 kB
// Theme Management function initTheme() { const themeToggle = document.getElementById('theme-toggle'); const themeIcon = document.getElementById('theme-icon'); const html = document.documentElement; // Get saved theme or default to light const savedTheme = localStorage.getItem('theme') || 'light'; html.setAttribute('data-theme', savedTheme); updateThemeIcon(savedTheme); // Theme toggle functionality themeToggle.addEventListener('click', function() { const currentTheme = html.getAttribute('data-theme'); const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; html.setAttribute('data-theme', newTheme); localStorage.setItem('theme', newTheme); updateThemeIcon(newTheme); // Add a subtle animation themeToggle.style.transform = 'scale(0.9)'; setTimeout(() => { themeToggle.style.transform = 'scale(1)'; }, 150); }); function updateThemeIcon(theme) { themeIcon.textContent = theme === 'dark' ? '☀️' : '🌙'; themeToggle.setAttribute('aria-label', theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode' ); } } // Dynamic Version Fetching async function fetchLatestVersion() { const versionDisplay = document.getElementById('version-display'); const fallbackVersion = 'v0.5.1'; // Show loading state versionDisplay.style.opacity = '0.6'; versionDisplay.textContent = 'Loading...'; try { // Check cache first const cached = localStorage.getItem('cached-version'); if (cached) { const cacheData = JSON.parse(cached); const oneHour = 60 * 60 * 1000; if (Date.now() - cacheData.timestamp < oneHour) { versionDisplay.textContent = cacheData.version; versionDisplay.style.opacity = '1'; return; } } const response = await fetch('https://api.github.com/repos/cameronrye/openzim-mcp/releases/latest', { headers: { 'Accept': 'application/vnd.github.v3+json' } }); if (!response.ok) throw new Error('Failed to fetch'); const data = await response.json(); const version = data.tag_name || fallbackVersion; // Animate the version update versionDisplay.style.opacity = '0'; setTimeout(() => { versionDisplay.textContent = version; versionDisplay.style.opacity = '1'; }, 150); // Cache the version for 1 hour const cacheData = { version: version, timestamp: Date.now() }; localStorage.setItem('cached-version', JSON.stringify(cacheData)); } catch (error) { console.log('Using fallback version due to:', error.message); // Use fallback version with animation versionDisplay.style.opacity = '0'; setTimeout(() => { versionDisplay.textContent = fallbackVersion; versionDisplay.style.opacity = '1'; }, 150); } } // Mobile Navigation Toggle document.addEventListener('DOMContentLoaded', function() { const navToggle = document.getElementById('nav-toggle'); const navMenu = document.getElementById('nav-menu'); const navLinks = document.querySelectorAll('.nav-link'); // Toggle mobile menu navToggle.addEventListener('click', function() { navToggle.classList.toggle('active'); navMenu.classList.toggle('active'); document.body.classList.toggle('nav-open'); }); // Close mobile menu when clicking on a link navLinks.forEach(link => { link.addEventListener('click', function() { navToggle.classList.remove('active'); navMenu.classList.remove('active'); document.body.classList.remove('nav-open'); }); }); // Keyboard navigation for mobile menu navToggle.addEventListener('keydown', function(e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); navToggle.click(); } }); // Close mobile menu when clicking outside document.addEventListener('click', function(e) { if (!navToggle.contains(e.target) && !navMenu.contains(e.target)) { navToggle.classList.remove('active'); navMenu.classList.remove('active'); document.body.classList.remove('nav-open'); } }); }); // Navbar scroll effect window.addEventListener('scroll', function() { const navbar = document.getElementById('navbar'); const theme = document.documentElement.getAttribute('data-theme') || 'light'; if (window.scrollY > 50) { if (theme === 'dark') { navbar.style.background = 'rgba(15, 23, 42, 0.98)'; } else { navbar.style.background = 'rgba(255, 255, 255, 0.98)'; } navbar.style.boxShadow = '0 4px 6px -1px rgba(0, 0, 0, 0.1)'; } else { if (theme === 'dark') { navbar.style.background = 'rgba(15, 23, 42, 0.95)'; } else { navbar.style.background = 'rgba(255, 255, 255, 0.95)'; } navbar.style.boxShadow = 'none'; } }); // Smooth scrolling for anchor links document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); const target = document.querySelector(this.getAttribute('href')); if (target) { const offsetTop = target.offsetTop - 80; // Account for fixed navbar window.scrollTo({ top: offsetTop, behavior: 'smooth' }); } }); }); // Copy to clipboard functionality function initCopyButtons() { const copyButtons = document.querySelectorAll('.copy-btn'); copyButtons.forEach(button => { button.addEventListener('click', async function() { const targetId = this.getAttribute('data-clipboard-target'); const targetElement = document.querySelector(targetId); if (targetElement) { const text = targetElement.textContent || targetElement.innerText; try { await navigator.clipboard.writeText(text); // Visual feedback const originalIcon = this.querySelector('.copy-icon'); const originalText = originalIcon.textContent; originalIcon.textContent = '✅'; setTimeout(() => { originalIcon.textContent = originalText; }, 2000); } catch (err) { console.error('Failed to copy text: ', err); // Fallback for older browsers const textArea = document.createElement('textarea'); textArea.value = text; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); // Visual feedback const originalIcon = this.querySelector('.copy-icon'); const originalText = originalIcon.textContent; originalIcon.textContent = '✅'; setTimeout(() => { originalIcon.textContent = originalText; }, 2000); } } }); }); } // Tab functionality for usage examples function initTabs() { const tabButtons = document.querySelectorAll('.tab-btn'); const tabPanes = document.querySelectorAll('.tab-pane'); tabButtons.forEach(button => { button.addEventListener('click', function() { const targetTab = this.getAttribute('data-tab'); // Remove active class from all buttons and panes tabButtons.forEach(btn => btn.classList.remove('active')); tabPanes.forEach(pane => pane.classList.remove('active')); // Add active class to clicked button and corresponding pane this.classList.add('active'); const targetPane = document.getElementById(targetTab); if (targetPane) { targetPane.classList.add('active'); } }); }); } // Tab content is now in HTML, no need for dynamic generation // Intersection Observer for animations function initScrollAnimations() { const observerOptions = { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.style.opacity = '1'; entry.target.style.transform = 'translateY(0)'; } }); }, observerOptions); // Observe feature cards document.querySelectorAll('.feature-card').forEach(card => { card.style.opacity = '0'; card.style.transform = 'translateY(20px)'; card.style.transition = 'opacity 0.6s ease, transform 0.6s ease'; observer.observe(card); }); // Observe installation steps document.querySelectorAll('.step').forEach(step => { step.style.opacity = '0'; step.style.transform = 'translateY(20px)'; step.style.transition = 'opacity 0.6s ease, transform 0.6s ease'; observer.observe(step); }); } // Add keyboard navigation for tabs function initKeyboardNavigation() { const tabButtons = document.querySelectorAll('.tab-btn'); tabButtons.forEach((button, index) => { button.addEventListener('keydown', function(e) { let targetIndex; switch(e.key) { case 'ArrowLeft': e.preventDefault(); targetIndex = index > 0 ? index - 1 : tabButtons.length - 1; tabButtons[targetIndex].focus(); tabButtons[targetIndex].click(); break; case 'ArrowRight': e.preventDefault(); targetIndex = index < tabButtons.length - 1 ? index + 1 : 0; tabButtons[targetIndex].focus(); tabButtons[targetIndex].click(); break; case 'Home': e.preventDefault(); tabButtons[0].focus(); tabButtons[0].click(); break; case 'End': e.preventDefault(); tabButtons[tabButtons.length - 1].focus(); tabButtons[tabButtons.length - 1].click(); break; } }); }); } // Performance optimization: Lazy load images function initLazyLoading() { const images = document.querySelectorAll('img[data-src]'); const imageObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; img.classList.remove('lazy'); imageObserver.unobserve(img); } }); }); images.forEach(img => imageObserver.observe(img)); } // Initialize all functionality when DOM is loaded document.addEventListener('DOMContentLoaded', function() { initTheme(); fetchLatestVersion(); initCopyButtons(); initTabs(); initScrollAnimations(); initKeyboardNavigation(); initLazyLoading(); }); // Add some Easter eggs for developers console.log(` 🧠 OpenZIM MCP - Intelligent Knowledge Access for AI Models Thanks for checking out the console! If you're interested in contributing to OpenZIM MCP, check out our GitHub repository: https://github.com/cameronrye/openzim-mcp Built with ❤️ by the OpenZIM MCP Development Team `); // Enhanced error handling for theme changes function handleThemeChange() { const navbar = document.getElementById('navbar'); if (navbar && window.scrollY > 50) { // Re-trigger scroll effect to update navbar background window.dispatchEvent(new Event('scroll')); } } // Listen for theme changes document.addEventListener('DOMContentLoaded', function() { const observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.type === 'attributes' && mutation.attributeName === 'data-theme') { handleThemeChange(); } }); }); observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] }); }); // Add performance monitoring if ('performance' in window) { window.addEventListener('load', function() { setTimeout(() => { try { const perfData = performance.getEntriesByType('navigation')[0]; if (perfData) { console.log('Page load performance:', { 'DOM Content Loaded': Math.round(perfData.domContentLoadedEventEnd - perfData.domContentLoadedEventStart), 'Load Complete': Math.round(perfData.loadEventEnd - perfData.loadEventStart), 'Total Load Time': Math.round(perfData.loadEventEnd - perfData.fetchStart) }); } } catch (error) { console.log('Performance monitoring not available'); } }, 0); }); } // Add service worker registration for better caching (optional) if ('serviceWorker' in navigator) { window.addEventListener('load', function() { // Only register if we have a service worker file // This is commented out as we haven't created one yet // navigator.serviceWorker.register('/sw.js').catch(() => {}); }); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/cameronrye/openzim-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server