/* ======================================== MSPE Website - Main JavaScript Multiple Service Provider Experts ======================================== */ // API Base URL - adjust based on your hosting setup const API_BASE_URL = '/api'; /** * Escape HTML special characters to prevent XSS when inserting into innerHTML. * Use for ALL user/API data interpolated into template literals. */ function escapeHTML(str) { if (str == null) return ''; return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } /** * Escape a string for safe use inside an HTML attribute (e.g. href, src, data-*). * Rejects javascript: and data: URIs to prevent script injection via attributes. */ function escapeAttr(str) { if (str == null) return ''; const s = String(str).trim(); if (/^(javascript|data):/i.test(s)) return ''; return s .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } document.addEventListener('DOMContentLoaded', function() { // Initialize all modules initPremiumExperience(); initSiteLinkEnhancements(); optimizeImagesForCLS(); loadPublicSiteSettings(); initPreloader(); initNavigation(); initHeroSlider(); initCounters(); initTestimonials(); initBackToTop(); initWhatsAppButton(); initScrollAnimations(); initContactForm(); initNewsletterForm(); initFAQ(); // Load dynamic content from API loadNews(); loadTestimonials(); loadPortfolio(); loadServices(); loadTeam(); }); function initSiteLinkEnhancements() { // Fallback social URLs: only used for placeholder links (href="#" or "javascript:void(0)"). // Preferred approach: set correct URLs directly in HTML. These are a safety net. const socialProfiles = { facebook: 'https://www.facebook.com/mspe.pro', linkedin: 'https://www.linkedin.com/company/mspe-pro', twitter: 'https://x.com/mspe_pro', instagram: 'https://www.instagram.com/mspe.pro', whatsapp: 'https://wa.me/96178782023' }; const iconToNetwork = [ { selector: '.fa-facebook-f, .fa-facebook', network: 'facebook' }, { selector: '.fa-linkedin-in, .fa-linkedin', network: 'linkedin' }, { selector: '.fa-twitter, .fa-x-twitter', network: 'twitter' }, { selector: '.fa-instagram', network: 'instagram' }, { selector: '.fa-whatsapp', network: 'whatsapp' } ]; document.querySelectorAll('a[href="javascript:void(0)"], a[href="#"]').forEach(link => { const icon = link.querySelector('i'); const label = (link.getAttribute('aria-label') || link.getAttribute('title') || '').toLowerCase(); let network = ''; if (icon) { const iconMatch = iconToNetwork.find(item => icon.matches(item.selector)); if (iconMatch) network = iconMatch.network; } if (!network) { if (label.includes('facebook')) network = 'facebook'; if (label.includes('linkedin')) network = 'linkedin'; if (label.includes('twitter') || label.includes('x')) network = 'twitter'; if (label.includes('instagram')) network = 'instagram'; if (label.includes('whatsapp')) network = 'whatsapp'; } if (network && socialProfiles[network]) { link.href = socialProfiles[network]; link.target = '_blank'; link.rel = 'noopener noreferrer'; link.title = `Follow us on ${network.charAt(0).toUpperCase()}${network.slice(1)}`; } }); document.querySelectorAll('a').forEach(link => { const text = (link.textContent || '').trim().toLowerCase(); if (text === 'privacy policy') { link.href = 'privacy.html'; } if (text === 'terms of service' || text === 'terms') { link.href = 'terms.html'; } }); } function optimizeImagesForCLS() { const images = document.querySelectorAll('img'); if (!images.length) return; images.forEach(img => { if (!img.hasAttribute('loading')) { img.setAttribute('loading', 'lazy'); } if (!img.hasAttribute('decoding')) { img.setAttribute('decoding', 'async'); } if (img.classList.contains('logo-image') && !img.hasAttribute('width') && !img.hasAttribute('height')) { img.setAttribute('width', '200'); img.setAttribute('height', '50'); } const setNaturalDimensions = () => { if ((!img.hasAttribute('width') || !img.hasAttribute('height')) && img.naturalWidth && img.naturalHeight) { img.setAttribute('width', String(img.naturalWidth)); img.setAttribute('height', String(img.naturalHeight)); } }; if (img.complete) { setNaturalDimensions(); } else { img.addEventListener('load', setNaturalDimensions, { once: true }); } }); } async function loadPublicSiteSettings() { try { const response = await fetch(`${API_BASE_URL}/public-settings.php`); const result = await response.json(); if (!result || !result.success || !result.data) return; applyPublicSiteSettings(result.data); } catch (error) { console.log('Public settings unavailable'); } } function applyPublicSiteSettings(settings) { if (settings.contact_email) { document.querySelectorAll('a[href^="mailto:"]').forEach(link => { link.href = `mailto:${settings.contact_email}`; if (!link.textContent.trim() || link.textContent.includes('@')) { link.textContent = settings.contact_email; } }); document.querySelectorAll('[data-site-email]').forEach(el => { el.textContent = settings.contact_email; }); } if (settings.contact_phone) { const phoneDigits = settings.contact_phone.replace(/\s+/g, ''); document.querySelectorAll('a[href^="tel:"]').forEach(link => { link.href = `tel:${phoneDigits}`; }); document.querySelectorAll('[data-site-phone]').forEach(el => { el.textContent = settings.contact_phone; }); } if (settings.business_hours) { document.querySelectorAll('[data-business-hours]').forEach(el => { el.textContent = settings.business_hours; }); } // Site name & tagline if (settings.site_name) { document.querySelectorAll('[data-site-name]').forEach(el => { el.textContent = settings.site_name; }); // Update page title prefix if it uses site name if (document.title && !document.title.startsWith(settings.site_name)) { const sep = document.title.includes('|') ? ' | ' : ' - '; const suffix = document.title.split(/[|\-]/)[1]?.trim(); document.title = suffix ? `${settings.site_name}${sep}${suffix}` : settings.site_name; } } if (settings.site_tagline) { document.querySelectorAll('[data-site-tagline]').forEach(el => { el.textContent = settings.site_tagline; }); } // Social links — wire up any etc. const socialKeys = ['facebook', 'linkedin', 'twitter', 'instagram', 'youtube', 'github']; socialKeys.forEach(platform => { const url = settings[`social_${platform}`]; if (url) { document.querySelectorAll(`a[data-social="${platform}"]`).forEach(link => { link.href = url; link.removeAttribute('hidden'); link.style.display = ''; }); } }); // Brand colors via CSS custom properties const colorMap = { primary_color: '--primary-color', secondary_color: '--secondary-color', accent_color: '--accent-color' }; Object.entries(colorMap).forEach(([key, cssVar]) => { if (settings[key]) { document.documentElement.style.setProperty(cssVar, settings[key]); } }); } /* ==================== Premium Experience ==================== */ function initPremiumExperience() { document.body.classList.add('premium-ui'); setActiveNavLinkByPath(); initPremiumReveal(); initSmoothAnchorOffset(); initCardPointerLift(); } function setActiveNavLinkByPath() { const navLinks = document.querySelectorAll('.nav-link[href]'); if (!navLinks.length) return; const currentPath = window.location.pathname.split('/').pop() || 'index.html'; navLinks.forEach(link => { const href = link.getAttribute('href') || ''; if (href.startsWith('#') || href.includes('#')) return; const linkPath = href.split('/').pop(); if (linkPath === currentPath) { navLinks.forEach(item => item.classList.remove('active')); link.classList.add('active'); } }); } function initPremiumReveal() { const revealTargets = document.querySelectorAll( '.section-header, .service-card, .value-card, .team-card, .news-card, .portfolio-card, .contact-card, .feature-item, .process-step, .stat-item, .testimonial-card' ); if (!revealTargets.length || !('IntersectionObserver' in window)) return; revealTargets.forEach((el, index) => { el.classList.add('premium-reveal'); el.style.transitionDelay = `${Math.min(index % 6, 5) * 70}ms`; }); const observer = new IntersectionObserver((entries, currentObserver) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('is-visible'); currentObserver.unobserve(entry.target); } }); }, { threshold: 0.14, rootMargin: '0px 0px -40px 0px' }); revealTargets.forEach(el => observer.observe(el)); } function initSmoothAnchorOffset() { const anchorLinks = document.querySelectorAll('a[href^="#"]:not([href="#"])'); if (!anchorLinks.length) return; anchorLinks.forEach(link => { link.addEventListener('click', (event) => { const targetId = link.getAttribute('href'); const target = document.querySelector(targetId); if (!target) return; event.preventDefault(); const header = document.getElementById('header') || document.querySelector('.header'); const offset = header ? header.offsetHeight + 18 : 90; const top = target.getBoundingClientRect().top + window.scrollY - offset; window.scrollTo({ top, behavior: 'smooth' }); }); }); } function initCardPointerLift() { const cards = document.querySelectorAll('.service-card, .value-card, .team-card, .portfolio-card, .news-card, .contact-card'); if (!cards.length || window.innerWidth < 768 || 'ontouchstart' in window || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; cards.forEach(card => { card.addEventListener('pointermove', (event) => { const rect = card.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; const rotateY = ((x / rect.width) - 0.5) * 4; const rotateX = ((y / rect.height) - 0.5) * -4; card.style.transform = `translateY(-4px) rotateX(${rotateX.toFixed(2)}deg) rotateY(${rotateY.toFixed(2)}deg)`; }); card.addEventListener('pointerleave', () => { card.style.transform = ''; }); }); } /* ==================== Preloader ==================== */ function initPreloader() { // Support both preloader structures used across pages const preloader = document.getElementById('preloader') || document.querySelector('.preloader'); if (!preloader) return; window.addEventListener('load', () => { setTimeout(() => { preloader.classList.add('hidden'); preloader.style.opacity = '0'; preloader.style.visibility = 'hidden'; document.body.classList.remove('no-scroll'); // Remove preloader from DOM after animation setTimeout(() => { preloader.style.display = 'none'; }, 500); }, 500); }); } /* ==================== Navigation ==================== */ function initNavigation() { const header = document.getElementById('header') || document.querySelector('.header'); // Support both nav toggle button selectors used across pages const navToggle = document.getElementById('nav-toggle') || document.querySelector('.mobile-toggle') || document.querySelector('.nav-toggle'); const navMenu = document.getElementById('nav-menu') || document.querySelector('.nav-menu'); const navLinks = document.querySelectorAll('.nav-link'); if (!header) return; // Scroll effect - throttled for performance let scrollTicking = false; window.addEventListener('scroll', () => { if (!scrollTicking) { requestAnimationFrame(() => { if (window.scrollY > 100) { header.classList.add('scrolled'); } else { header.classList.remove('scrolled'); } scrollTicking = false; }); scrollTicking = true; } }, { passive: true }); // Mobile menu toggle if (navToggle && navMenu) { navToggle.addEventListener('click', () => { navMenu.classList.toggle('active'); document.body.classList.toggle('no-scroll'); // Animate hamburger + update aria-expanded navToggle.classList.toggle('active'); const isExpanded = navMenu.classList.contains('active'); navToggle.setAttribute('aria-expanded', String(isExpanded)); }); } // Close menu on link click navLinks.forEach(link => { link.addEventListener('click', () => { if (navMenu) navMenu.classList.remove('active'); document.body.classList.remove('no-scroll'); if (navToggle) navToggle.classList.remove('active'); }); }); // Close menu on outside click document.addEventListener('click', (e) => { if (navMenu && navToggle && !navMenu.contains(e.target) && !navToggle.contains(e.target)) { navMenu.classList.remove('active'); document.body.classList.remove('no-scroll'); if (navToggle) navToggle.classList.remove('active'); } }); // Handle dropdown menus const dropdowns = document.querySelectorAll('.nav-item.dropdown'); dropdowns.forEach(dropdown => { const link = dropdown.querySelector('.nav-link'); if (link && window.innerWidth <= 1024) { link.addEventListener('click', (e) => { if (window.innerWidth <= 1024) { e.preventDefault(); dropdown.classList.toggle('active'); } }); } }); } /* ==================== Hero Slider ==================== */ function initHeroSlider() { const slides = document.querySelectorAll('.hero-slide'); const dots = document.querySelectorAll('.dot'); const prevBtn = document.getElementById('hero-prev'); const nextBtn = document.getElementById('hero-next'); if (!slides.length) return; let currentSlide = 0; let slideInterval; function goToSlide(index) { slides[currentSlide].classList.remove('active'); dots[currentSlide].classList.remove('active'); currentSlide = index; if (currentSlide >= slides.length) currentSlide = 0; if (currentSlide < 0) currentSlide = slides.length - 1; slides[currentSlide].classList.add('active'); dots[currentSlide].classList.add('active'); } function nextSlide() { goToSlide(currentSlide + 1); } function prevSlide() { goToSlide(currentSlide - 1); } function startAutoSlide() { slideInterval = setInterval(nextSlide, 6000); } function stopAutoSlide() { clearInterval(slideInterval); } // Event listeners if (prevBtn) { prevBtn.addEventListener('click', () => { stopAutoSlide(); prevSlide(); startAutoSlide(); }); } if (nextBtn) { nextBtn.addEventListener('click', () => { stopAutoSlide(); nextSlide(); startAutoSlide(); }); } dots.forEach((dot, index) => { dot.addEventListener('click', () => { stopAutoSlide(); goToSlide(index); startAutoSlide(); }); }); // Start auto slide startAutoSlide(); } /* ==================== Counter Animation ==================== */ function initCounters() { const counters = document.querySelectorAll('.counter'); const observerOptions = { threshold: 0.5 }; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const counter = entry.target; const target = parseInt(counter.closest('.stat-item').dataset.count); animateCounter(counter, target); observer.unobserve(counter); } }); }, observerOptions); counters.forEach(counter => observer.observe(counter)); } function animateCounter(element, target) { let current = 0; const increment = target / 50; const duration = 2000; const stepTime = duration / 50; const timer = setInterval(() => { current += increment; if (current >= target) { element.textContent = target; clearInterval(timer); } else { element.textContent = Math.floor(current); } }, stepTime); } /* ==================== Testimonials Slider ==================== */ function initTestimonials() { const testimonials = document.querySelectorAll('.testimonial-card'); const prevBtn = document.querySelector('.testimonial-prev'); const nextBtn = document.querySelector('.testimonial-next'); const dotsContainer = document.getElementById('testimonial-dots'); if (!testimonials.length) return; let currentIndex = 0; // Create dots testimonials.forEach((_, index) => { const dot = document.createElement('span'); dot.classList.add('dot'); if (index === 0) dot.classList.add('active'); dot.addEventListener('click', () => goToTestimonial(index)); dotsContainer.appendChild(dot); }); const dots = dotsContainer.querySelectorAll('.dot'); function goToTestimonial(index) { testimonials[currentIndex].classList.remove('active'); dots[currentIndex].classList.remove('active'); currentIndex = index; if (currentIndex >= testimonials.length) currentIndex = 0; if (currentIndex < 0) currentIndex = testimonials.length - 1; testimonials[currentIndex].classList.add('active'); dots[currentIndex].classList.add('active'); } if (prevBtn) { prevBtn.addEventListener('click', () => goToTestimonial(currentIndex - 1)); } if (nextBtn) { nextBtn.addEventListener('click', () => goToTestimonial(currentIndex + 1)); } // Auto slide with pause on hover/focus for accessibility let testimonialPaused = false; const testimonialContainer = document.querySelector('.testimonials-slider, .testimonial-slider, .testimonials'); if (testimonialContainer) { testimonialContainer.addEventListener('mouseenter', () => { testimonialPaused = true; }); testimonialContainer.addEventListener('mouseleave', () => { testimonialPaused = false; }); testimonialContainer.addEventListener('focusin', () => { testimonialPaused = true; }); testimonialContainer.addEventListener('focusout', () => { testimonialPaused = false; }); } setInterval(() => { if (!testimonialPaused) goToTestimonial(currentIndex + 1); }, 8000); } /* ==================== Back to Top ==================== */ function initBackToTop() { // Support both selectors used across pages const backToTop = document.getElementById('back-to-top') || document.querySelector('.back-to-top'); if (!backToTop) return; let btTicking = false; window.addEventListener('scroll', () => { if (!btTicking) { requestAnimationFrame(() => { if (window.scrollY > 500) { backToTop.classList.add('visible'); backToTop.style.opacity = '1'; backToTop.style.visibility = 'visible'; } else { backToTop.classList.remove('visible'); backToTop.style.opacity = '0'; backToTop.style.visibility = 'hidden'; } btTicking = false; }); btTicking = true; } }, { passive: true }); backToTop.addEventListener('click', () => { window.scrollTo({ top: 0, behavior: 'smooth' }); }); } /* ==================== WhatsApp Float Button ==================== */ function initWhatsAppButton() { if (document.querySelector('.whatsapp-float')) return; const btn = document.createElement('a'); btn.href = 'https://wa.me/96178782023'; btn.className = 'whatsapp-float'; btn.target = '_blank'; btn.rel = 'noopener noreferrer'; btn.setAttribute('aria-label', 'Chat with us on WhatsApp'); btn.setAttribute('title', 'Chat with us on WhatsApp'); btn.innerHTML = ''; document.body.appendChild(btn); } /* ==================== Scroll Animations ==================== */ function initScrollAnimations() { const elements = document.querySelectorAll('[data-aos]'); const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const delay = entry.target.dataset.aosDelay || 0; setTimeout(() => { entry.target.classList.add('aos-animate'); }, delay); } }); }, { threshold: 0.1 }); elements.forEach(el => observer.observe(el)); } /* ==================== Form Handling ==================== */ function initContactForm() { const form = document.getElementById('contact-form'); if (!form) return; // Check if form already has a submit handler from page-specific script if (form.dataset.initialized) return; form.dataset.initialized = 'true'; form.addEventListener('submit', async (e) => { e.preventDefault(); const formData = new FormData(form); const data = Object.fromEntries(formData); // Handle checkbox for newsletter data.newsletter = formData.get('newsletter') === 'yes'; const submitBtn = form.querySelector('button[type="submit"]'); const formMessage = document.getElementById('form-message'); const originalText = submitBtn.innerHTML; submitBtn.innerHTML = ' Sending...'; submitBtn.disabled = true; try { const response = await fetch(`${API_BASE_URL}/contact.php`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); const result = await response.json(); if (result.success) { showNotification(result.message || 'Message sent successfully!', 'success'); if (formMessage) { formMessage.className = 'form-message success'; formMessage.innerHTML = ' ' + escapeHTML(result.message || 'Thank you! Your message has been sent successfully.'); formMessage.style.display = 'block'; } form.reset(); } else { showNotification(result.message || 'Failed to send message. Please try again.', 'error'); if (formMessage) { formMessage.className = 'form-message error'; formMessage.innerHTML = ' ' + escapeHTML(result.message || 'Failed to send message.'); formMessage.style.display = 'block'; } } } catch (error) { console.error('Contact form error:', error); showNotification('An error occurred. Please try again later.', 'error'); if (formMessage) { formMessage.className = 'form-message error'; formMessage.innerHTML = ' An error occurred. Please try again or email us directly.'; formMessage.style.display = 'block'; } } finally { submitBtn.innerHTML = originalText; submitBtn.disabled = false; // Hide message after 5 seconds if (formMessage) { setTimeout(() => { formMessage.style.display = 'none'; }, 5000); } } }); } /* ==================== Notification ==================== */ function showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.innerHTML = ` ${escapeHTML(message)} `; document.body.appendChild(notification); setTimeout(() => { notification.classList.add('show'); }, 100); setTimeout(() => { notification.classList.remove('show'); setTimeout(() => { notification.remove(); }, 300); }, 5000); } /* ==================== Load News from API ==================== */ async function loadNews(limit = 3) { const newsGrid = document.getElementById('news-grid'); const newsSection = document.getElementById('news-section'); const comingSoonSection = document.getElementById('coming-soon-section'); // Only proceed if news grid exists if (!newsGrid) return; try { const response = await fetch(`${API_BASE_URL}/news.php?limit=${limit}&status=published`); const result = await response.json(); // Handle both response formats const news = result.data || result; if (news && news.length > 0) { // Show news, hide coming soon if (newsSection) newsSection.style.display = 'block'; if (comingSoonSection) comingSoonSection.style.display = 'none'; newsGrid.innerHTML = news.map(item => { const date = new Date(item.created_at); const categoryLabels = { 'news': 'Company News', 'events': 'Events', 'insights': 'Industry Insights', 'updates': 'Product Updates' }; return `
${date.getDate()} ${date.toLocaleString('default', { month: 'short' })}
${escapeHTML(categoryLabels[item.category] || item.category || 'News')}

