// Enhanced JavaScript for UK Data Services Website document.addEventListener('DOMContentLoaded', function() { // Rotating Hero Text Effect (like original UK Data Services site) const rotatingText = document.getElementById('rotating-text'); const heroSubtitle = document.getElementById('hero-subtitle'); if (rotatingText && heroSubtitle) { const slides = [ { title: "Voted UK's No.1 Web Scraping Service", subtitle: "We are experts in web scraping, data analysis and competitor price monitoring." }, { title: "UK-based Team", subtitle: "Our team is based in the UK for a clearer, faster response." }, { title: "Professional Price Monitoring", subtitle: "Let us monitor your competitor's pricing and product ranges." }, { title: "Bespoke Software Solutions", subtitle: "Let our experts build your ideal scraping solution." } ]; let currentSlide = 0; function rotateSlide() { // Fade out rotatingText.style.opacity = '0'; heroSubtitle.style.opacity = '0'; setTimeout(() => { // Change text rotatingText.textContent = slides[currentSlide].title; heroSubtitle.textContent = slides[currentSlide].subtitle; // Fade in rotatingText.style.opacity = '1'; heroSubtitle.style.opacity = '1'; currentSlide = (currentSlide + 1) % slides.length; }, 500); } // Add transition styles immediately rotatingText.style.transition = 'opacity 0.5s ease-in-out'; heroSubtitle.style.transition = 'opacity 0.5s ease-in-out'; // Start rotation after a short delay setTimeout(() => { rotateSlide(); setInterval(rotateSlide, 4000); // Change every 4 seconds }, 2000); // Start after 2 seconds console.log('Hero text rotation initialized'); } else { console.log('Rotating text elements not found'); } // Mobile Navigation Toggle const navToggle = document.getElementById('nav-toggle'); const navMenu = document.getElementById('nav-menu'); if (navToggle && navMenu) { navToggle.addEventListener('click', function() { navMenu.classList.toggle('active'); navToggle.classList.toggle('active'); }); // Close mobile menu when clicking on a link const navLinks = document.querySelectorAll('.nav-link'); navLinks.forEach(link => { link.addEventListener('click', () => { navMenu.classList.remove('active'); navToggle.classList.remove('active'); }); }); } // Navbar Scroll Effect const navbar = document.getElementById('navbar'); function handleNavbarScroll() { if (window.scrollY > 50) { navbar.classList.add('scrolled'); } else { navbar.classList.remove('scrolled'); } } window.addEventListener('scroll', handleNavbarScroll); // Smooth Scrolling for Navigation Links const smoothScrollLinks = document.querySelectorAll('a[href^="#"]'); smoothScrollLinks.forEach(link => { link.addEventListener('click', function(e) { e.preventDefault(); const targetId = this.getAttribute('href'); const targetSection = document.querySelector(targetId); if (targetSection) { const headerOffset = 80; const elementPosition = targetSection.getBoundingClientRect().top; const offsetPosition = elementPosition + window.pageYOffset - headerOffset; window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); } }); }); // Enhanced Scroll Animations const animatedElements = document.querySelectorAll('.animate-on-scroll, .service-card, .feature, .step'); const observerOptions = { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }; const observer = new IntersectionObserver(function(entries) { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('animated'); entry.target.style.opacity = '1'; entry.target.style.transform = 'translateY(0)'; observer.unobserve(entry.target); } }); }, observerOptions); animatedElements.forEach((element, index) => { // Set initial state element.style.opacity = '0'; element.style.transform = 'translateY(30px)'; element.style.transition = `opacity 0.8s ease-out ${index * 0.1}s, transform 0.8s ease-out ${index * 0.1}s`; observer.observe(element); }); // Add hover animations to service cards const serviceCards = document.querySelectorAll('.service-card'); serviceCards.forEach(card => { card.addEventListener('mouseenter', function() { this.style.transform = 'translateY(-10px) scale(1.02)'; this.style.boxShadow = '0 20px 40px rgba(0, 0, 0, 0.15)'; }); card.addEventListener('mouseleave', function() { this.style.transform = 'translateY(0) scale(1)'; this.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.08)'; }); }); // Add pulse animation to CTA buttons const ctaButtons = document.querySelectorAll('.btn-primary'); ctaButtons.forEach(btn => { btn.addEventListener('mouseenter', function() { this.style.animation = 'pulse 0.5s ease-in-out'; }); btn.addEventListener('mouseleave', function() { this.style.animation = 'none'; }); }); console.log('Enhanced animations initialized'); // Form Validation and Enhancement const contactForm = document.querySelector('.contact-form form'); if (contactForm) { contactForm.addEventListener('submit', function(e) { e.preventDefault(); // Basic form validation const formData = new FormData(this); const name = formData.get('name'); const email = formData.get('email'); const message = formData.get('message'); // Validation let isValid = true; const errors = []; if (!name || name.trim().length < 2) { errors.push('Please enter a valid name'); isValid = false; } if (!email || !isValidEmail(email)) { errors.push('Please enter a valid email address'); isValid = false; } if (!message || message.trim().length < 10) { errors.push('Please provide more details about your project (minimum 10 characters)'); isValid = false; } if (isValid) { // Show loading state const submitButton = this.querySelector('button[type="submit"]'); const originalText = submitButton.textContent; submitButton.textContent = 'Sending...'; submitButton.disabled = true; // Submit form (you'll need to implement the backend handler) fetch('contact-handler.php', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { showNotification('Message sent successfully! We\'ll get back to you soon.', 'success'); this.reset(); } else { showNotification('There was an error sending your message. Please try again.', 'error'); } }) .catch(error => { console.error('Error:', error); showNotification('There was an error sending your message. Please try again.', 'error'); }) .finally(() => { submitButton.textContent = originalText; submitButton.disabled = false; }); } else { showNotification(errors.join('
'), 'error'); } }); } // Email validation function function isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } // Notification system function showNotification(message, type = 'info') { // Remove existing notifications const existingNotification = document.querySelector('.notification'); if (existingNotification) { existingNotification.remove(); } // Create notification element const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.innerHTML = `
${message}
`; // Add styles notification.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 10000; background: ${type === 'success' ? '#10b981' : type === 'error' ? '#ef4444' : '#3b82f6'}; color: white; padding: 16px 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); max-width: 400px; font-family: 'Inter', sans-serif; font-size: 14px; opacity: 0; transform: translateX(100%); transition: all 0.3s ease; `; notification.querySelector('.notification-content').style.cssText = ` display: flex; justify-content: space-between; align-items: center; gap: 12px; `; notification.querySelector('.notification-close').style.cssText = ` background: none; border: none; color: white; font-size: 18px; cursor: pointer; padding: 0; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; `; // Add to page document.body.appendChild(notification); // Animate in setTimeout(() => { notification.style.opacity = '1'; notification.style.transform = 'translateX(0)'; }, 100); // Handle close button notification.querySelector('.notification-close').addEventListener('click', () => { hideNotification(notification); }); // Auto hide after 5 seconds setTimeout(() => { if (document.body.contains(notification)) { hideNotification(notification); } }, 5000); } function hideNotification(notification) { notification.style.opacity = '0'; notification.style.transform = 'translateX(100%)'; setTimeout(() => { if (document.body.contains(notification)) { notification.remove(); } }, 300); } // Stats Counter Animation const stats = document.querySelectorAll('.stat-number'); function animateStats() { stats.forEach(stat => { const originalText = stat.textContent.trim(); console.log('Animating stat:', originalText); // Handle different stat types if (originalText.includes('£2.5M+')) { return; // Keep as is, don't animate currency } else if (originalText.includes('99.8%')) { animateNumber(stat, 0, 99.8, '%'); } else if (originalText.includes('ISO 27001')) { return; // Keep as is, don't animate certification } }); } function animateNumber(element, start, end, suffix = '') { let current = start; const increment = (end - start) / 60; // 60 steps for smoother animation const timer = setInterval(() => { current += increment; if (current >= end) { current = end; clearInterval(timer); } element.textContent = current.toFixed(1) + suffix; }, 50); // Every 50ms } // Trigger stats animation when section is visible const statsSection = document.querySelector('.hero-stats'); if (statsSection) { const statsObserver = new IntersectionObserver(function(entries) { entries.forEach(entry => { if (entry.isIntersecting) { console.log('Stats section is visible, starting animation'); setTimeout(() => { animateStats(); }, 500); // Small delay statsObserver.unobserve(entry.target); } }); }, { threshold: 0.3 }); statsObserver.observe(statsSection); } else { console.log('Stats section not found'); } // Enhanced Lazy Loading for Images with WebP support const images = document.querySelectorAll('img[loading="lazy"]'); // WebP support detection function supportsWebP() { const canvas = document.createElement('canvas'); canvas.width = 1; canvas.height = 1; return canvas.toDataURL('image/webp').indexOf('webp') !== -1; } if ('IntersectionObserver' in window) { const imageObserver = new IntersectionObserver(function(entries) { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; // Handle data-src for lazy loading if (img.dataset.src) { img.src = img.dataset.src; } // Handle WebP support if (img.dataset.webp && supportsWebP()) { img.src = img.dataset.webp; } img.classList.add('loaded'); img.style.opacity = '1'; imageObserver.unobserve(img); } }); }, { rootMargin: '50px 0px', threshold: 0.1 }); images.forEach(img => { // Set initial opacity for lazy images if (img.loading === 'lazy') { img.style.opacity = '0'; img.style.transition = 'opacity 0.3s ease'; } imageObserver.observe(img); }); } // Scroll to Top Button const scrollTopBtn = document.createElement('button'); scrollTopBtn.innerHTML = '↑'; scrollTopBtn.className = 'scroll-top-btn'; scrollTopBtn.style.cssText = ` position: fixed; bottom: 30px; right: 30px; width: 50px; height: 50px; border: none; border-radius: 50%; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; font-size: 20px; cursor: pointer; opacity: 0; visibility: hidden; transition: all 0.3s ease; z-index: 1000; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); `; document.body.appendChild(scrollTopBtn); scrollTopBtn.addEventListener('click', () => { window.scrollTo({ top: 0, behavior: 'smooth' }); }); // Show/hide scroll to top button function handleScrollTopButton() { if (window.scrollY > 500) { scrollTopBtn.style.opacity = '1'; scrollTopBtn.style.visibility = 'visible'; } else { scrollTopBtn.style.opacity = '0'; scrollTopBtn.style.visibility = 'hidden'; } } window.addEventListener('scroll', handleScrollTopButton); // Performance: Throttle scroll events let scrollTimeout; const originalHandlers = [handleNavbarScroll, handleScrollTopButton]; function throttledScrollHandler() { if (!scrollTimeout) { scrollTimeout = setTimeout(() => { originalHandlers.forEach(handler => handler()); scrollTimeout = null; }, 16); // ~60fps } } window.removeEventListener('scroll', handleNavbarScroll); window.removeEventListener('scroll', handleScrollTopButton); window.addEventListener('scroll', throttledScrollHandler); // Preload critical resources with WebP support function preloadResource(href, as = 'image', type = null) { const link = document.createElement('link'); link.rel = 'preload'; link.href = href; link.as = as; if (type) { link.type = type; } document.head.appendChild(link); } // Preload critical images with WebP format preference function preloadCriticalImages() { const criticalImages = [ 'assets/images/ukds-main-logo.png', 'assets/images/hero-data-analytics.svg' ]; criticalImages.forEach(imagePath => { // Try WebP first if supported if (supportsWebP()) { const webpPath = imagePath.replace(/\.(jpg|jpeg|png)$/i, '.webp'); preloadResource(webpPath, 'image', 'image/webp'); } else { preloadResource(imagePath, 'image'); } }); } // Initialize critical image preloading preloadCriticalImages(); // Initialize tooltips (if needed) const tooltipElements = document.querySelectorAll('[data-tooltip]'); tooltipElements.forEach(element => { element.addEventListener('mouseenter', function() { const tooltipText = this.getAttribute('data-tooltip'); const tooltip = document.createElement('div'); tooltip.className = 'tooltip'; tooltip.textContent = tooltipText; tooltip.style.cssText = ` position: absolute; background: #1a1a1a; color: white; padding: 8px 12px; border-radius: 6px; font-size: 14px; white-space: nowrap; z-index: 10000; opacity: 0; transition: opacity 0.3s ease; pointer-events: none; `; document.body.appendChild(tooltip); const rect = this.getBoundingClientRect(); tooltip.style.left = rect.left + (rect.width / 2) - (tooltip.offsetWidth / 2) + 'px'; tooltip.style.top = rect.top - tooltip.offsetHeight - 10 + 'px'; setTimeout(() => { tooltip.style.opacity = '1'; }, 100); this.addEventListener('mouseleave', function() { tooltip.style.opacity = '0'; setTimeout(() => { if (document.body.contains(tooltip)) { tooltip.remove(); } }, 300); }, { once: true }); }); }); // Security: Prevent XSS in dynamic content function sanitizeHTML(str) { const temp = document.createElement('div'); temp.textContent = str; return temp.innerHTML; } // Service Worker Registration (for PWA capabilities) if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js') .then(registration => { console.log('SW registered: ', registration); }) .catch(registrationError => { console.log('SW registration failed: ', registrationError); }); }); } // Performance monitoring if ('performance' in window) { window.addEventListener('load', function() { setTimeout(() => { const perfData = performance.getEntriesByType('navigation')[0]; if (perfData) { console.log('Page Load Performance:', { 'DNS Lookup': Math.round(perfData.domainLookupEnd - perfData.domainLookupStart), 'TCP Connection': Math.round(perfData.connectEnd - perfData.connectStart), 'Request/Response': Math.round(perfData.responseEnd - perfData.requestStart), 'DOM Processing': Math.round(perfData.domComplete - perfData.domLoading), 'Total Load Time': Math.round(perfData.loadEventEnd - perfData.navigationStart) }); } }, 0); }); } console.log('UK Data Services website initialized successfully'); console.log('Performance optimizations: Lazy loading, WebP support, and preloading enabled'); // Universal Blog Pagination System initializeBlogPagination(); function initializeBlogPagination() { const paginationContainer = document.querySelector('.blog-pagination'); const articlesGrid = document.querySelector('.articles-grid'); if (!paginationContainer || !articlesGrid) { return; // No pagination on this page } const prevButton = paginationContainer.querySelector('button:first-child'); const nextButton = paginationContainer.querySelector('button:last-child'); const paginationInfo = paginationContainer.querySelector('.pagination-info'); if (!prevButton || !nextButton || !paginationInfo) { return; // Invalid pagination structure } // Get current page from URL or default to 1 const urlParams = new URLSearchParams(window.location.search); let currentPage = parseInt(urlParams.get('page')) || 1; // Get all articles on the page const allArticles = Array.from(articlesGrid.querySelectorAll('.article-card')); const articlesPerPage = 6; const totalPages = Math.ceil(allArticles.length / articlesPerPage); // If we have actual multiple pages of content, use the original pagination logic // Otherwise, implement client-side pagination if (totalPages <= 1) { // Hide pagination if not needed paginationContainer.style.display = 'none'; return; } function renderPage(page, shouldScroll = false) { // Hide all articles allArticles.forEach(article => { article.style.display = 'none'; }); // Show articles for current page const startIndex = (page - 1) * articlesPerPage; const endIndex = startIndex + articlesPerPage; for (let i = startIndex; i < endIndex && i < allArticles.length; i++) { allArticles[i].style.display = 'block'; allArticles[i].style.animation = 'fadeInUp 0.6s ease forwards'; } // Update pagination info paginationInfo.textContent = `Page ${page} of ${totalPages}`; // Update button states prevButton.disabled = (page <= 1); nextButton.disabled = (page >= totalPages); // Update URL without page reload const newUrl = new URL(window.location); if (page > 1) { newUrl.searchParams.set('page', page); } else { newUrl.searchParams.delete('page'); } window.history.replaceState({}, '', newUrl); // Only scroll to articles section when navigating between pages if (shouldScroll) { articlesGrid.scrollIntoView({ behavior: 'smooth', block: 'start' }); } } // Event listeners prevButton.addEventListener('click', function(e) { e.preventDefault(); if (currentPage > 1) { currentPage--; renderPage(currentPage, true); } }); nextButton.addEventListener('click', function(e) { e.preventDefault(); if (currentPage < totalPages) { currentPage++; renderPage(currentPage, true); } }); // Initialize first page (don't scroll on initial load) renderPage(currentPage, false); // Add CSS animation for article transitions const style = document.createElement('style'); style.textContent = ` @keyframes fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .article-card { transition: opacity 0.3s ease, transform 0.3s ease; } .article-card[style*="display: none"] { opacity: 0; transform: translateY(20px); } `; document.head.appendChild(style); console.log(`Blog pagination initialized: ${totalPages} pages, ${allArticles.length} articles`); } // Viewport-based Image Loading Optimization function initializeViewportImageLoading() { // Intersection Observer for lazy loading optimization const imageObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; // Load high-quality version when in viewport if (img.dataset.src) { img.src = img.dataset.src; img.removeAttribute('data-src'); } // Load WebP for supported browsers if (img.dataset.webp && supportsWebP()) { img.src = img.dataset.webp; } observer.unobserve(img); } }); }, { rootMargin: '50px 0px', threshold: 0.01 }); // Observe all images with data-src document.querySelectorAll('img[data-src]').forEach(img => { imageObserver.observe(img); }); // WebP support detection function supportsWebP() { return new Promise(resolve => { const webP = new Image(); webP.onload = webP.onerror = () => resolve(webP.height === 2); webP.src = 'data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA'; }); } } // Touch Target Optimization for Mobile function optimizeTouchTargets() { const minTouchSize = 44; // 44px minimum touch target // Check and optimize button sizes document.querySelectorAll('button, .btn, a[role="button"]').forEach(element => { const rect = element.getBoundingClientRect(); if (rect.width < minTouchSize || rect.height < minTouchSize) { element.style.minWidth = minTouchSize + 'px'; element.style.minHeight = minTouchSize + 'px'; element.style.display = 'inline-flex'; element.style.alignItems = 'center'; element.style.justifyContent = 'center'; } }); // Add touch-friendly spacing document.querySelectorAll('.nav-menu a, .social-links a').forEach(element => { element.style.padding = '12px 16px'; element.style.margin = '4px'; }); } // Initialize optimizations initializeViewportImageLoading(); if ('ontouchstart' in window) { optimizeTouchTargets(); } });