ezsepa-website/main.js
2025-11-02 20:30:44 +01:00

281 lines
8.2 KiB
JavaScript

/**
* EzSepa Bank Payment Landing Page - Main JavaScript
* Handles mobile navigation, smooth scrolling, and analytics
*/
import { initHeroScene2D } from './hero-scene-2d.js';
// Mobile menu toggle
const initMobileMenu = () => {
const menuButton = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu');
const menuOpenIcon = menuButton?.querySelector('.menu-open');
const menuCloseIcon = menuButton?.querySelector('.menu-close');
if (!menuButton || !mobileMenu) return;
menuButton.addEventListener('click', () => {
const isExpanded = menuButton.getAttribute('aria-expanded') === 'true';
// Toggle menu visibility
mobileMenu.classList.toggle('hidden');
// Toggle icons
menuOpenIcon?.classList.toggle('hidden');
menuCloseIcon?.classList.toggle('hidden');
// Update aria-expanded
menuButton.setAttribute('aria-expanded', !isExpanded);
});
// Close mobile menu when clicking on a link
const mobileMenuLinks = mobileMenu.querySelectorAll('a');
mobileMenuLinks.forEach(link => {
link.addEventListener('click', () => {
mobileMenu.classList.add('hidden');
menuOpenIcon?.classList.remove('hidden');
menuCloseIcon?.classList.add('hidden');
menuButton.setAttribute('aria-expanded', 'false');
});
});
// Close mobile menu when clicking outside
document.addEventListener('click', (event) => {
const isClickInsideMenu = mobileMenu.contains(event.target);
const isClickOnButton = menuButton.contains(event.target);
if (!isClickInsideMenu && !isClickOnButton && !mobileMenu.classList.contains('hidden')) {
mobileMenu.classList.add('hidden');
menuOpenIcon?.classList.remove('hidden');
menuCloseIcon?.classList.add('hidden');
menuButton.setAttribute('aria-expanded', 'false');
}
});
};
// Smooth scroll for anchor links
const initSmoothScroll = () => {
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
const href = this.getAttribute('href');
// Skip if href is just "#"
if (href === '#') return;
const targetId = href.substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
e.preventDefault();
// Calculate offset for fixed header
const headerOffset = 80;
const elementPosition = targetElement.getBoundingClientRect().top;
const offsetPosition = elementPosition + window.pageYOffset - headerOffset;
window.scrollTo({
top: offsetPosition,
behavior: 'smooth'
});
}
});
});
};
// Add shadow to nav on scroll
const initNavShadow = () => {
const nav = document.querySelector('nav');
if (!nav) return;
const handleScroll = () => {
if (window.scrollY > 10) {
nav.classList.add('shadow-md');
} else {
nav.classList.remove('shadow-md');
}
};
window.addEventListener('scroll', handleScroll);
handleScroll(); // Initial check
};
// Animate elements on scroll (Intersection Observer)
const initScrollAnimations = () => {
const observerOptions = {
threshold: 0.1,
rootMargin: '0px 0px -100px 0px'
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animate-fade-in-up');
observer.unobserve(entry.target);
}
});
}, observerOptions);
// Observe feature cards, pricing cards, etc.
const animatedElements = document.querySelectorAll('.card, .grid > div');
animatedElements.forEach(el => observer.observe(el));
};
// Analytics stub - replace with your actual analytics implementation
const initAnalytics = () => {
// Placeholder for analytics tracking
window._ezsepa = window._ezsepa || {
track: (eventName, properties = {}) => {
console.log('📊 Analytics Event:', eventName, properties);
// Replace this with your actual analytics provider
// Examples:
// - Google Analytics: gtag('event', eventName, properties);
// - Mixpanel: mixpanel.track(eventName, properties);
// - Segment: analytics.track(eventName, properties);
// - Plausible: plausible(eventName, { props: properties });
}
};
// Track page view
window._ezsepa.track('Page View', {
page: window.location.pathname,
referrer: document.referrer,
timestamp: new Date().toISOString()
});
// Track CTA clicks
const ctaButtons = document.querySelectorAll('a[href*="{INSTALL_LINK}"]');
ctaButtons.forEach(button => {
button.addEventListener('click', () => {
window._ezsepa.track('CTA Clicked', {
location: button.closest('section')?.id || 'unknown',
text: button.textContent.trim(),
url: button.href
});
});
});
// Track demo link clicks
const demoLinks = document.querySelectorAll('a[href*="{DEMO_LINK}"]');
demoLinks.forEach(link => {
link.addEventListener('click', () => {
window._ezsepa.track('Demo Link Clicked', {
location: link.closest('section')?.id || 'unknown',
text: link.textContent.trim()
});
});
});
// Track section visibility
const sections = document.querySelectorAll('section[id]');
const sectionObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
window._ezsepa.track('Section Viewed', {
section: entry.target.id
});
}
});
}, { threshold: 0.5 });
sections.forEach(section => sectionObserver.observe(section));
};
// Performance monitoring
const initPerformanceMonitoring = () => {
if ('PerformanceObserver' in window) {
// Largest Contentful Paint (LCP)
const lcpObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('⚡ LCP:', lastEntry.renderTime || lastEntry.loadTime);
});
lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });
// First Input Delay (FID)
const fidObserver = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
console.log('⚡ FID:', entry.processingStart - entry.startTime);
});
});
fidObserver.observe({ entryTypes: ['first-input'] });
// Cumulative Layout Shift (CLS)
let clsValue = 0;
const clsObserver = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
if (!entry.hadRecentInput) {
clsValue += entry.value;
console.log('⚡ CLS:', clsValue);
}
});
});
clsObserver.observe({ entryTypes: ['layout-shift'] });
}
};
// Handle external links (open in new tab with security)
const initExternalLinks = () => {
const externalLinks = document.querySelectorAll('a[href^="http"]');
externalLinks.forEach(link => {
const url = new URL(link.href);
if (url.hostname !== window.location.hostname) {
link.setAttribute('target', '_blank');
link.setAttribute('rel', 'noopener noreferrer');
}
});
};
// Keyboard navigation improvements
const initKeyboardNav = () => {
// Add visible focus indicators for keyboard users
let isUsingMouse = false;
document.addEventListener('mousedown', () => {
isUsingMouse = true;
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
isUsingMouse = false;
}
});
document.addEventListener('focusin', () => {
if (!isUsingMouse) {
document.body.classList.add('using-keyboard');
}
});
document.addEventListener('focusout', () => {
document.body.classList.remove('using-keyboard');
});
};
// Initialize all features when DOM is ready
const init = () => {
initMobileMenu();
initSmoothScroll();
initNavShadow();
initScrollAnimations();
initAnalytics();
initPerformanceMonitoring();
initExternalLinks();
initKeyboardNav();
// Initialize 2D hero scene
initHeroScene2D();
console.log('✅ EzSepa landing page initialized');
};
// Run initialization
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
// Export for potential use in other scripts
export { initMobileMenu, initSmoothScroll, initAnalytics };