${escapeHTML(item.title)}

${escapeHTML(item.excerpt || (item.content ? item.content.substring(0, 120) + '...' : 'Read more about this topic.'))}

Read More
`}).join(''); } else { // No news found - show coming soon if (newsSection) newsSection.style.display = 'none'; if (comingSoonSection) comingSoonSection.style.display = 'block'; // Clear spinner on pages that have no coming-soon section (e.g. homepage) if (!newsSection) { newsGrid.innerHTML = ''; const parentSection = newsGrid.closest('section'); if (parentSection) parentSection.style.display = 'none'; } } } catch (error) { console.log('Error loading news:', error); // Fallback to showing coming soon on error if (newsSection) newsSection.style.display = 'none'; if (comingSoonSection) comingSoonSection.style.display = 'block'; if (!newsSection) { newsGrid.innerHTML = ''; const parentSection = newsGrid.closest('section'); if (parentSection) parentSection.style.display = 'none'; } } } /* ==================== Load Testimonials from API ==================== */ async function loadTestimonials() { const slider = document.getElementById('testimonials-slider'); if (!slider) return; try { const response = await fetch(`${API_BASE_URL}/testimonials.php?status=published`); const result = await response.json(); const testimonials = result.data || result; if (testimonials && testimonials.length > 0) { slider.innerHTML = testimonials.map((item, index) => { const name = item.name || 'Anonymous'; const initials = name.split(' ').map(n => n[0]).join(''); const text = item.text || item.content || ''; return `

