Spaces:
Running
Running
| // AnkleCare - Interactive functionality for chronic ankle instability information | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Initialize feather icons | |
| feather.replace(); | |
| // Smooth scrolling for navigation links | |
| initSmoothScrolling(); | |
| // Intersection Observer for animations | |
| initScrollAnimations(); | |
| // Progress bar animations | |
| initProgressBars(); | |
| // Mobile menu functionality | |
| initMobileMenu(); | |
| // Statistics counter animation | |
| initCounters(); | |
| // Paper cards interaction | |
| initPaperCards(); | |
| // Form handling if present | |
| initForms(); | |
| }); | |
| // Smooth scrolling for navigation | |
| function initSmoothScrolling() { | |
| const navLinks = document.querySelectorAll('a[href^="#"]'); | |
| navLinks.forEach(link => { | |
| link.addEventListener('click', function(e) { | |
| e.preventDefault(); | |
| const targetId = this.getAttribute('href').substring(1); | |
| const targetElement = document.getElementById(targetId); | |
| if (targetElement) { | |
| const offsetTop = targetElement.offsetTop - 80; // Account for fixed nav | |
| window.scrollTo({ | |
| top: offsetTop, | |
| behavior: 'smooth' | |
| }); | |
| // Close mobile menu if open | |
| closeMobileMenu(); | |
| } | |
| }); | |
| }); | |
| } | |
| // Scroll animations using Intersection Observer | |
| function initScrollAnimations() { | |
| const animatedElements = document.querySelectorAll('.card, .paper-card, .stat-card'); | |
| const observer = new IntersectionObserver((entries) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| entry.target.classList.add('animate-fade-in'); | |
| observer.unobserve(entry.target); | |
| } | |
| }); | |
| }, { | |
| threshold: 0.1, | |
| rootMargin: '0px 0px -50px 0px' | |
| }); | |
| animatedElements.forEach(element => { | |
| observer.observe(element); | |
| }); | |
| } | |
| // Animate progress bars when in view | |
| function initProgressBars() { | |
| const progressBars = document.querySelectorAll('.progress-fill'); | |
| const progressObserver = new IntersectionObserver((entries) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| const progressBar = entry.target; | |
| const width = progressBar.style.width || progressBar.getAttribute('style').match(/width:\s*(\d+%)/)[1]; | |
| setTimeout(() => { | |
| progressBar.style.width = width; | |
| }, 200); | |
| progressObserver.unobserve(progressBar); | |
| } | |
| }); | |
| }, { threshold: 0.5 }); | |
| progressBars.forEach(bar => { | |
| bar.style.width = '0%'; | |
| progressObserver.observe(bar); | |
| }); | |
| } | |
| // Mobile menu functionality | |
| function initMobileMenu() { | |
| const mobileMenuButton = document.querySelector('[data-feather="menu"]'); | |
| const nav = document.querySelector('nav'); | |
| if (mobileMenuButton) { | |
| mobileMenuButton.addEventListener('click', function() { | |
| const navMenu = document.querySelector('.nav-menu'); | |
| if (navMenu) { | |
| navMenu.classList.toggle('mobile-open'); | |
| } else { | |
| // Create mobile menu if it doesn't exist | |
| createMobileMenu(); | |
| } | |
| }); | |
| } | |
| } | |
| function createMobileMenu() { | |
| const nav = document.querySelector('nav .flex'); | |
| const mobileMenu = document.createElement('div'); | |
| mobileMenu.className = 'mobile-menu fixed top-16 left-0 right-0 bg-white shadow-lg z-40 transform -translate-y-full opacity-0 transition-all duration-300'; | |
| const mobileMenuContent = document.createElement('div'); | |
| mobileMenuContent.className = 'p-4 space-y-4'; | |
| mobileMenuContent.innerHTML = ` | |
| <a href="#overview" class="block py-2 text-slate-600 hover:text-blue-600">Overview</a> | |
| <a href="#causes" class="block py-2 text-slate-600 hover:text-blue-600">Causes</a> | |
| <a href="#symptoms" class="block py-2 text-slate-600 hover:text-blue-600">Symptoms</a> | |
| <a href="#treatment" class="block py-2 text-slate-600 hover:text-blue-600">Treatment</a> | |
| <a href="#research" class="block py-2 text-slate-600 hover:text-blue-600">Research</a> | |
| `; | |
| mobileMenu.appendChild(mobileMenuContent); | |
| document.body.appendChild(mobileMenu); | |
| // Add event listeners for mobile menu links | |
| mobileMenuContent.querySelectorAll('a').forEach(link => { | |
| link.addEventListener('click', function() { | |
| closeMobileMenu(); | |
| }); | |
| }); | |
| // Store reference for toggling | |
| window.mobileMenu = mobileMenu; | |
| } | |
| function closeMobileMenu() { | |
| if (window.mobileMenu) { | |
| window.mobileMenu.style.transform = 'translateY(-100%)'; | |
| window.mobileMenu.style.opacity = '0'; | |
| } | |
| } | |
| // Animated counters for statistics | |
| function initCounters() { | |
| const counters = document.querySelectorAll('.stat-number'); | |
| const counterObserver = new IntersectionObserver((entries) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| animateCounter(entry.target); | |
| counterObserver.unobserve(entry.target); | |
| } | |
| }); | |
| }, { threshold: 0.5 }); | |
| counters.forEach(counter => { | |
| counterObserver.observe(counter); | |
| }); | |
| } | |
| function animateCounter(element) { | |
| const target = element.textContent; | |
| const numericValue = parseInt(target.replace(/\D/g, '')); | |
| const suffix = target.replace(/[\d%]/g, ''); | |
| if (isNaN(numericValue)) return; | |
| let current = 0; | |
| const increment = numericValue / 50; | |
| const duration = 2000; | |
| const stepTime = duration / 50; | |
| const timer = setInterval(() => { | |
| current += increment; | |
| if (current >= numericValue) { | |
| current = numericValue; | |
| clearInterval(timer); | |
| } | |
| element.textContent = Math.floor(current) + suffix; | |
| }, stepTime); | |
| } | |
| // Interactive paper cards | |
| function initPaperCards() { | |
| const paperCards = document.querySelectorAll('.paper-card'); | |
| paperCards.forEach(card => { | |
| card.addEventListener('click', function() { | |
| // Add click animation | |
| this.style.transform = 'scale(0.98)'; | |
| setTimeout(() => { | |
| this.style.transform = ''; | |
| }, 150); | |
| // You could expand this to show paper details | |
| showPaperDetails(this); | |
| }); | |
| // Add hover effects | |
| card.addEventListener('mouseenter', function() { | |
| this.style.borderLeftColor = '#3b82f6'; | |
| }); | |
| card.addEventListener('mouseleave', function() { | |
| this.style.borderLeftColor = '#3b82f6'; | |
| }); | |
| }); | |
| } | |
| function showPaperDetails(card) { | |
| // Simple alert for now - could be expanded to modal | |
| const title = card.querySelector('.paper-title')?.textContent || 'Research Paper'; | |
| alert(`Paper details for: ${title}\n\nThis would typically show:\n- Abstract\n- Key findings\n- Methodology\n- Full citation\n- Download options`); | |
| } | |
| // Form handling | |
| function initForms() { | |
| const forms = document.querySelectorAll('form'); | |
| forms.forEach(form => { | |
| form.addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| // Add loading state | |
| const submitButton = this.querySelector('button[type="submit"]'); | |
| const originalText = submitButton.textContent; | |
| submitButton.innerHTML = '<span class="loading"></span> Submitting...'; | |
| submitButton.disabled = true; | |
| // Simulate form submission | |
| setTimeout(() => { | |
| submitButton.textContent = 'Submitted!'; | |
| submitButton.style.background = '#10b981'; | |
| setTimeout(() => { | |
| submitButton.textContent = originalText; | |
| submitButton.style.background = ''; | |
| submitButton.disabled = false; | |
| }, 2000); | |
| }, 1500); | |
| }); | |
| }); | |
| } | |
| // Utility functions | |
| function debounce(func, wait) { | |
| let timeout; | |
| return function executedFunction(...args) { | |
| const later = () => { | |
| clearTimeout(timeout); | |
| func(...args); | |
| }; | |
| clearTimeout(timeout); | |
| timeout = setTimeout(later, wait); | |
| }; | |
| } | |
| function throttle(func, limit) { | |
| let inThrottle; | |
| return function() { | |
| const args = arguments; | |
| const context = this; | |
| if (!inThrottle) { | |
| func.apply(context, args); | |
| inThrottle = true; | |
| setTimeout(() => inThrottle = false, limit); | |
| } | |
| } | |
| } | |
| // Scroll-based navigation changes | |
| window.addEventListener('scroll', throttle(function() { | |
| const nav = document.querySelector('nav'); | |
| if (window.scrollY > 50) { | |
| nav.classList.add('scrolled'); | |
| } else { | |
| nav.classList.remove('scrolled'); | |
| } | |
| }, 100)); | |
| // Keyboard navigation support | |
| document.addEventListener('keydown', function(e) { | |
| // ESC key to close mobile menu | |
| if (e.key === 'Escape') { | |
| closeMobileMenu(); | |
| } | |
| // Arrow key navigation for sections | |
| if (e.altKey) { | |
| const sections = ['overview', 'causes', 'symptoms', 'treatment', 'research']; | |
| const currentSection = getCurrentSection(); | |
| const currentIndex = sections.indexOf(currentSection); | |
| if (e.key === 'ArrowDown' && currentIndex < sections.length - 1) { | |
| e.preventDefault(); | |
| document.getElementById(sections[currentIndex + 1]).scrollIntoView({ behavior: 'smooth' }); | |
| } else if (e.key === 'ArrowUp' && currentIndex > 0) { | |
| e.preventDefault(); | |
| document.getElementById(sections[currentIndex - 1]).scrollIntoView({ behavior: 'smooth' }); | |
| } | |
| } | |
| }); | |
| function getCurrentSection() { | |
| const sections = document.querySelectorAll('section[id]'); | |
| const scrollPosition = window.scrollY + 100; | |
| for (let section of sections) { | |
| if (scrollPosition >= section.offsetTop && scrollPosition < section.offsetTop + section.offsetHeight) { | |
| return section.id; | |
| } | |
| } | |
| return 'overview'; | |
| } | |
| // Performance monitoring | |
| if ('performance' in window) { | |
| window.addEventListener('load', function() { | |
| setTimeout(function() { | |
| const perfData = performance.getEntriesByType('navigation')[0]; | |
| if (perfData.loadEventEnd > 3000) { | |
| console.warn('Page load time exceeds 3 seconds:', perfData.loadEventEnd + 'ms'); | |
| } | |
| }, 0); | |
| }); | |
| } | |
| // Error handling | |
| window.addEventListener('error', function(e) { | |
| console.error('JavaScript error:', e.error); | |
| // In production, you might want to send this to an error tracking service | |
| }); | |
| // Export functions for testing (if needed) | |
| if (typeof module !== 'undefined' && module.exports) { | |
| module.exports = { | |
| initSmoothScrolling, | |
| initScrollAnimations, | |
| animateCounter, | |
| debounce, | |
| throttle | |
| }; | |
| } |