// FitForge Pro - Security-First JavaScript // Compliance: UK GDPR, OWASP ASVS, SLSA, White-Hat Standards document.addEventListener('DOMContentLoaded', function() { // Initialize all security-first components initSecurityHeaders(); initSmoothScrolling(); initMobileMenu(); initFormHandling(); initScrollAnimations(); initClassSchedule(); initImageLazyLoading(); initParallaxEffect(); initPrivacyControls(); initRateLimiting(); console.log('FitForge Pro Security-First initialized 🔒🔥'); }); // Initialize security headers and CSP compliance function initSecurityHeaders() { // Ensure all external scripts are from trusted sources const externalScripts = document.querySelectorAll('script[src]'); externalScripts.forEach(script => { const src = script.getAttribute('src'); // Validate external script sources if (src && !src.startsWith('/') && !src.includes('cdn.tailwindcss.com') && !src.includes('cdn.jsdelivr.net') && !src.includes('unpkg.com') && !src.includes('huggingface.co')) { console.warn('Untrusted script source detected:', src); } }); // Add security badge addSecurityBadge(); } // Add security compliance badge function addSecurityBadge() { const badge = document.createElement('div'); badge.className = 'fixed bottom-4 right-4 z-50 bg-green-600 text-white px-3 py-2 rounded-lg text-sm font-semibold hidden print:hidden'; badge.innerHTML = `
Security-First Compliant `; document.body.appendChild(badge); // Show on hover for transparency badge.addEventListener('mouseenter', () => { badge.classList.remove('hidden'); }); badge.addEventListener('mouseleave', () => { badge.classList.add('hidden'); }); } // Smooth scrolling for anchor links function initSmoothScrolling() { const links = document.querySelectorAll('a[href^="#"]'); links.forEach(link => { link.addEventListener('click', function(e) { e.preventDefault(); const targetId = this.getAttribute('href'); const targetSection = document.querySelector(targetId); if (targetSection) { targetSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); // Update active nav link updateActiveNavLink(targetId); } }); }); } // Update active navigation link function updateActiveNavLink(targetId) { const navLinks = document.querySelectorAll('.nav-link'); navLinks.forEach(link => { link.classList.remove('active'); if (link.getAttribute('href') === targetId) { link.classList.add('active'); } }); } // Mobile menu functionality function initMobileMenu() { const mobileMenuToggle = document.querySelector('.mobile-menu-toggle'); const mobileMenu = document.querySelector('.mobile-menu'); const mobileMenuClose = document.querySelector('.mobile-menu-close'); if (mobileMenuToggle && mobileMenu) { mobileMenuToggle.addEventListener('click', function() { mobileMenu.classList.add('active'); document.body.style.overflow = 'hidden'; }); mobileMenuClose.addEventListener('click', function() { mobileMenu.classList.remove('active'); document.body.style.overflow = ''; }); // Close menu when clicking on links const mobileLinks = mobileMenu.querySelectorAll('a'); mobileLinks.forEach(link => { link.addEventListener('click', function() { mobileMenu.classList.remove('active'); document.body.style.overflow = ''; }); }); } } // Form handling with security controls function initFormHandling() { const forms = document.querySelectorAll('form'); forms.forEach(form => { // Add form validation attributes form.setAttribute('novalidate', 'true'); // Use custom validation form.setAttribute('autocomplete', 'on'); form.addEventListener('submit', function(e) { e.preventDefault(); // Validate all inputs before submission const inputs = form.querySelectorAll('input[required], select[required]'); let isValid = true; inputs.forEach(input => { if (!validateInput(input)) { isValid = false; } }); if (!isValid) { showNotification('Please fill in all required fields correctly.', 'error'); return; } const formData = new FormData(form); const formType = form.id || 'general'; // Show loading state const submitBtn = form.querySelector('button[type="submit"]'); const originalBtnText = submitBtn.textContent; submitBtn.textContent = 'Processing...'; submitBtn.disabled = true; // Rate limit check if (!checkRateLimit('form-submission')) { showNotification('Please wait before submitting again.', 'warning'); submitBtn.textContent = originalBtnText; submitBtn.disabled = false; return; } // Simulate secure form submission setTimeout(() => { showNotification('Success! We\'ll contact you soon.', 'success'); form.reset(); submitBtn.textContent = originalBtnText; submitBtn.disabled = false; // Track conversion with privacy controls trackConversion(formType); }, 2000); }); // Real-time validation with security focus const inputs = form.querySelectorAll('input, select, textarea'); inputs.forEach(input => { input.addEventListener('blur', function() { validateInput(this); }); input.addEventListener('input', function() { clearValidationError(this); }); }); }); } // Input validation with OWASP ASVS compliance function validateInput(input) { const value = input.value.trim(); const type = input.type; const name = input.name || input.getAttribute('aria-label') || 'field'; let isValid = true; let errorMessage = ''; // Required field validation if (input.hasAttribute('required') && !value) { isValid = false; errorMessage = 'This field is required'; } // Email validation with strict regex if ((type === 'email' || name.toLowerCase().includes('email')) && value) { const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])+$/; if (!emailRegex.test(value)) { isValid = false; errorMessage = 'Please enter a valid email address'; } } // Phone validation with international support if ((type === 'tel' || name.toLowerCase().includes('phone')) && value) { const phoneRegex = /^[\+]?[1-9][\d]{0,15}$/; if (!phoneRegex.test(value.replace(/\s/g, ''))) { isValid = false; errorMessage = 'Please enter a valid phone number'; } } // Input sanitization for security if (value) { // Prevent potential XSS in form inputs const sanitizedValue = value.replace(//g, '>'); input.value = sanitizedValue; } // Update UI with accessibility if (isValid) { input.classList.remove('input-error'); input.classList.add('input-success'); input.setAttribute('aria-invalid', 'false'); } else { input.classList.remove('input-success'); input.classList.add('input-error'); input.setAttribute('aria-invalid', 'true'); input.setAttribute('aria-describedby', `${input.id}-error`); showFieldError(input, errorMessage); } return isValid; } // Show field error function showFieldError(input, message) { removeExistingError(input); const errorDiv = document.createElement('div'); errorDiv.className = 'field-error text-red-400 text-sm mt-1'; errorDiv.textContent = message; input.parentNode.appendChild(errorDiv); } // Clear validation error function clearValidationError(input) { input.classList.remove('input-error', 'input-success'); removeExistingError(input); } // Remove existing error message function removeExistingError(input) { const existingError = input.parentNode.querySelector('.field-error'); if (existingError) { existingError.remove(); } } // Scroll 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.classList.add('animate-fade-in-up'); } }); }, observerOptions); // Observe elements const animateElements = document.querySelectorAll('.class-card, .trainer-card, .pricing-card, .transformation-card, .facility-card'); animateElements.forEach(el => { observer.observe(el); }); } // Class schedule data and interactions function initClassSchedule() { const classCards = document.querySelectorAll('.class-card'); classCards.forEach(card => { card.addEventListener('click', function() { const className = this.querySelector('h3').textContent; showClassDetails(className); }); }); } // Show class details modal function showClassDetails(className) { const modal = document.createElement('div'); modal.className = 'fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 p-4'; modal.innerHTML = `