"${escapeHTML(text)}"

${item.photo ? `${escapeAttr(name)}` : `
${escapeHTML(initials)}
` }

${escapeHTML(name)}

${escapeHTML(item.position || '')}${item.position && item.company ? ', ' : ''}${escapeHTML(item.company || '')}

`}).join(''); // Reinitialize testimonials slider initTestimonials(); } } catch (error) { console.log('Using static testimonials content or API unavailable'); } } /* ==================== Load Portfolio from API ==================== */ async function loadPortfolio(limit = 6) { const portfolioGrid = document.getElementById('portfolio-grid'); const portfolioSection = document.getElementById('portfolio-section'); const comingSoonSection = document.getElementById('coming-soon-section'); // Only proceed if we're on the portfolio page (or homepage if it has portfolio section) if (!portfolioGrid) return; try { const response = await fetch(`${API_BASE_URL}/portfolio.php?limit=${limit}`); const result = await response.json(); const projects = result.data || result; if (projects && projects.length > 0) { // Show portfolio, hide coming soon if (portfolioSection) portfolioSection.style.display = 'block'; if (comingSoonSection) comingSoonSection.style.display = 'none'; portfolioGrid.innerHTML = projects.map(item => { const categoryLabels = { 'it-support': 'IT Support', 'cybersecurity': 'Cybersecurity', 'cloud': 'Cloud Services', 'consulting': 'Consulting' }; return `
${escapeAttr(item.title)}
${escapeHTML(categoryLabels[item.category] || item.category || 'Project')}

