From 8a10eaaeb852474ee0be6c6f3d3215c43b29c7b1 Mon Sep 17 00:00:00 2001 From: BarnacleBoy Date: Thu, 23 Apr 2026 22:51:21 +0000 Subject: [PATCH] Fix audit form backend, update blog page, add I2P links, robots.txt, sitemap, audit form JS - 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 --- blog.html | 48 ++-------------------- contact.html | 1 + free-audit.html | 1 + index.html | 1 + js/main.js | 91 +++++++++++++++++++++++++++++++---------- pricing.html | 1 + robots.txt | 5 +++ scripts/sync-eepsite.sh | 71 ++++++++++++++++++++++++++++++++ services.html | 1 + sitemap.xml | 33 +++++++++++++++ 10 files changed, 187 insertions(+), 66 deletions(-) create mode 100644 robots.txt create mode 100755 scripts/sync-eepsite.sh create mode 100644 sitemap.xml diff --git a/blog.html b/blog.html index 625923d..9c0821e 100644 --- a/blog.html +++ b/blog.html @@ -39,51 +39,8 @@
-
- -
-

Building a Multi-Agent Fleet with OpenClaw

-

April 2026

-

- How I set up five specialized AI agents that work together like a small team: one researches, - one writes code, one audits quality, one hunts falsehoods, and one orchestrates. Real tasks, - real delegation, real quality control. Here's what actually works and what I wish I knew - starting out. -

-
- -
-

Using AI as a Community Manager Without Alienating Your Community

-

April 2026

-

- I run a community platform and also build AI tools. The temptation to automate everything - is real, but communities can smell inauthenticity from miles away. Here's how I use AI - for spam detection, content research, and i18n while keeping the human touch that - actually matters. -

-
- -
-

