krustyplanet.org/js/main.js
Jezza Hehn c5c14d2fad Add functional contact form with API backend
- Replaced placeholder form submission with real POST to /api/contact
- Added honeypot field for spam protection
- Success/error feedback states
- Rate limiting handled by backend
2026-04-13 18:41:41 +00:00

108 lines
No EOL
3.4 KiB
JavaScript

// Theme cycle: dark → light → forest → coffee
(function() {
const themes = ['dark', 'light', 'forest', 'coffee'];
const icons = { dark: '🌙', light: '☀️', forest: '🌿', coffee: '☕' };
function getStored() {
try { return localStorage.getItem('kp-theme'); } catch(e) { return null; }
}
function setStored(theme) {
try { localStorage.setItem('kp-theme', theme); } catch(e) {}
}
function applyTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
setStored(theme);
var btn = document.getElementById('theme-toggle');
if (btn) btn.textContent = icons[theme] || '🌙';
}
// Init: stored preference, then data-theme attr, then default dark
var stored = getStored();
var current = stored || document.documentElement.getAttribute('data-theme') || 'dark';
if (themes.indexOf(current) === -1) current = 'dark';
applyTheme(current);
// Toggle
document.getElementById('theme-toggle').addEventListener('click', function() {
var idx = themes.indexOf(document.documentElement.getAttribute('data-theme'));
var next = themes[(idx < 0 ? 0 : idx + 1) % themes.length];
applyTheme(next);
});
})();
// Mobile nav toggle
document.getElementById('nav-toggle').addEventListener('click', function() {
var menu = document.getElementById('nav-menu');
this.classList.toggle('is-active');
menu.classList.toggle('is-active');
});
// Contact form - sends to backend API
var form = document.getElementById('contact-form');
if (form) {
form.addEventListener('submit', function(e) {
e.preventDefault();
var status = document.getElementById('form-status');
var submitBtn = form.querySelector('button[type="submit"]');
var originalBtnText = submitBtn.textContent;
// Collect form data
var formData = new FormData(form);
var data = {
name: formData.get('name'),
email: formData.get('email'),
service: formData.get('service'),
message: formData.get('message')
};
// Honeypot for spam
var honeypot = formData.get('website') || formData.get('url') || formData.get('honey');
if (honeypot) {
// Bot detected - pretend success
status.className = 'notification is-success';
status.textContent = 'Message received. We\'ll respond within 48 hours.';
status.style.display = 'block';
form.reset();
return;
}
// Disable button and show sending state
submitBtn.disabled = true;
submitBtn.textContent = 'Sending...';
status.style.display = 'none';
// Send to API
fetch('/api/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then(function(response) {
return response.json();
})
.then(function(result) {
if (result.success) {
status.className = 'notification is-success';
status.textContent = result.message || 'Message received. We\'ll respond within 48 hours.';
status.style.display = 'block';
form.reset();
} else {
throw new Error(result.error || 'Failed to send message');
}
})
.catch(function(error) {
status.className = 'notification is-danger';
status.textContent = error.message || 'Failed to send message. Please try again.';
status.style.display = 'block';
})
.finally(function() {
submitBtn.disabled = false;
submitBtn.textContent = originalBtnText;
});
});
}