- Add /api/audit endpoint to contact-api (deployed to server) - Replace blog ghost links with 'coming soon' placeholder - Add I2P mirror link to all page footers - Wire up audit form frontend handler in main.js - Update contact form handler to use fetch API - Add robots.txt and sitemap.xml - Add scripts/sync-eepsite.sh for I2P/clearnet sync
157 lines
5.3 KiB
JavaScript
157 lines
5.3 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] || '🌙';
|
|
}
|
|
|
|
var stored = getStored();
|
|
var current = stored || document.documentElement.getAttribute('data-theme') || 'dark';
|
|
if (themes.indexOf(current) === -1) current = 'dark';
|
|
applyTheme(current);
|
|
|
|
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 contactForm = document.getElementById('contact-form');
|
|
if (contactForm) {
|
|
contactForm.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
|
|
var status = document.getElementById('form-status');
|
|
var submitBtn = contactForm.querySelector('button[type="submit"]');
|
|
var originalBtnText = submitBtn.textContent;
|
|
|
|
var formData = new FormData(contactForm);
|
|
var data = {
|
|
name: formData.get('name'),
|
|
email: formData.get('email'),
|
|
service: formData.get('service'),
|
|
message: formData.get('message')
|
|
};
|
|
|
|
var honeypot = formData.get('website') || formData.get('url') || formData.get('honey');
|
|
if (honeypot) {
|
|
status.className = 'notification is-success';
|
|
status.textContent = 'Message received. We\'ll respond within 48 hours.';
|
|
status.style.display = 'block';
|
|
contactForm.reset();
|
|
return;
|
|
}
|
|
|
|
submitBtn.disabled = true;
|
|
submitBtn.textContent = 'Sending...';
|
|
status.style.display = 'none';
|
|
|
|
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';
|
|
contactForm.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;
|
|
});
|
|
});
|
|
}
|
|
|
|
// Audit form - sends to backend API
|
|
var auditForm = document.getElementById('audit-form');
|
|
if (auditForm) {
|
|
auditForm.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
|
|
var status = document.getElementById('audit-status');
|
|
var submitBtn = auditForm.querySelector('button[type="submit"]');
|
|
var originalBtnText = submitBtn.textContent;
|
|
|
|
var formData = new FormData(auditForm);
|
|
var data = {
|
|
name: formData.get('name'),
|
|
email: formData.get('email'),
|
|
business: formData.get('business'),
|
|
audit_type: formData.get('audit_type'),
|
|
description: formData.get('description')
|
|
};
|
|
|
|
var honeypot = formData.get('website') || formData.get('url') || formData.get('honey');
|
|
if (honeypot) {
|
|
status.className = 'notification is-success';
|
|
status.textContent = 'Audit request received. We\'ll be in touch within 48 hours.';
|
|
status.style.display = 'block';
|
|
auditForm.reset();
|
|
return;
|
|
}
|
|
|
|
submitBtn.disabled = true;
|
|
submitBtn.textContent = 'Submitting...';
|
|
status.style.display = 'none';
|
|
|
|
fetch('/api/audit', {
|
|
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 || 'Audit request received. We\'ll review your situation and send a report within 48 hours.';
|
|
status.style.display = 'block';
|
|
auditForm.reset();
|
|
} else {
|
|
throw new Error(result.error || 'Failed to submit audit request');
|
|
}
|
|
})
|
|
.catch(function(error) {
|
|
status.className = 'notification is-danger';
|
|
status.textContent = error.message || 'Failed to submit audit request. Please try again.';
|
|
status.style.display = 'block';
|
|
})
|
|
.finally(function() {
|
|
submitBtn.disabled = false;
|
|
submitBtn.textContent = originalBtnText;
|
|
});
|
|
});
|
|
}
|