Colocation/datacenter service website with: - One-page main site (hero, services, pricing, contact form) - Technical specs page (power, cooling, connectivity, security) - Dark blue technical theme, fully responsive - PHP backend for contact form with rate limiting Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
127 lines
4.2 KiB
JavaScript
127 lines
4.2 KiB
JavaScript
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
// === Mobile nav toggle ===
|
|
const navToggle = document.getElementById('nav-toggle');
|
|
const nav = document.getElementById('nav');
|
|
|
|
if (navToggle && nav) {
|
|
navToggle.addEventListener('click', () => {
|
|
nav.classList.toggle('open');
|
|
navToggle.classList.toggle('open');
|
|
});
|
|
|
|
// Close nav on link click
|
|
nav.querySelectorAll('a').forEach(link => {
|
|
link.addEventListener('click', () => {
|
|
nav.classList.remove('open');
|
|
navToggle.classList.remove('open');
|
|
});
|
|
});
|
|
}
|
|
|
|
// === Sticky header ===
|
|
const header = document.getElementById('header');
|
|
if (header) {
|
|
window.addEventListener('scroll', () => {
|
|
header.classList.toggle('scrolled', window.scrollY > 50);
|
|
}, { passive: true });
|
|
}
|
|
|
|
// === Scroll animations ===
|
|
const observerOptions = {
|
|
threshold: 0.1,
|
|
rootMargin: '0px 0px -50px 0px'
|
|
};
|
|
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
entry.target.classList.add('visible');
|
|
observer.unobserve(entry.target);
|
|
}
|
|
});
|
|
}, observerOptions);
|
|
|
|
// Add fade-in to sections
|
|
document.querySelectorAll('.service-card, .feature, .pricing-card, .contact-form, .contact-info').forEach(el => {
|
|
el.classList.add('fade-in');
|
|
observer.observe(el);
|
|
});
|
|
|
|
// === Contact form ===
|
|
const form = document.getElementById('contact-form');
|
|
const formStatus = document.getElementById('form-status');
|
|
|
|
if (form) {
|
|
form.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
const submitBtn = form.querySelector('.btn-submit');
|
|
const originalText = submitBtn.textContent;
|
|
submitBtn.textContent = 'Lähetetään...';
|
|
submitBtn.disabled = true;
|
|
|
|
// Honeypot check
|
|
const honeypot = form.querySelector('input[name="website"]');
|
|
if (honeypot && honeypot.value) {
|
|
formStatus.textContent = 'Kiitos viestistäsi!';
|
|
formStatus.className = 'form-status success';
|
|
form.reset();
|
|
submitBtn.textContent = originalText;
|
|
submitBtn.disabled = false;
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData(form);
|
|
|
|
try {
|
|
const response = await fetch('api.php?action=contact', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
formStatus.textContent = 'Kiitos viestistäsi! Otamme yhteyttä pian.';
|
|
formStatus.className = 'form-status success';
|
|
form.reset();
|
|
} else {
|
|
formStatus.textContent = data.error || 'Viestin lähetys epäonnistui. Yritä uudelleen.';
|
|
formStatus.className = 'form-status error';
|
|
}
|
|
} catch {
|
|
formStatus.textContent = 'Viestin lähetys epäonnistui. Yritä uudelleen.';
|
|
formStatus.className = 'form-status error';
|
|
}
|
|
|
|
submitBtn.textContent = originalText;
|
|
submitBtn.disabled = false;
|
|
});
|
|
}
|
|
|
|
// === Active nav link on scroll ===
|
|
const sections = document.querySelectorAll('section[id]');
|
|
const navLinks = document.querySelectorAll('.nav a[href^="#"]');
|
|
|
|
if (sections.length && navLinks.length) {
|
|
window.addEventListener('scroll', () => {
|
|
let current = '';
|
|
sections.forEach(section => {
|
|
const top = section.offsetTop - 100;
|
|
if (window.scrollY >= top) {
|
|
current = section.getAttribute('id');
|
|
}
|
|
});
|
|
|
|
navLinks.forEach(link => {
|
|
link.classList.remove('active');
|
|
if (link.getAttribute('href') === '#' + current) {
|
|
link.classList.add('active');
|
|
}
|
|
});
|
|
}, { passive: true });
|
|
}
|
|
|
|
});
|