// Portfolio filter functionality let portfolioItems = []; let portfolioFilters = []; let currentFilter = 'all'; // Named functions for event listeners function handlePortfolioFilter(e) { e.preventDefault(); const filter = e.target.getAttribute('data-filter'); if (filter && filter !== currentFilter) { currentFilter = filter; // Update filter buttons portfolioFilters.forEach(btn => { btn.classList.remove('active', 'bg-[var(--primary-color)]', 'text-white'); btn.classList.add('bg-[var(--secondary-button-bg-color)]', 'text-[var(--secondary-button-text-color)]'); }); e.target.classList.add('active', 'bg-[var(--primary-color)]', 'text-white'); e.target.classList.remove('bg-[var(--secondary-button-bg-color)]', 'text-[var(--secondary-button-text-color)]'); // Filter items portfolioItems.forEach(item => { const category = item.getAttribute('data-category'); if (filter === 'all' || category === filter) { item.style.display = 'block'; item.style.animation = 'fadeInUp 0.5s ease forwards'; } else { item.style.display = 'none'; } }); } } // Enhanced smooth scroll for anchor links function handleSmoothScroll(e) { const href = e.target.getAttribute('href'); if (href && href.startsWith('#')) { e.preventDefault(); const targetId = href.substring(1); const target = document.getElementById(targetId); if (target) { // Add smooth scrolling with offset for fixed header const headerOffset = 100; // Adjust based on your header height const elementPosition = target.getBoundingClientRect().top; const offsetPosition = elementPosition + window.pageYOffset - headerOffset; window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); // Add a highlight effect to the target element target.style.animation = 'highlightSection 2s ease-out'; setTimeout(() => { target.style.animation = ''; }, 2000); } } } // Phone number formatting function formatPhoneNumber(phoneNumber) { const cleaned = phoneNumber.replace(/\D/g, ''); const match = cleaned.match(/^(\d{3})(\d{3})(\d{4})$/); if (match) { return `(${match[1]}) ${match[2]}-${match[3]}`; } return phoneNumber; } // Add hover effects to service cards function handleServiceCardHover() { const serviceCards = document.querySelectorAll('.group'); serviceCards.forEach(card => { card.addEventListener('mouseenter', function() { this.style.transform = 'translateY(-8px) scale(1.02)'; }); card.addEventListener('mouseleave', function() { this.style.transform = 'translateY(0) scale(1)'; }); }); } // Add animation to stats when they come into view function animateStats() { const stats = document.querySelectorAll('[class*="text-4xl"][class*="font-bold"]'); const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const target = entry.target; const finalValue = target.textContent.replace(/[^\d]/g, ''); if (finalValue) { let currentValue = 0; const increment = Math.ceil(finalValue / 50); const timer = setInterval(() => { currentValue += increment; if (currentValue >= finalValue) { currentValue = finalValue; clearInterval(timer); } target.textContent = target.textContent.replace(/\d+/, currentValue); }, 30); } observer.unobserve(target); } }); }, { threshold: 0.5 }); stats.forEach(stat => { observer.observe(stat); }); } // Add floating animation to hero elements function addFloatingAnimation() { const floatingElements = document.querySelectorAll('.animate-pulse, .animate-bounce'); floatingElements.forEach((element, index) => { element.style.animationDelay = `${index * 0.5}s`; element.style.animationDuration = `${2 + (index * 0.5)}s`; }); } // Add scroll reveal animations function addScrollRevealAnimations() { const revealElements = document.querySelectorAll('.group, .portfolio-item'); const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.style.opacity = '1'; entry.target.style.transform = 'translateY(0)'; observer.unobserve(entry.target); } }); }, { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }); revealElements.forEach((element, index) => { element.style.opacity = '0'; element.style.transform = 'translateY(20px)'; element.style.transition = `opacity 0.6s ease ${index * 0.1}s, transform 0.6s ease ${index * 0.1}s`; observer.observe(element); }); } // Initialize portfolio functionality function initPortfolio() { portfolioItems = Array.from(document.querySelectorAll('.portfolio-item')); portfolioFilters = Array.from(document.querySelectorAll('.portfolio-filter')); // Add click listeners to filter buttons portfolioFilters.forEach(filter => { filter.addEventListener('click', handlePortfolioFilter); }); // Add CSS for animations const style = document.createElement('style'); style.textContent = ` @keyframes fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } @keyframes highlightSection { 0% { background-color: transparent; } 50% { background-color: rgba(0, 119, 204, 0.1); } 100% { background-color: transparent; } } .portfolio-item { transition: all 0.3s ease; } .group:hover .group-hover\\:scale-110 { transform: scale(1.1); } `; document.head.appendChild(style); } // Form validation and enhancement function enhanceForms() { const forms = document.querySelectorAll('form[data-landingsite-contact-form]'); forms.forEach(form => { // Add loading states form.addEventListener('submit', function(e) { const submitBtn = form.querySelector('button[type="submit"]'); if (submitBtn) { const originalText = submitBtn.innerHTML; submitBtn.innerHTML = 'Sending Message...'; submitBtn.disabled = true; // Add success feedback after submission setTimeout(() => { submitBtn.innerHTML = 'Message Sent!'; submitBtn.classList.add('bg-green-500'); // Reset after another 2 seconds setTimeout(() => { submitBtn.innerHTML = originalText; submitBtn.disabled = false; submitBtn.classList.remove('bg-green-500'); }, 2000); }, 1000); } }); // Add real-time validation const inputs = form.querySelectorAll('input, textarea, select'); inputs.forEach(input => { input.addEventListener('blur', function() { validateInput(this); }); // Auto-format phone numbers if (input.type === 'tel') { input.addEventListener('input', function() { this.value = formatPhoneNumber(this.value); }); } }); }); } function validateInput(input) { const value = input.value.trim(); let isValid = true; // Remove existing error messages const existingError = input.parentNode.querySelector('.error-message'); if (existingError) { existingError.remove(); } // Basic validation if (input.hasAttribute('required') && !value) { isValid = false; showError(input, 'This field is required'); } else if (input.type === 'email' && value) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(value)) { isValid = false; showError(input, 'Please enter a valid email address'); } } else if (input.type === 'tel' && value) { const phoneRegex = /^\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/; if (!phoneRegex.test(value)) { isValid = false; showError(input, 'Please enter a valid phone number'); } } // Update input styling if (isValid) { input.classList.remove('border-red-500'); input.classList.add('border-green-500'); } else { input.classList.remove('border-green-500'); input.classList.add('border-red-500'); } } function showError(input, message) { const errorDiv = document.createElement('div'); errorDiv.className = 'error-message text-red-500 text-sm mt-1'; errorDiv.textContent = message; input.parentNode.appendChild(errorDiv); } // Add click tracking for analytics function trackClicks() { // Track CTA buttons const ctaButtons = document.querySelectorAll('a[href="#contact-form"], a[href^="tel:"], a[href^="mailto:"]'); ctaButtons.forEach(button => { button.addEventListener('click', function() { const action = this.href.includes('tel:') ? 'phone_click' : this.href.includes('mailto:') ? 'email_click' : 'consultation_click'; // Track the action (you could send this to Google Analytics or other tools) console.log(`User clicked: ${action}`, this.href); // Add visual feedback if (action === 'consultation_click') { this.style.transform = 'scale(0.95)'; setTimeout(() => { this.style.transform = ''; }, 150); } }); }); // Track portfolio project views const projectButtons = document.querySelectorAll('.portfolio-item button'); projectButtons.forEach(button => { button.addEventListener('click', function() { const projectCard = this.closest('.portfolio-item'); const projectName = projectCard.querySelector('h3').textContent; console.log(`Portfolio project viewed: ${projectName}`); }); }); } // Add consultation booking success tracking function addBookingTracking() { // Track when users reach the contact form const contactForm = document.getElementById('contact-form'); if (contactForm) { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { console.log('User reached contact form section'); // You could send this to Google Analytics } }); }, { threshold: 0.3 }); observer.observe(contactForm); } } // Main initialization function function init() { console.log('2pweb Development website initialized'); // Initialize all functionality initPortfolio(); handleServiceCardHover(); animateStats(); addFloatingAnimation(); addScrollRevealAnimations(); enhanceForms(); trackClicks(); addBookingTracking(); // Add enhanced smooth scrolling to all anchor links document.addEventListener('click', function(e) { if (e.target.tagName === 'A' || e.target.closest('a')) { const link = e.target.tagName === 'A' ? e.target : e.target.closest('a'); if (link.href.includes('#')) { handleSmoothScroll({target: link, preventDefault: () => e.preventDefault()}); } } }); // Add intersection observer for scroll animations const observerOptions = { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }; // Log successful initialization setTimeout(() => { console.log(`Portfolio initialized with ${portfolioItems.length} items and ${portfolioFilters.length} filters`); console.log('Contact form booking functionality enabled'); }, 100); } // Cleanup function function teardown() { console.log('2pweb Development website teardown'); // Remove portfolio event listeners portfolioFilters.forEach(filter => { filter.removeEventListener('click', handlePortfolioFilter); }); // Remove other event listeners document.removeEventListener('click', handleSmoothScroll); // Reset arrays portfolioItems = []; portfolioFilters = []; currentFilter = 'all'; } // Export functions for the website system if (typeof module !== 'undefined' && module.exports) { module.exports = { init, teardown }; } else { window.websiteInit = init; window.websiteTeardown = teardown; }