Self-Hosting AI: What You Actually Gain (and What You Don't)

-

April 2026

-

- Everyone says "self-host your AI for privacy." Few explain what that actually means in - practice. I deployed an entire AI agent system on a $6/month VPS. Here's the honest - breakdown of what's private, what isn't, what costs money, and what the tradeoffs are. -

-
- -
-

Running AI Services on I2P: A Practical Guide

-

April 2026

-

- Why would anyone put an AI consulting service on the darknet? Because some clients - care about anonymity, and I2P (not Tor) is better suited for services, not just - browsing. Here's how I set up an eepsite and what I learned about serving anonymous - clients. -

-
- +
+

Articles coming soon. Check back later.

@@ -91,6 +48,7 @@ diff --git a/contact.html b/contact.html index cb0cfda..4bbe14f 100644 --- a/contact.html +++ b/contact.html @@ -96,6 +96,7 @@ diff --git a/free-audit.html b/free-audit.html index cffa96a..7f707d8 100644 --- a/free-audit.html +++ b/free-audit.html @@ -193,6 +193,7 @@ diff --git a/index.html b/index.html index 0dcf959..07993b6 100644 --- a/index.html +++ b/index.html @@ -139,6 +139,7 @@ diff --git a/js/main.js b/js/main.js index 10893ec..2925542 100644 --- a/js/main.js +++ b/js/main.js @@ -18,13 +18,11 @@ 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]; @@ -40,17 +38,16 @@ document.getElementById('nav-toggle').addEventListener('click', function() { }); // Contact form - sends to backend API -var form = document.getElementById('contact-form'); -if (form) { - form.addEventListener('submit', function(e) { +var contactForm = document.getElementById('contact-form'); +if (contactForm) { + contactForm.addEventListener('submit', function(e) { e.preventDefault(); var status = document.getElementById('form-status'); - var submitBtn = form.querySelector('button[type="submit"]'); + var submitBtn = contactForm.querySelector('button[type="submit"]'); var originalBtnText = submitBtn.textContent; - // Collect form data - var formData = new FormData(form); + var formData = new FormData(contactForm); var data = { name: formData.get('name'), email: formData.get('email'), @@ -58,39 +55,31 @@ if (form) { 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(); + contactForm.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' - }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }) - .then(function(response) { - return response.json(); - }) + .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(); + contactForm.reset(); } else { throw new Error(result.error || 'Failed to send message'); } @@ -105,4 +94,64 @@ if (form) { submitBtn.textContent = originalBtnText; }); }); -} \ No newline at end of file +} + +// 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; + }); + }); +} diff --git a/pricing.html b/pricing.html index d44b522..bf0c3e4 100644 --- a/pricing.html +++ b/pricing.html @@ -158,6 +158,7 @@ diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..3a12cc5 --- /dev/null +++ b/robots.txt @@ -0,0 +1,5 @@ +User-agent: * +Allow: / +Disallow: /api/ + +Sitemap: https://krustyplanet.org/sitemap.xml diff --git a/scripts/sync-eepsite.sh b/scripts/sync-eepsite.sh new file mode 100755 index 0000000..174fa54 --- /dev/null +++ b/scripts/sync-eepsite.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# sync-eepsite.sh — Copy clearnet site to eepsite with appropriate modifications +# Usage: ./sync-eepsite.sh [--dry-run] +# +# The eepsite is a simplified version of the clearnet site: +# - No blog page (no blog content yet) +# - No free-audit page (audit form uses /api which isn't available on I2P) +# - Cross-reference links point to clearnet (not eepsite) and vice versa +# - Same JS/CSS +# - Same footer with I2P/clearnet cross-link + +set -euo pipefail + +CLEARNET="/var/www/krustyplanet" +EEPSITE="/var/www/eepsite" +DRY_RUN=false + +if [[ "${1:-}" == "--dry-run" ]]; then + DRY_RUN=true + echo "DRY RUN — no files will be modified" +fi + +# Pages that exist on both sites +SHARED_PAGES=("index.html" "services.html" "pricing.html" "contact.html") + +# Pages only on clearnet (these have backend API dependencies) +CLEARNET_ONLY=("blog.html" "free-audit.html") + +# Copy shared static assets +for dir in css js; do + echo "Syncing $dir/" + if $DRY_RUN; then + diff -rq "$CLEARNET/$dir/" "$EEPSITE/$dir/" || true + else + rsync -av --delete "$CLEARNET/$dir/" "$EEPSITE/$dir/" + fi +done + +# Copy favicon +echo "Syncing favicon.svg" +if ! $DRY_RUN; then + cp "$CLEARNET/favicon.svg" "$EEPSITE/favicon.svg" +fi + +# Sync shared pages with I2P-specific modifications +for page in "${SHARED_PAGES[@]}"; do + echo "Syncing $page" + if $DRY_RUN; then + echo " Would transform and copy $CLEARNET/$page -> $EEPSITE/$page" + else + # Start with clearnet version + cp "$CLEARNET/$page" "$EEPSITE/$page" + + # Replace clearnet I2P mirror link with clearnet reference link + sed -i 's|🔒 I2P Mirror:.*b32\.i2p

|🌐 Clearnet: https://krustyplanet.org

|' "$EEPSITE/$page" + + # Remove nav links to clearnet-only pages (free-audit, blog) + sed -i '/href="\/free-audit\.html"/d' "$EEPSITE/$page" + sed -i '/href="\/blog\.html"/d' "$EEPSITE/$page" + fi +done + +echo "Done. Eepsite is in sync with clearnet." +echo "" +echo "NOTE: The following pages are clearnet-only (require /api backend):" +for page in "${CLEARNET_ONLY[@]}"; do + echo " - $page" +done +echo "" +echo "NOTE: The eepsite contact form JS still posts to /api/contact" +echo " which won't work over I2P. Consider adding a mailto: fallback." diff --git a/services.html b/services.html index 71d9b7a..1d9ae76 100644 --- a/services.html +++ b/services.html @@ -83,6 +83,7 @@ diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..c9df466 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,33 @@ + + + + https://krustyplanet.org/ + weekly + 1.0 + + + https://krustyplanet.org/services.html + monthly + 0.8 + + + https://krustyplanet.org/pricing.html + monthly + 0.8 + + + https://krustyplanet.org/free-audit.html + monthly + 0.9 + + + https://krustyplanet.org/contact.html + monthly + 0.7 + + + https://krustyplanet.org/blog.html + weekly + 0.6 + +