${escapeHTML(item.title)}

${item.client ? `

Client: ${escapeHTML(item.client)}

` : ''} View Details
`}).join(''); } else { // No projects found - show coming soon if (portfolioSection) portfolioSection.style.display = 'none'; if (comingSoonSection) comingSoonSection.style.display = 'block'; } } catch (error) { console.log('Error loading portfolio:', error); // Fallback to showing coming soon on error if (portfolioSection) portfolioSection.style.display = 'none'; if (comingSoonSection) comingSoonSection.style.display = 'block'; } } /* ==================== Load Services from API ==================== */ async function loadServices() { const servicesGrids = document.querySelectorAll('[data-services-grid]'); if (!servicesGrids.length) return; try { const response = await fetch(`${API_BASE_URL}/services.php`); const result = await response.json(); const services = result.data || result; if (services && services.length > 0) { const servicesMarkup = services.map((service, index) => { const icon = service.icon || 'fa-cogs'; const features = (service.features || '') .split(/\r?\n/) .map(item => item.trim()) .filter(Boolean) .slice(0, 3); const slug = (service.slug || service.name || 'service') .toString() .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/(^-|-$)/g, ''); const ctaText = service.cta_text || 'Learn More'; return `

${escapeHTML(service.name || 'Service')}

${escapeHTML(service.description || '')}

