// 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 with ARIA and Focus Trap
const navToggle = document.getElementById('nav-toggle');
const navMenu = document.getElementById('nav-menu');
if (navToggle && navMenu) {
// Get focusable elements in the menu
const getFocusableElements = () => {
return navMenu.querySelectorAll('a[href], button:not([disabled])');
};
// Focus trap handler
const handleFocusTrap = (e) => {
if (!navMenu.classList.contains('active')) return;
const focusableElements = getFocusableElements();
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
} else if (!e.shiftKey && document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
}
// Close menu on Escape key
if (e.key === 'Escape') {
closeMenu();
}
};
const openMenu = () => {
navMenu.classList.add('active');
navToggle.classList.add('active');
navToggle.setAttribute('aria-expanded', 'true');
document.addEventListener('keydown', handleFocusTrap);
// Focus first menu item
const firstFocusable = getFocusableElements()[0];
if (firstFocusable) {
setTimeout(() => firstFocusable.focus(), 100);
}
};
const closeMenu = () => {
navMenu.classList.remove('active');
navToggle.classList.remove('active');
navToggle.setAttribute('aria-expanded', 'false');
document.removeEventListener('keydown', handleFocusTrap);
navToggle.focus();
};
navToggle.addEventListener('click', function() {
const isExpanded = navToggle.getAttribute('aria-expanded') === 'true';
if (isExpanded) {
closeMenu();
} else {
openMenu();
}
});
// Close mobile menu when clicking on a link
const navLinks = document.querySelectorAll('.nav-link');
navLinks.forEach(link => {
link.addEventListener('click', () => {
closeMenu();
});
});
// Close menu when clicking outside
document.addEventListener('click', (e) => {
if (navMenu.classList.contains('active') &&
!navMenu.contains(e.target) &&
!navToggle.contains(e.target)) {
closeMenu();
}
});
}
// 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');
// Initialize reCAPTCHA and form tracking
let interactionScore = 0;
let formStartTime = Date.now();
// Track user interactions for bot detection
document.addEventListener('mousemove', () => interactionScore += 1);
document.addEventListener('keydown', () => interactionScore += 2);
document.addEventListener('click', () => interactionScore += 3);
// Set form timestamp
const timestampField = document.getElementById('form_timestamp');
if (timestampField) {
timestampField.value = formStartTime;
}
// Form Validation and Enhancement with ARIA support
const contactForm = document.querySelector('.contact-form form');
// Helper function to set field error state
function setFieldError(fieldId, errorMessage) {
const field = document.getElementById(fieldId);
const errorSpan = document.getElementById(fieldId + '-error');
if (field && errorSpan) {
field.setAttribute('aria-invalid', 'true');
errorSpan.textContent = errorMessage;
}
}
// Helper function to clear field error state
function clearFieldError(fieldId) {
const field = document.getElementById(fieldId);
const errorSpan = document.getElementById(fieldId + '-error');
if (field && errorSpan) {
field.setAttribute('aria-invalid', 'false');
errorSpan.textContent = '';
}
}
// Clear all form errors
function clearAllErrors() {
['name', 'email', 'company', 'message'].forEach(clearFieldError);
}
if (contactForm) {
// Real-time validation on blur
const requiredFields = contactForm.querySelectorAll('[required]');
requiredFields.forEach(field => {
field.addEventListener('blur', function() {
validateField(this);
});
field.addEventListener('input', function() {
if (this.getAttribute('aria-invalid') === 'true') {
validateField(this);
}
});
});
function validateField(field) {
const value = field.value.trim();
const fieldId = field.id;
if (fieldId === 'name' && value.length < 2) {
setFieldError(fieldId, 'Please enter a valid name (at least 2 characters)');
return false;
} else if (fieldId === 'email' && !isValidEmail(value)) {
setFieldError(fieldId, 'Please enter a valid email address');
return false;
} else if (fieldId === 'company' && value.length < 2) {
setFieldError(fieldId, 'Please enter your organisation name');
return false;
} else if (fieldId === 'message' && value.length < 10) {
setFieldError(fieldId, 'Please provide more details (at least 10 characters)');
return false;
}
clearFieldError(fieldId);
return true;
}
contactForm.addEventListener('submit', function(e) {
e.preventDefault();
clearAllErrors();
// Basic form validation
const formData = new FormData(this);
const name = formData.get('name');
const email = formData.get('email');
const company = formData.get('company');
const message = formData.get('message');
// Validation
let isValid = true;
let firstErrorField = null;
if (!name || name.trim().length < 2) {
setFieldError('name', 'Please enter a valid name (at least 2 characters)');
isValid = false;
if (!firstErrorField) firstErrorField = document.getElementById('name');
}
if (!email || !isValidEmail(email)) {
setFieldError('email', 'Please enter a valid email address');
isValid = false;
if (!firstErrorField) firstErrorField = document.getElementById('email');
}
if (!company || company.trim().length < 2) {
setFieldError('company', 'Please enter your organisation name');
isValid = false;
if (!firstErrorField) firstErrorField = document.getElementById('company');
}
if (!message || message.trim().length < 10) {
setFieldError('message', 'Please provide more details (at least 10 characters)');
isValid = false;
if (!firstErrorField) firstErrorField = document.getElementById('message');
}
// Focus first error field for accessibility
if (!isValid && firstErrorField) {
firstErrorField.focus();
return;
}
if (isValid) {
// Show loading state
const submitButton = this.querySelector('button[type="submit"]');
const originalText = submitButton.textContent;
submitButton.textContent = 'Sending...';
submitButton.disabled = true;
// Update form timestamp to current time (ensures it's fresh at submission)
const timestampField = document.getElementById('form_timestamp');
if (timestampField) {
timestampField.value = Date.now();
}
// Recreate formData after updating timestamp
const freshFormData = new FormData(this);
// Execute reCAPTCHA and submit form
if (typeof grecaptcha !== 'undefined') {
grecaptcha.ready(() => {
grecaptcha.execute(window.recaptchaSiteKey, {action: 'contact_form'}).then((token) => {
// Add reCAPTCHA token and interaction data
freshFormData.set('recaptcha_response', token);
freshFormData.set('interaction_token', btoa(JSON.stringify({score: Math.min(interactionScore, 100), time: Date.now() - formStartTime})));
// Submit form
fetch('contact-handler.php', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
},
body: freshFormData
})
.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(data.message || '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 {
// Fallback if reCAPTCHA not loaded
showNotification('Security verification not available. Please refresh the page.', 'error');
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 = `