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
This commit is contained in:
parent
8b0c095c50
commit
8a10eaaeb8
10 changed files with 187 additions and 66 deletions
48
blog.html
48
blog.html
|
|
@ -39,51 +39,8 @@
|
|||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="content is-medium">
|
||||
|
||||
<article class="mb-6">
|
||||
<h2 class="title is-4"><a href="/blog/2026/building-multi-agent-fleet.html" class="has-text-white">Building a Multi-Agent Fleet with OpenClaw</a></h2>
|
||||
<p class="has-text-grey-light mb-2">April 2026</p>
|
||||
<p class="has-text-grey-light">
|
||||
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.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="mb-6">
|
||||
<h2 class="title is-4"><a href="/blog/2026/ai-community-manager.html" class="has-text-white">Using AI as a Community Manager Without Alienating Your Community</a></h2>
|
||||
<p class="has-text-grey-light mb-2">April 2026</p>
|
||||
<p class="has-text-grey-light">
|
||||
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.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="mb-6">
|
||||
<h2 class="title is-4"><a href="/blog/2026/self-hosting-ai-privacy.html" class="has-text-white">Self-Hosting AI: What You Actually Gain (and What You Don't)</a></h2>
|
||||
<p class="has-text-grey-light mb-2">April 2026</p>
|
||||
<p class="has-text-grey-light">
|
||||
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.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="mb-6">
|
||||
<h2 class="title is-4"><a href="/blog/2026/i2p-ai-darknet.html" class="has-text-white">Running AI Services on I2P: A Practical Guide</a></h2>
|
||||
<p class="has-text-grey-light mb-2">April 2026</p>
|
||||
<p class="has-text-grey-light">
|
||||
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.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<div class="content is-medium has-text-centered">
|
||||
<p class="has-text-grey-light mt-6">Articles coming soon. Check back later.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -91,6 +48,7 @@
|
|||
<footer class="footer has-background-dark has-text-light">
|
||||
<div class="content has-text-centered">
|
||||
<p><strong>🌍 Krusty Planet</strong> — Privacy-focused AI consulting</p>
|
||||
<p class="is-size-7 mt-3"><span class="has-text-success">🔒 I2P Mirror:</span> <a href="http://g3xdiv7psi6y6l255kbkzowemsnd4tmbvy5ykaoajlu7oboma7zq.b32.i2p" style="color:#b5b5b5;" rel="nofollow noopener">g3xdiv7psi6y6l255kbkzowemsnd4tmbvy5ykaoajlu7oboma7zq.b32.i2p</a></p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@
|
|||
<footer class="footer has-background-dark has-text-light">
|
||||
<div class="content has-text-centered">
|
||||
<p><strong>🌍 Krusty Planet</strong> — Privacy-focused AI consulting</p>
|
||||
<p class="is-size-7 mt-3"><span class="has-text-success">🔒 I2P Mirror:</span> <a href="http://g3xdiv7psi6y6l255kbkzowemsnd4tmbvy5ykaoajlu7oboma7zq.b32.i2p" style="color:#b5b5b5;" rel="nofollow noopener">g3xdiv7psi6y6l255kbkzowemsnd4tmbvy5ykaoajlu7oboma7zq.b32.i2p</a></p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
|
|
|||
|
|
@ -193,6 +193,7 @@
|
|||
<footer class="footer has-background-dark has-text-light">
|
||||
<div class="content has-text-centered">
|
||||
<p><strong>🌍 Krusty Planet</strong> — Privacy-focused AI consulting</p>
|
||||
<p class="is-size-7 mt-3"><span class="has-text-success">🔒 I2P Mirror:</span> <a href="http://g3xdiv7psi6y6l255kbkzowemsnd4tmbvy5ykaoajlu7oboma7zq.b32.i2p" style="color:#b5b5b5;" rel="nofollow noopener">g3xdiv7psi6y6l255kbkzowemsnd4tmbvy5ykaoajlu7oboma7zq.b32.i2p</a></p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@
|
|||
<footer class="footer has-background-dark has-text-light">
|
||||
<div class="content has-text-centered">
|
||||
<p><strong>🌍 Krusty Planet</strong> — Privacy-focused AI consulting</p>
|
||||
<p class="is-size-7 mt-3"><span class="has-text-success">🔒 I2P Mirror:</span> <a href="http://g3xdiv7psi6y6l255kbkzowemsnd4tmbvy5ykaoajlu7oboma7zq.b32.i2p" style="color:#b5b5b5;" rel="nofollow noopener">g3xdiv7psi6y6l255kbkzowemsnd4tmbvy5ykaoajlu7oboma7zq.b32.i2p</a></p>
|
||||
<p class="is-size-7">Anonymous. Encrypted. Professional.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
|
|
|||
89
js/main.js
89
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');
|
||||
}
|
||||
|
|
@ -106,3 +95,63 @@ if (form) {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 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;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
<footer class="footer has-background-dark has-text-light">
|
||||
<div class="content has-text-centered">
|
||||
<p><strong>🌍 Krusty Planet</strong> — Privacy-focused AI consulting</p>
|
||||
<p class="is-size-7 mt-3"><span class="has-text-success">🔒 I2P Mirror:</span> <a href="http://g3xdiv7psi6y6l255kbkzowemsnd4tmbvy5ykaoajlu7oboma7zq.b32.i2p" style="color:#b5b5b5;" rel="nofollow noopener">g3xdiv7psi6y6l255kbkzowemsnd4tmbvy5ykaoajlu7oboma7zq.b32.i2p</a></p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
|
|
|||
5
robots.txt
Normal file
5
robots.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
User-agent: *
|
||||
Allow: /
|
||||
Disallow: /api/
|
||||
|
||||
Sitemap: https://krustyplanet.org/sitemap.xml
|
||||
71
scripts/sync-eepsite.sh
Executable file
71
scripts/sync-eepsite.sh
Executable file
|
|
@ -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</a></p>|🌐 Clearnet: <a href="https://krustyplanet.org" style="color:#b5b5b5;">https://krustyplanet.org</a></p>|' "$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."
|
||||
|
|
@ -83,6 +83,7 @@
|
|||
<footer class="footer has-background-dark has-text-light">
|
||||
<div class="content has-text-centered">
|
||||
<p><strong>🌍 Krusty Planet</strong> — Privacy-focused AI consulting</p>
|
||||
<p class="is-size-7 mt-3"><span class="has-text-success">🔒 I2P Mirror:</span> <a href="http://g3xdiv7psi6y6l255kbkzowemsnd4tmbvy5ykaoajlu7oboma7zq.b32.i2p" style="color:#b5b5b5;" rel="nofollow noopener">g3xdiv7psi6y6l255kbkzowemsnd4tmbvy5ykaoajlu7oboma7zq.b32.i2p</a></p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
|
|
|||
33
sitemap.xml
Normal file
33
sitemap.xml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://krustyplanet.org/</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://krustyplanet.org/services.html</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://krustyplanet.org/pricing.html</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://krustyplanet.org/free-audit.html</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://krustyplanet.org/contact.html</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://krustyplanet.org/blog.html</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
Loading…
Add table
Add a link
Reference in a new issue