${features.length ? ` ` : ''} ${escapeHTML(ctaText)}
`; }).join(''); servicesGrids.forEach(grid => { grid.innerHTML = servicesMarkup; }); } else { // No API data returned — static fallback cards already rendered in HTML } } catch (error) { // Static fallback cards already rendered in HTML — do not hide the section console.log('Using static services content or API unavailable'); } } /* ==================== Load Team from API ==================== */ async function loadTeam() { const teamGrid = document.querySelector('[data-team-grid]'); if (!teamGrid) return; try { const response = await fetch(`${API_BASE_URL}/team.php`); const result = await response.json(); const members = result.data || result; if (members && members.length > 0) { teamGrid.innerHTML = members.map(member => { let photo = member.photo || `https://ui-avatars.com/api/?name=${encodeURIComponent(member.name || 'Team')}&background=0ea5e9&color=fff`; // Ensure uploaded photos have a leading slash for absolute path if (photo && !photo.startsWith('http') && !photo.startsWith('/')) { photo = '/' + photo; } const bioHtml = member.bio ? `${escapeHTML(member.bio)}` : ''; return `
${escapeAttr(member.name || 'Team Member')}

${escapeHTML(member.name || 'Team Member')}

${escapeHTML(member.role || '')}

${bioHtml}
`; }).join(''); } } catch (error) { console.log('Using static team content or API unavailable'); } } /* ==================== Newsletter Form ==================== */ function initNewsletterForm() { const newsletterForms = document.querySelectorAll('#newsletter-form, .newsletter-form'); newsletterForms.forEach(form => { if (form.dataset.initialized) return; form.dataset.initialized = 'true'; form.addEventListener('submit', async (e) => { e.preventDefault(); const emailInput = form.querySelector('input[type="email"]'); const submitBtn = form.querySelector('button[type="submit"]'); const email = emailInput.value; if (!email) return; const originalText = submitBtn.innerHTML; submitBtn.innerHTML = ''; submitBtn.disabled = true; try { // Subscribe via dedicated subscribe API const response = await fetch(`${API_BASE_URL}/subscribe.php`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: email, name: '', source: 'newsletter_form' }) }); const result = await response.json(); if (result.success) { showNotification('Thank you for subscribing!', 'success'); form.reset(); } else { showNotification(result.message || 'Subscription failed. Please try again.', 'error'); } } catch (error) { console.error('Newsletter error:', error); showNotification('An error occurred. Please try again.', 'error'); } finally { submitBtn.innerHTML = originalText; submitBtn.disabled = false; } }); }); } // Initialize dynamic content on first DOMContentLoaded (no duplicate listener needed) /* ==================== FAQ Accordion ==================== */ function initFAQ() { const faqItems = document.querySelectorAll('.faq-item'); if (!faqItems.length) return; faqItems.forEach(item => { const question = item.querySelector('.faq-question'); if (!question) return; question.addEventListener('click', () => { const isActive = item.classList.contains('active'); // Close all open items faqItems.forEach(i => { i.classList.remove('active'); const icon = i.querySelector('.faq-toggle i'); if (icon) icon.className = 'fas fa-plus'; }); // Open clicked item if it was closed if (!isActive) { item.classList.add('active'); const icon = item.querySelector('.faq-toggle i'); if (icon) icon.className = 'fas fa-minus'; } }); }); }