Harden onion boot flow and deepen site surfaces

Add persistent onion key backup and restore, improve startup resilience, and flesh out the major site verticals with richer navigation, search coverage, and operator documentation.

Made-with: Cursor
This commit is contained in:
drjones
2026-04-07 21:35:52 -07:00
parent 52432dccfa
commit 78a071ba02
162 changed files with 21692 additions and 39 deletions

156
components/ChatWidget.tsx Normal file
View File

@@ -0,0 +1,156 @@
"use client";
import { useState, useEffect, useRef } from "react";
const ChatWidget = () => {
const [messages, setMessages] = useState([
{ id: 1, sender: "System", text: "Welcome to the encrypted channel. All messages are endtoend encrypted.", time: "12:00" },
{ id: 2, sender: "Ghost", text: "Has anyone tested the new stimulant batch?", time: "12:05" },
{ id: 3, sender: "Vendor_X", text: "Batch #8 passes all purity tests. Available for bulk.", time: "12:07" },
{ id: 4, sender: "Anonymous", text: "Need a EU passport within 48 hours. DM if you can deliver.", time: "12:10" },
]);
const [input, setInput] = useState("");
/** Set after mount so SSR HTML matches first client paint (no Math.random in initial state). */
const [encryptionKey, setEncryptionKey] = useState("");
const chatContainerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
setEncryptionKey("session_" + Math.random().toString(36).substring(2, 9));
}, []);
useEffect(() => {
if (chatContainerRef.current) {
chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight;
}
}, [messages]);
const handleSend = () => {
if (!input.trim()) return;
const newMessage = {
id: messages.length + 1,
sender: "You",
text: input,
time: new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }),
};
setMessages([...messages, newMessage]);
setInput("");
// Simulate a reply after a delay
setTimeout(() => {
const replies = [
"Received. Encryption verified.",
"I can help with that. Check your vault.",
"New drop incoming in 24 hours.",
"Your request has been logged.",
];
const randomReply = replies[Math.floor(Math.random() * replies.length)];
const replyMessage = {
id: messages.length + 2,
sender: "System",
text: randomReply,
time: new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }),
};
setMessages(prev => [...prev, replyMessage]);
}, 1000);
};
const handleEncrypt = () => {
alert(`Session key: ${encryptionKey}\nAll messages are encrypted with this key.`);
};
return (
<div className="glass rounded-3xl border border-white/10 p-8">
<div className="mb-8 flex items-center justify-between">
<div>
<h2 className="font-orbitron text-3xl font-bold">ENCRYPTED CHAT</h2>
<p className="text-foreground/60">Endtoend encrypted messaging. No logs, no traces.</p>
</div>
<button
onClick={handleEncrypt}
className="rounded-full bg-gradient-to-r from-neon-cyan to-neon-purple px-6 py-3 font-bold text-background"
>
🔐 SHOW SESSION KEY
</button>
</div>
{/* Chat messages */}
<div
ref={chatContainerRef}
className="glass mb-6 h-96 overflow-y-auto rounded-2xl border border-white/10 p-6"
>
{messages.map((msg) => (
<div
key={msg.id}
className={`mb-4 ${msg.sender === "You" ? "text-right" : ""}`}
>
<div className="mb-1 flex items-center gap-2">
<span className={`inline-block rounded-full px-3 py-1 text-xs font-medium ${msg.sender === "You"
? "bg-neon-cyan/20 text-neon-cyan"
: msg.sender === "System"
? "bg-neon-purple/20 text-neon-purple"
: "bg-white/10"
}`}>
{msg.sender}
</span>
<span className="text-xs text-foreground/40">{msg.time}</span>
</div>
<div className={`glass inline-block max-w-[80%] rounded-2xl px-4 py-3 ${msg.sender === "You"
? "bg-gradient-to-r from-neon-cyan/20 to-neon-cyan/10"
: "bg-white/5"
}`}>
{msg.text}
</div>
</div>
))}
</div>
{/* Input area */}
<div className="flex gap-4">
<input
type="text"
className="glass flex-1 rounded-2xl border border-white/10 bg-transparent px-6 py-4"
placeholder="Type your encrypted message..."
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleSend()}
/>
<button
onClick={handleSend}
className="rounded-full bg-gradient-to-r from-neon-cyan to-neon-purple px-8 py-4 font-bold text-background"
>
SEND
</button>
</div>
{/* Chat features */}
<div className="mt-8 grid grid-cols-2 gap-6 md:grid-cols-4">
<div className="glass rounded-2xl border border-white/10 p-4 text-center">
<div className="text-2xl">🔒</div>
<div className="mt-2 text-sm font-bold">AES256GCM</div>
<div className="text-xs text-foreground/60">Encryption</div>
</div>
<div className="glass rounded-2xl border border-white/10 p-4 text-center">
<div className="text-2xl"></div>
<div className="mt-2 text-sm font-bold">ZeroKnowledge</div>
<div className="text-xs text-foreground/60">No server storage</div>
</div>
<div className="glass rounded-2xl border border-white/10 p-4 text-center">
<div className="text-2xl">🌐</div>
<div className="mt-2 text-sm font-bold">WebRTC</div>
<div className="text-xs text-foreground/60">P2P possible</div>
</div>
<div className="glass rounded-2xl border border-white/10 p-4 text-center">
<div className="text-2xl">🕶</div>
<div className="mt-2 text-sm font-bold">SelfDestruct</div>
<div className="text-xs text-foreground/60">Messages expire</div>
</div>
</div>
<div className="mt-8 text-center text-xs text-foreground/40">
<p>Ephemeral client-side queue nothing leaves this browser profile until a real transport is configured.</p>
</div>
</div>
);
};
export default ChatWidget;