// 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;
}