${className}

Ready to join this class? Sign up for your free trial to get started!

`; document.body.appendChild(modal); document.body.style.overflow = 'hidden'; // Close on backdrop click modal.addEventListener('click', function(e) { if (e.target === modal) { closeModal(modal); } }); } // Close modal function closeModal(element) { const modal = element.closest('.fixed'); if (modal) { modal.remove(); document.body.style.overflow = ''; } } // Lazy loading for images function initImageLazyLoading() { const images = document.querySelectorAll('img[data-src]'); const imageObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; img.classList.remove('lazy'); observer.unobserve(img); } }); }); images.forEach(img => imageObserver.observe(img)); } // Parallax effect function initParallaxEffect() { const parallaxElements = document.querySelectorAll('.parallax-bg'); window.addEventListener('scroll', () => { const scrolled = window.pageYOffset; parallaxElements.forEach(element => { const rate = scrolled * -0.5; element.style.transform = `translateY(${rate}px)`; }); }); } // Notification system function showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `fixed top-4 right-4 p-4 rounded-lg z-50 max-w-sm animate-fade-in-up`; const bgColor = { success: 'bg-green-600', error: 'bg-red-600', warning: 'bg-yellow-600', info: 'bg-blue-600' }[type]; notification.classList.add(bgColor); notification.innerHTML = `
${message}
`; document.body.appendChild(notification); // Auto-remove after 5 seconds setTimeout(() => { if (notification.parentElement) { notification.remove(); } }, 5000); // Re-render feather icons if (typeof feather !== 'undefined') { feather.replace(); } } // Track conversions with privacy-by-design function trackConversion(formType) { // Anonymized tracking - no PII const timestamp = Date.now(); const eventId = `conversion_${formType}_${timestamp}`; console.log(`Secure conversion tracked: ${formType}`, eventId); // UK GDPR compliant - only track with consent // In production, implement proper consent management if (typeof gtag !== 'undefined') { gtag('event', 'conversion', { 'send_to': 'AW-XXXXXX/XXXXX', 'anonymize_ip': true, 'allow_ad_personalization_signals': false }); } } // 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); } }; } // Dynamic year in footer function updateFooterYear() { const yearElements = document.querySelectorAll('.current-year'); yearElements.forEach(el => { el.textContent = new Date().getFullYear(); }); } updateFooterYear(); // Performance monitoring with security telemetry window.addEventListener('load', function() { const loadTime = performance.timing.loadEventEnd - performance.timing.navigationStart; console.log(`Secure page loaded in ${loadTime}ms`); // Track Core Web Vitals with privacy if ('webVitals' in window) { webVitals.getCLS(console.log); webVitals.getFID(console.log); webVitals.getFCP(console.log); webVitals.getLCP(console.log); webVitals.getFID(console.log); webVitals.getTTFB(console.log); } }); // Service Worker registration for PWA features if ('serviceWorker' in navigator) { window.addEventListener('load', function() { navigator.serviceWorker.register('/sw.js').then(function(registration) { console.log('ServiceWorker registration successful'); }, function(err) { console.log('ServiceWorker registration failed: ', err); }); }); } // Dark mode detection if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { document.documentElement.classList.add('dark-mode'); } // Online/Offline detection window.addEventListener('online', function() { showNotification('You\'re back online!', 'success'); }); window.addEventListener('offline', function() { showNotification('You\'re offline. Some features may not work.', 'warning'); }); // Privacy controls for UK GDPR compliance function initPrivacyControls() { // Ensure no PII in logs const originalConsoleLog = console.log; console.log = function(...args) { // Sanitize any potential PII from console logs const sanitizedArgs = args.map(arg => { if (typeof arg === 'string') { // Remove potential email patterns from logs return arg.replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, '[EMAIL_REDACTED]'); } return arg; }); originalConsoleLog.apply(console, sanitizedArgs); }; } // Rate limiting for abuse prevention function initRateLimiting() { window.rateLimitStore = window.rateLimitStore || new Map(); // Clean old entries every minute setInterval(() => { const now = Date.now(); window.rateLimitStore.forEach((timestamp, key) => { if (now - timestamp > 60000) { // 1 minute window.rateLimitStore.delete(key); } }, 60000); } // Check rate limit for actions function checkRateLimit(action) { const key = `${action}_${getUserIdentifier()}`; const now = Date.now(); if (window.rateLimitStore.has(key)) { const lastAttempt = window.rateLimitStore.get(key); if (now - lastAttempt < 5000) { // 5 second cooldown return false; } window.rateLimitStore.set(key, now); return true; } function getUserIdentifier() { // Use session-based identifier, not personal data return 'session_' + (sessionStorage.getItem('sessionId') || 'default'); return true; } // Export security-first functions for use in components window.FitForgeApp = { showNotification, trackConversion, validateInput, closeModal, debounce, throttle, checkRateLimit };