10x every page: real interactions, kill fake content, wire everything

- ChatWidget: remove illegal seeds, real localStorage per-handle chat,
  honest bot replies about market/forum/funds
- ForumBoard: wire to real forumState (loadForum/addThread/vote), kill
  fake stats and illegal seed posts
- Home page: privacy features list reflects reality, footer links real
- Links: kill all alert() calls, replace fake onions with real clearnet
  privacy resources + internal route grid
- Support: per-coin copied state, env-driven addresses, real BTC addr
- Inner circle: wire to AccountContext, tier system from LUX balance,
  remove hardcoded admin/shadow credentials and fake trading signals
- Drop box: real sealed-note localStorage system, honest about no
  anonymous upload capability, real file picker with receipt
- Messages: fully functional per-handle localStorage chat, AI-style
  contextual bot replies, clear history, honest about local storage
- Wallets: pivot from fake PayPal accounts to Digital Access Passes,
  wire Buy Now to cart via ShopProduct interface
- Testimonials: wire submit form to localStorage, interactive star
  rating 1-10, display submitted reviews above the fold
- Raffle: use real merchant BTC address, real per-handle entry storage,
  honest LUX-only prize disclaimer, fix 0x address
- Drops/Lotto: real number picker 1-49 with Quick Pick, ticket
  submission, match display against drawn numbers, demo disclaimer
- Sanctuary: real 4-4-6-2 breathing timer, meditation passage with
  timer, candle-lighting with localStorage notes
- Game: full playable Void Pong with canvas physics, CPU AI, scoring,
  rally counter, localStorage high score
- Security analysis: honest architecture breakdown with real grades,
  layer-by-layer analysis, practical OPSEC guide, fiction banner
- Trust: compute real scores from actual localStorage data (LUX,
  USD, forum posts, testimonials), FAQ accordion

Made-with: Cursor
This commit is contained in:
drjones
2026-04-16 00:55:28 -07:00
parent 9da373a190
commit 2f928fbdc4
36 changed files with 3837 additions and 2354 deletions

View File

@@ -1,173 +1,42 @@
"use client";
import Link from "next/link";
import { useState } from "react";
/** Hub CTA — real provisioning lives on /sign-up (local vault + per-handle balance). */
const AccountCreation = () => {
const [step, setStep] = useState(1);
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [recoveryPhrase, setRecoveryPhrase] = useState("");
const [encryptionKey, setEncryptionKey] = useState("");
const [generated, setGenerated] = useState(false);
const generateRecovery = () => {
const phrases = [
"phantom quantum glacier nexus",
"crimson velvet paradox silence",
"neon abyss whisper eclipse",
"zero trace ghost protocol",
];
const randomPhrase = phrases[Math.floor(Math.random() * phrases.length)];
setRecoveryPhrase(randomPhrase);
setEncryptionKey(btoa(Date.now().toString()).slice(0, 16));
setGenerated(true);
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (step < 3) {
setStep(step + 1);
} else {
alert("Identity provisioned. Keys derived locally — backup your recovery phrase offline.");
}
};
return (
<div className="glass mx-auto max-w-2xl rounded-3xl border border-white/10 p-10">
<h2 className="mb-8 font-orbitron text-4xl font-bold text-center">CREATE SHADOW IDENTITY</h2>
{/* Steps */}
<div className="mb-10 flex justify-between">
{[1, 2, 3].map((s) => (
<div key={s} className="flex flex-col items-center">
<div
className={`flex h-12 w-12 items-center justify-center rounded-full border-2 text-lg font-bold ${step >= s
? "border-neon-cyan bg-neon-cyan/20 text-neon-cyan"
: "border-white/20 text-foreground/40"
}`}
>
{s}
</div>
<div className="mt-2 text-sm">
{s === 1 && "Credentials"}
{s === 2 && "Recovery"}
{s === 3 && "Encryption"}
</div>
</div>
))}
</div>
<form onSubmit={handleSubmit}>
{step === 1 && (
<div className="space-y-6">
<div>
<label className="mb-2 block font-medium">Shadow Alias</label>
<input
type="text"
className="glass w-full rounded-xl border border-white/10 bg-transparent p-4"
placeholder="e.g., Ghost_23"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
<p className="mt-2 text-sm text-foreground/60">Never your real name. This alias is encrypted.</p>
</div>
<div>
<label className="mb-2 block font-medium">ZeroKnowledge Password</label>
<input
type="password"
className="glass w-full rounded-xl border border-white/10 bg-transparent p-4"
placeholder="••••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
<p className="mt-2 text-sm text-foreground/60">We never store your password. Its used to derive your encryption key.</p>
</div>
</div>
)}
{step === 2 && (
<div className="space-y-6">
<div>
<label className="mb-2 block font-medium">Recovery Phrase</label>
<div className="glass rounded-xl border border-neon-cyan/30 p-6 font-mono text-center text-lg">
{generated ? recoveryPhrase : "Click generate to create"}
</div>
<button
type="button"
className="mt-4 w-full rounded-full bg-gradient-to-r from-neon-cyan to-neon-purple py-3 font-bold text-background"
onClick={generateRecovery}
>
GENERATE RECOVERY PHRASE
</button>
<p className="mt-4 text-sm text-foreground/60">
Write this down physically. Its the only way to recover your account. We do not store it.
</p>
</div>
</div>
)}
{step === 3 && (
<div className="space-y-6">
<div>
<label className="mb-2 block font-medium">Encryption Key</label>
<div className="glass rounded-xl border border-neon-green/30 p-6 font-mono text-center text-lg">
{encryptionKey || "Not generated"}
</div>
<p className="mt-4 text-sm text-foreground/60">
This key encrypts all your data locally. It is derived from your password and never leaves your device.
</p>
</div>
<div className="rounded-xl bg-gradient-to-r from-neon-cyan/10 to-neon-purple/10 p-6">
<h3 className="font-bold">Identity Summary</h3>
<div className="mt-4 grid grid-cols-2 gap-4 text-sm">
<div>Alias</div>
<div className="font-mono">{username || "—"}</div>
<div>Recovery Phrase</div>
<div className="font-mono truncate">{recoveryPhrase || "—"}</div>
<div>Encryption</div>
<div className="font-mono text-neon-green">AES256GCM</div>
</div>
</div>
</div>
)}
<div className="mt-10 flex justify-between">
<button
type="button"
className="glass rounded-full px-8 py-3 font-medium disabled:opacity-30"
onClick={() => setStep(step - 1)}
disabled={step === 1}
>
Back
</button>
<button
type="submit"
className="rounded-full bg-gradient-to-r from-neon-cyan to-neon-purple px-10 py-3 font-bold text-background"
>
{step === 3 ? "COMPLETE IDENTITY CREATION" : "CONTINUE →"}
</button>
</div>
</form>
<div className="mt-10 border-t border-white/10 pt-8 text-center text-sm text-foreground/40">
<p>Keys and aliases never leave this browser profile. Clear storage = identity loss.</p>
<p className="mt-4 text-foreground/55">
For a <strong className="text-neon-cyan/90">live stall session</strong> tied to wallet and activity feeds, use{" "}
<Link href="/sign-up" className="text-neon-cyan underline hover:text-neon-purple">
create account
</Link>{" "}
or{" "}
<Link href="/sign-in" className="text-neon-cyan underline hover:text-neon-purple">
sign in
</Link>
.
</p>
<h2 className="mb-4 text-center font-orbitron text-3xl font-bold md:text-4xl">Create an account</h2>
<p className="text-center text-foreground/70">
One handle for forum, exchange, barter, checkout, vault, and Bitcoin-funded USD balance on this origin.
Credentials stay in your browser; use{" "}
<Link href="/account/hidden-services" className="text-neon-cyan underline">
Hidden services
</Link>{" "}
to copy your identity to another .onion host.
</p>
<div className="mt-10 flex flex-col items-center justify-center gap-4 sm:flex-row">
<Link
href="/sign-up"
className="inline-block rounded-full bg-gradient-to-r from-neon-cyan to-neon-purple px-10 py-4 text-center font-bold text-background"
>
Register
</Link>
<Link
href="/sign-in"
className="inline-block rounded-full border border-white/20 px-10 py-4 text-center font-medium hover:border-neon-cyan/50"
>
Sign in
</Link>
</div>
<p className="mt-8 text-center text-sm text-foreground/50">
Add funds after sign-in:{" "}
<Link href="/account/add-funds" className="text-neon-green underline">
/account/add-funds
</Link>
</p>
</div>
);
};
export default AccountCreation;
export default AccountCreation;

View File

@@ -5,13 +5,13 @@ import { AccountProvider } from "@/contexts/AccountContext";
import { CartProvider } from "@/contexts/CartContext";
import { WalletProvider } from "@/contexts/WalletContext";
/** Client-only wrappers so wallet + persistent account work on every route. */
/** Account must wrap wallet so balance + vault ledger are keyed by the signed-in handle. */
export default function AppProviders({ children }: { children: ReactNode }) {
return (
<WalletProvider>
<AccountProvider>
<AccountProvider>
<WalletProvider>
<CartProvider>{children}</CartProvider>
</AccountProvider>
</WalletProvider>
</WalletProvider>
</AccountProvider>
);
}

View File

@@ -1,156 +1,195 @@
"use client";
import { useState, useEffect, useRef } from "react";
import { useState, useEffect, useRef, useCallback } from "react";
import Link from "next/link";
import { useAccount } from "@/contexts/AccountContext";
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" },
]);
type Msg = {
id: string;
sender: string;
text: string;
time: string;
mine: boolean;
};
const SEED_MSGS: Omit<Msg, "mine">[] = [
{ id: "s1", sender: "void_cartographer", text: "Anyone else notice the latency on the east relay dropped by 40ms this cycle?", time: "01:12" },
{ id: "s2", sender: "relay_op", text: "Circuit refresh interval was tuned last night. Should hold for 72h.", time: "01:14" },
{ id: "s3", sender: "ledger_moth", text: "New drops posted on /market — entropy dongles and the opsec consult bundle.", time: "01:17" },
{ id: "s4", sender: "phantom_q", text: "Verified the hub PGP against the mirror list. All checksums matched.", time: "01:19" },
{ id: "s5", sender: "void_cartographer", text: "Anyone have a rec for a good XMR <-> BTC bridge that doesn't log?", time: "01:22" },
{ id: "s6", sender: "relay_op", text: "Check /exchange — a few WTB listings went up in the last hour.", time: "01:24" },
];
const AUTO_REPLIES = [
"Received. Check your vault for any pending receipts.",
"Noted. The channel is ephemeral — nothing persists past this session unless you signed in.",
"Copy that. See /forum for threaded discussion on that topic.",
"Market is up. Check /drops for the latest scheduled releases.",
"Circuit healthy. Reply latency nominal.",
"Acknowledged. Keep OPSEC tight and verify mirrors before each session.",
];
const CHAT_KEY = "cyberlux-chat-v1";
function now() {
return new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
}
function uid() {
return Math.random().toString(36).slice(2, 10);
}
function loadHistory(): Msg[] {
if (typeof window === "undefined") return [];
try {
const raw = localStorage.getItem(CHAT_KEY);
if (!raw) return [];
return JSON.parse(raw) as Msg[];
} catch {
return [];
}
}
function saveHistory(msgs: Msg[]) {
if (typeof window === "undefined") return;
localStorage.setItem(CHAT_KEY, JSON.stringify(msgs.slice(-80)));
}
export default function ChatWidget() {
const { user } = useAccount();
const handle = user?.displayName ?? user?.username ?? "anon";
const [messages, setMessages] = useState<Msg[]>([]);
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);
const [hydrated, setHydrated] = useState(false);
const endRef = useRef<HTMLDivElement>(null);
useEffect(() => {
setEncryptionKey("session_" + Math.random().toString(36).substring(2, 9));
const stored = loadHistory();
if (stored.length > 0) {
setMessages(stored);
} else {
const seeded = SEED_MSGS.map((m) => ({ ...m, mine: false }));
setMessages(seeded);
saveHistory(seeded);
}
setHydrated(true);
}, []);
useEffect(() => {
if (chatContainerRef.current) {
chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight;
if (hydrated && messages.length > 0) {
saveHistory(messages);
endRef.current?.scrollIntoView({ behavior: "smooth" });
}
}, [messages]);
}, [messages, hydrated]);
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]);
const send = useCallback(() => {
const text = input.trim();
if (!text) return;
const mine: Msg = { id: uid(), sender: handle, text, time: now(), mine: true };
setMessages((p) => [...p, mine]);
setInput("");
// Simulate a reply after a delay
const delay = 900 + Math.random() * 900;
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.`);
};
const replyText = AUTO_REPLIES[Math.floor(Math.random() * AUTO_REPLIES.length)]!;
const reply: Msg = { id: uid(), sender: "System", text: replyText, time: now(), mine: false };
setMessages((p) => [...p, reply]);
}, delay);
}, [input, handle]);
return (
<div className="glass rounded-3xl border border-white/10 p-8">
<div className="mb-8 flex items-center justify-between">
<div className="mb-6 flex flex-wrap items-start justify-between gap-4">
<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>
<h2 className="font-orbitron text-3xl font-bold">CHANNEL CHAT</h2>
<p className="mt-1 text-sm text-foreground/60">
Ephemeral hub channel stored locally.{" "}
{user ? (
<span className="text-neon-cyan">Posting as @{user.username}</span>
) : (
<Link href="/sign-in" className="text-neon-cyan underline">
Sign in to tag your handle
</Link>
)}
</p>
</div>
<div className="flex gap-3 text-[10px] font-mono uppercase tracking-wider text-foreground/40">
<span className="flex items-center gap-1">
<span className="inline-block h-2 w-2 animate-pulse rounded-full bg-neon-green" />
channel active
</span>
<span>local store</span>
</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"
>
<div className="mb-4 h-72 overflow-y-auto rounded-2xl border border-white/10 bg-black/30 p-4 space-y-3">
{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}
<div key={msg.id} className={`flex flex-col ${msg.mine ? "items-end" : "items-start"}`}>
<div className="mb-0.5 flex items-center gap-2">
<span
className={`text-xs font-medium ${
msg.mine
? "text-neon-cyan"
: msg.sender === "System"
? "text-neon-purple"
: "text-foreground/70"
}`}
>
{msg.mine ? `@${handle}` : msg.sender === "System" ? "⚡ System" : `@${msg.sender}`}
</span>
<span className="text-xs text-foreground/40">{msg.time}</span>
<span className="text-[10px] text-foreground/30">{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"
}`}>
<div
className={`max-w-[80%] rounded-2xl px-4 py-2 text-sm ${
msg.mine
? "bg-neon-cyan/15 text-neon-cyan"
: msg.sender === "System"
? "bg-neon-purple/10 text-neon-purple/90"
: "bg-white/5 text-foreground/90"
}`}
>
{msg.text}
</div>
</div>
))}
<div ref={endRef} />
</div>
{/* Input area */}
<div className="flex gap-4">
<div className="flex gap-3">
<input
type="text"
className="glass flex-1 rounded-2xl border border-white/10 bg-transparent px-6 py-4"
placeholder="Type your encrypted message..."
className="flex-1 rounded-2xl border border-white/10 bg-black/40 px-5 py-3 text-sm focus:border-neon-cyan/40 focus:outline-none"
placeholder={user ? `Message as @${user.username}` : "Message the channel…"}
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleSend()}
onKeyDown={(e) => e.key === "Enter" && send()}
maxLength={500}
/>
<button
onClick={handleSend}
className="rounded-full bg-gradient-to-r from-neon-cyan to-neon-purple px-8 py-4 font-bold text-background"
type="button"
onClick={send}
disabled={!input.trim()}
className="rounded-2xl bg-gradient-to-r from-neon-cyan to-neon-purple px-6 py-3 text-sm font-bold text-background disabled:opacity-40"
>
SEND
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 className="mt-6 grid grid-cols-2 gap-4 md:grid-cols-4">
{[
{ icon: "🔒", label: "Client storage", sub: "Never leaves your browser" },
{ icon: "💬", label: "Open channel", sub: "Hub-wide thread" },
{ icon: "📬", label: "Forum threads", sub: "/forum for persistence" },
{ icon: "📦", label: "Market drops", sub: "/drops for schedule" },
].map((f) => (
<div key={f.label} className="glass rounded-2xl border border-white/10 p-4 text-center">
<div className="text-2xl">{f.icon}</div>
<div className="mt-2 text-sm font-bold">{f.label}</div>
<div className="text-xs text-foreground/50">{f.sub}</div>
</div>
))}
</div>
</div>
);
};
export default ChatWidget;
}

View File

@@ -2,6 +2,7 @@
import Link from "next/link";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useAccount } from "@/contexts/AccountContext";
import { useCart } from "@/contexts/CartContext";
import { useWallet } from "@/contexts/WalletContext";
import { getMerchantBtcAddress, isMerchantBtcConfigured } from "@/lib/merchantBtc";
@@ -17,7 +18,6 @@ const CheckoutFlow = () => {
const { lines, setLineQty, removeLine, clearCart, hydrated: cartHydrated } = useCart();
const [step, setStep] = useState(1);
const [paymentMethod, setPaymentMethod] = useState<"crypto" | "guest" | "card">("crypto");
const [cryptoType, setCryptoType] = useState<"BTC" | "ETH" | "XMR">("BTC");
const [guestEmail, setGuestEmail] = useState("");
const [encryptionLevel, setEncryptionLevel] = useState<"standard" | "enhanced" | "quantum">("enhanced");
const [btcUsd, setBtcUsd] = useState<number | null>(null);
@@ -26,8 +26,10 @@ const CheckoutFlow = () => {
const [verifyBusy, setVerifyBusy] = useState(false);
const [payError, setPayError] = useState<string | null>(null);
const [orderComplete, setOrderComplete] = useState(false);
const [completedOrderId, setCompletedOrderId] = useState<string | null>(null);
const { usdStoreCredit, spendUsdStoreCredit, verifyBtcDeposit } = useWallet();
const { user } = useAccount();
const { usdStoreCredit, spendUsdStoreCredit, verifyBtcDeposit, earnLuxCredits, addVaultReceipt } = useWallet();
useEffect(() => {
let cancelled = false;
@@ -107,14 +109,34 @@ const CheckoutFlow = () => {
const tryCompleteOrder = () => {
setPayError(null);
if (paymentMethod === "crypto") {
if (!user) {
setPayError("Sign in to charge your verified Bitcoin-funded USD balance.");
return;
}
if (!spendUsdStoreCredit(orderTotalUsd)) {
setPayError(
`Insufficient USD balance. You need $${orderTotalUsd.toFixed(2)}. Send Bitcoin to the address below, wait for at least one confirmation, then paste the transaction ID and click Verify.`,
`Insufficient USD balance. You need $${orderTotalUsd.toFixed(2)}. Add funds under Account → Add funds, then verify your txid.`,
);
return;
}
}
const oid =
typeof crypto !== "undefined" && "randomUUID" in crypto
? `CYBR-${crypto.randomUUID().slice(0, 8).toUpperCase()}`
: `CYBR-${Date.now().toString(36).toUpperCase()}`;
setCompletedOrderId(oid);
setOrderComplete(true);
if (user && paymentMethod === "crypto") {
const lux = Math.min(500, Math.max(25, Math.floor(orderTotalUsd * 2)));
earnLuxCredits(lux);
addVaultReceipt({
id: oid,
title: `Order ${oid}`,
desc: `Checkout ${orderTotalUsd.toFixed(2)} USD · ${encryptionLevel} tier.`,
date: new Date().toISOString().slice(0, 10),
severity: "normal",
});
}
clearCart();
};
@@ -251,9 +273,17 @@ const CheckoutFlow = () => {
<span className="font-orbitron">${usdStoreCredit.toFixed(2)}</span>
<span className="text-foreground/60">
{" "}
use after Bitcoin deposit is verified (on-chain).
per signed-in handle after verified Bitcoin deposits.
</span>
</div>
{paymentMethod === "crypto" && !user ? (
<p className="mb-4 rounded-lg border border-amber-500/30 bg-amber-500/10 px-4 py-3 text-sm text-amber-200">
<Link href="/sign-in?next=/checkout" className="font-bold underline">
Sign in
</Link>{" "}
to spend USD balance. You can still browse deposit instructions below.
</p>
) : null}
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
<button
type="button"
@@ -273,7 +303,7 @@ const CheckoutFlow = () => {
>
<div className="mb-4 text-4xl">👤</div>
<div className="font-bold">Guest Checkout</div>
<div className="mt-2 text-sm text-foreground/60">Demo no USD charge</div>
<div className="mt-2 text-sm text-foreground/60">No USD charge order completes locally only</div>
</button>
<button
type="button"
@@ -282,52 +312,29 @@ const CheckoutFlow = () => {
onClick={() => setPaymentMethod("card")}
>
<div className="mb-4 text-4xl">💳</div>
<div className="font-bold">Card (Secure)</div>
<div className="mt-2 text-sm text-foreground/60">Demo no USD charge</div>
<div className="font-bold">Card</div>
<div className="mt-2 text-sm text-foreground/60">Not enabled use Bitcoin balance instead</div>
</button>
</div>
{paymentMethod === "crypto" && (
<div className="mt-8">
<label className="mb-2 block font-medium">Select Cryptocurrency</label>
<div className="flex gap-4">
{(["BTC", "ETH", "XMR"] as const).map((coin) => (
<button
key={coin}
type="button"
className={`glass flex-1 rounded-xl border py-4 ${cryptoType === coin ? "border-neon-cyan bg-neon-cyan/10" : "border-white/10"
}`}
onClick={() => setCryptoType(coin)}
>
<div className="text-2xl">{coin === "BTC" ? "₿" : coin === "ETH" ? "Ξ" : "⏣"}</div>
<div className="font-bold">{coin}</div>
</button>
))}
<div className="mt-8 space-y-4 rounded-xl bg-white/5 p-4">
<div className="text-sm text-foreground/60">Bitcoin-only · estimated order total after encryption step</div>
<div className="font-orbitron text-2xl text-neon-cyan">
${orderTotalUsd.toLocaleString()} USD
{orderTotalBtc != null && btcUsd != null && (
<span className="ml-3 text-lg text-foreground/70">
( {orderTotalBtc} BTC @ ${btcUsd.toLocaleString()}/BTC)
</span>
)}
</div>
{cryptoType !== "BTC" && (
<p className="mt-4 rounded-lg bg-white/5 p-4 text-sm text-foreground/70">
Deposits that add USD balance use <strong>Bitcoin</strong> only. Switch to BTC to see the
deposit address. (ETH/XMR shown for display.)
</p>
)}
{cryptoType === "BTC" && (
<div className="mt-6 space-y-4 rounded-xl bg-white/5 p-4">
<div className="text-sm text-foreground/60">Estimated order total after encryption step</div>
<div className="font-orbitron text-2xl text-neon-cyan">
${orderTotalUsd.toLocaleString()} USD
{orderTotalBtc != null && btcUsd != null && (
<span className="ml-3 text-lg text-foreground/70">
( {orderTotalBtc} BTC @ ${btcUsd.toLocaleString()}/BTC)
</span>
)}
</div>
<p className="text-xs text-foreground/50">
Final total includes the encryption add-on you pick in the next step. Fund your balance
with <strong>any amount</strong> of BTC; verified value is credited in USD at spot rate.
You can pay this order once your balance covers the total.
</p>
</div>
)}
<p className="text-xs text-foreground/50">
Final total includes the encryption add-on you pick in the next step. Add funds on{" "}
<Link href="/account/add-funds" className="text-neon-cyan underline">
/account/add-funds
</Link>{" "}
(same balance here and at checkout).
</p>
</div>
)}
@@ -342,13 +349,13 @@ const CheckoutFlow = () => {
onChange={(e) => setGuestEmail(e.target.value)}
/>
<p className="mt-2 text-sm text-foreground/60">
Guest checkout is a demo and does not charge your USD balance.
Guest checkout does not touch your USD balance; nothing is sent to a payment processor.
</p>
</div>
)}
<div className="mt-10 border-t border-white/10 pt-8">
<h4 className="mb-4 font-orbitron text-lg font-bold text-neon-cyan">Bitcoin deposit (all checkouts)</h4>
<h4 className="mb-4 font-orbitron text-lg font-bold text-neon-cyan">Bitcoin deposit · processor</h4>
{!merchantReady ? (
<p className="text-sm text-amber-300/90">
Set <code className="rounded bg-white/10 px-1">NEXT_PUBLIC_MERCHANT_BTC_ADDRESS</code> and{" "}
@@ -358,8 +365,12 @@ const CheckoutFlow = () => {
) : (
<>
<p className="mb-3 text-sm text-foreground/70">
Send Bitcoin to this address. After the network confirms your payment (at least one
block), paste the transaction ID to add the equivalent USD to your balance.
Send Bitcoin to this address. After at least one confirmation, paste the txid credits apply
to the signed-in handle only (see{" "}
<Link href="/account/add-funds" className="text-neon-cyan underline">
Add funds
</Link>
).
</p>
<div className="rounded-xl border border-white/10 bg-black/40 p-4 font-mono text-sm break-all">
{merchantAddr}
@@ -471,7 +482,7 @@ const CheckoutFlow = () => {
<div className="mt-2 flex justify-between text-sm">
<span className="text-foreground/60">Payment path</span>
<span className="font-bold">
{paymentMethod === "crypto" ? "Crypto (USD balance)" : "Demo (guest/card)"}
{paymentMethod === "crypto" ? "Bitcoin-funded USD balance" : "Guest / card (local only)"}
</span>
</div>
</div>
@@ -481,7 +492,7 @@ const CheckoutFlow = () => {
onClick={tryCompleteOrder}
className="rounded-full bg-gradient-to-r from-neon-cyan to-neon-purple px-10 py-3 font-bold text-background"
>
{paymentMethod === "crypto" ? "Charge my USD balance & complete" : "Complete demo order"}
{paymentMethod === "crypto" ? "Charge my USD balance & complete" : "Complete order (local)"}
</button>
{paymentMethod === "crypto" && (
<p className="mx-auto mt-6 max-w-xl text-xs text-foreground/50">
@@ -506,7 +517,9 @@ const CheckoutFlow = () => {
</p>
<div className="my-10">
<div className="glass mx-auto max-w-md rounded-2xl border border-white/10 p-6">
<div className="font-orbitron text-2xl font-bold text-neon-cyan">ORDER #CYBR9A2FB8E1</div>
<div className="font-orbitron text-2xl font-bold text-neon-cyan">
ORDER #{completedOrderId ?? "—"}
</div>
<div className="mt-4 grid grid-cols-2 gap-4 text-sm">
<div className="text-left text-foreground/60">Amount</div>
<div className="text-right font-bold">${orderTotalUsd.toLocaleString()} USD</div>
@@ -514,7 +527,7 @@ const CheckoutFlow = () => {
<div className="text-right font-bold">{encryptionLevel.toUpperCase()}</div>
<div className="text-left text-foreground/60">Payment</div>
<div className="truncate text-right font-mono text-neon-green">
{paymentMethod === "crypto" ? "USD balance (BTC-funded)" : "Demo guest/card"}
{paymentMethod === "crypto" ? "USD balance (BTC-funded)" : "Guest or card (no processor)"}
</div>
</div>
</div>

View File

@@ -1,220 +1,168 @@
"use client";
import Link from "next/link";
import { useState } from "react";
import { useState, useEffect } from "react";
import { useAccount } from "@/contexts/AccountContext";
import {
loadForum,
addThread,
displayScore,
setVoteForThread,
getVoteForThread,
type ForumThread,
} from "@/lib/forumState";
const ForumBoard = () => {
const [posts, setPosts] = useState([
{
id: 1,
user: "GhostInTheShell",
avatar: "👻",
timestamp: "2 hours ago",
content: "Has anyone tested the new neural stimulant? Need reports on sideeffects.",
upvotes: 42,
replies: 12,
category: "BioEnhancements",
},
{
id: 2,
user: "ZeroCool",
avatar: "🕵️",
timestamp: "5 hours ago",
content: "Quantum counterfeit notes batch #8 passes all validation tests. Available for bulk orders.",
upvotes: 89,
replies: 24,
category: "Financial",
},
{
id: 3,
user: "CypherPunk",
avatar: "🔐",
timestamp: "1 day ago",
content: "New darknet router vulnerability discovered. Patch your firmware immediately.",
upvotes: 156,
replies: 47,
category: "Security",
},
{
id: 4,
user: "Ethereal",
avatar: "🌌",
timestamp: "2 days ago",
content: "Looking for reliable identity pack vendor with EU passports. DM with reputation score.",
upvotes: 33,
replies: 8,
category: "Identity",
},
]);
const CATEGORIES = ["All", "Security", "Market", "OPSEC", "Tech", "General"];
export default function ForumBoard() {
const { user } = useAccount();
const [threads, setThreads] = useState<ForumThread[]>([]);
const [newPost, setNewPost] = useState("");
const [selectedCategory, setSelectedCategory] = useState("All");
const [selectedCat, setSelectedCat] = useState("All");
const [voteBump, setVoteBump] = useState(0);
const [posting, setPosting] = useState(false);
const categories = ["All", "BioEnhancements", "Financial", "Security", "Identity", "Weapons", "Chemicals"];
useEffect(() => {
setThreads(loadForum().slice(0, 8));
}, []);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!newPost.trim()) return;
const newPostObj = {
id: posts.length + 1,
user: "Anonymous",
avatar: "🕶️",
timestamp: "Just now",
content: newPost,
upvotes: 0,
replies: 0,
category: "Uncategorized",
};
setPosts([newPostObj, ...posts]);
if (!newPost.trim() || posting) return;
setPosting(true);
const thread = addThread({
title: newPost.slice(0, 80),
body: newPost,
author: user?.username ?? "anon",
topicSlug: "general",
category: "General",
replies: [],
});
setThreads([thread, ...threads].slice(0, 8));
setNewPost("");
setPosting(false);
};
const filteredPosts = selectedCategory === "All"
? posts
: posts.filter(p => p.category === selectedCategory);
const handleVote = (id: string, delta: 1 | -1) => {
const existing = getVoteForThread(id);
const next = existing === delta ? 0 : delta;
setVoteForThread(id, next as 1 | -1 | 0);
setVoteBump((v) => v + 1);
};
const visible = selectedCat === "All"
? threads
: threads.filter((t) => t.category === selectedCat || t.topicSlug === selectedCat.toLowerCase());
return (
<div className="glass rounded-3xl border border-white/10 p-8">
<div className="mb-10">
<h2 className="font-orbitron text-4xl font-bold">DARKNET FORUM</h2>
<p className="mt-2 text-foreground/70">Encrypted, anonymous discussions. No logs, no traces.</p>
<div className="mb-8 flex flex-wrap items-start justify-between gap-4">
<div>
<h2 className="font-orbitron text-3xl font-bold">VOID AGGREGATE</h2>
<p className="mt-1 text-sm text-foreground/60">
{threads.length} threads shown · full board at{" "}
<Link href="/forum" className="text-neon-cyan hover:underline">/forum</Link>
</p>
</div>
</div>
{/* Category filter */}
<div className="mb-8 flex flex-wrap gap-3">
{categories.map((cat) => (
<div className="mb-6 flex flex-wrap gap-2">
{CATEGORIES.map((cat) => (
<button
key={cat}
className={`rounded-full px-4 py-2 text-sm font-medium transition-all ${selectedCategory === cat
type="button"
onClick={() => setSelectedCat(cat)}
className={`rounded-full px-4 py-1.5 text-xs font-medium transition-all ${
selectedCat === cat
? "bg-gradient-to-r from-neon-cyan to-neon-purple text-background"
: "glass border border-white/10"
}`}
onClick={() => setSelectedCategory(cat)}
: "glass border border-white/10 hover:border-neon-cyan/30"
}`}
>
{cat}
</button>
))}
</div>
{/* New post form */}
<form onSubmit={handleSubmit} className="mb-10">
<div className="glass rounded-2xl border border-white/10 p-6">
{/* Quick post form */}
<form onSubmit={handleSubmit} className="mb-8">
<div className="glass rounded-2xl border border-white/10 p-5">
<textarea
className="w-full bg-transparent text-foreground placeholder-foreground/50 focus:outline-none"
rows={3}
placeholder="Type your encrypted message... (all posts are ephemeral)"
className="w-full bg-transparent text-sm text-foreground placeholder-foreground/40 focus:outline-none"
rows={2}
placeholder={user ? `Post as @${user.username}` : "Quick post (stored in your browser)…"}
value={newPost}
onChange={(e) => setNewPost(e.target.value)}
maxLength={500}
/>
<div className="mt-4 flex items-center justify-between">
<div className="flex gap-4">
<button
type="button"
className="flex items-center gap-2 rounded-full bg-white/5 px-4 py-2 text-sm"
>
<span>🔒</span> Encrypt
</button>
<button
type="button"
className="flex items-center gap-2 rounded-full bg-white/5 px-4 py-2 text-sm"
>
<span>🕵</span> Post Anonymously
</button>
</div>
<div className="mt-3 flex items-center justify-between">
<span className="text-[10px] text-foreground/30">{newPost.length}/500 · local storage</span>
<button
type="submit"
className="rounded-full bg-gradient-to-r from-neon-cyan to-neon-purple px-6 py-3 font-bold text-background"
disabled={!newPost.trim() || posting}
className="rounded-full bg-gradient-to-r from-neon-cyan to-neon-purple px-5 py-2 text-sm font-bold text-background disabled:opacity-40"
>
PUBLISH
Post
</button>
</div>
</div>
</form>
{/* Posts list */}
<div className="space-y-6">
{filteredPosts.map((post) => (
<div key={post.id} className="glass rounded-2xl border border-white/10 p-6">
<div className="flex items-start justify-between">
<div className="flex items-center gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-gradient-to-br from-neon-cyan to-neon-purple text-2xl">
{post.avatar}
</div>
<div>
{/* Thread list */}
<div className="space-y-4">
{visible.length === 0 ? (
<p className="py-6 text-center text-sm text-foreground/40">No threads in this category yet.</p>
) : (
visible.map((t) => {
const score = displayScore(t);
const voted = typeof window !== "undefined" ? getVoteForThread(t.id) : 0;
return (
<div key={t.id + voteBump} className="glass rounded-2xl border border-white/10 p-5">
<div className="flex items-start justify-between gap-3">
<div className="flex items-center gap-3">
<span className="font-bold">{post.user}</span>
<span className="rounded-full bg-white/5 px-3 py-1 text-xs">{post.category}</span>
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-gradient-to-br from-neon-cyan/20 to-neon-purple/20 text-sm font-bold">
{(t.author ?? "?").charAt(0).toUpperCase()}
</div>
<div>
<div className="flex flex-wrap items-center gap-2">
<span className="text-sm font-bold">@{t.author ?? "anon"}</span>
{t.category && (
<span className="rounded-full bg-white/5 px-2 py-0.5 text-[10px]">{t.category}</span>
)}
</div>
<div className="text-xs text-foreground/40">{new Date(t.ts).toLocaleDateString()}</div>
</div>
</div>
<div className="text-sm text-foreground/60">{post.timestamp}</div>
<div className="flex shrink-0 items-center gap-3">
<button
type="button"
onClick={() => handleVote(t.id, 1)}
className={`text-xs transition-colors ${voted === 1 ? "text-neon-cyan" : "text-foreground/40 hover:text-neon-cyan"}`}
>
{score}
</button>
<span className="text-xs text-foreground/30">💬 {t.replies.length}</span>
</div>
</div>
<div className="mt-3">
<Link href="/forum" className="text-sm hover:text-neon-cyan/80">
{t.title ?? t.body?.slice(0, 100)}
</Link>
</div>
</div>
<div className="flex items-center gap-4">
<button className="flex items-center gap-2 text-sm text-foreground/60 hover:text-neon-cyan">
<span></span> {post.upvotes}
</button>
<button className="flex items-center gap-2 text-sm text-foreground/60 hover:text-neon-cyan">
<span>💬</span> {post.replies}
</button>
<button className="rounded-full bg-white/5 p-2 hover:bg-white/10">
<span>🔗</span>
</button>
</div>
</div>
<div className="mt-6">
<p>{post.content}</p>
</div>
<div className="mt-6 flex items-center gap-6 border-t border-white/10 pt-6">
<button className="flex items-center gap-2 text-sm">
<span></span> Upvote
</button>
<button className="flex items-center gap-2 text-sm">
<span></span> Downvote
</button>
<button className="flex items-center gap-2 text-sm">
<span>💬</span> Reply
</button>
<button className="flex items-center gap-2 text-sm">
<span>🔐</span> Encrypt Reply
</button>
<button className="flex items-center gap-2 text-sm">
<span>🚀</span> Boost
</button>
</div>
</div>
))}
);
})
)}
</div>
{/* Forum stats */}
<div className="mt-10 grid grid-cols-2 gap-6 md:grid-cols-4">
<div className="glass rounded-2xl border border-white/10 p-6 text-center">
<div className="text-3xl font-bold text-neon-cyan">2.4k</div>
<div className="text-sm text-foreground/60">Active Users</div>
</div>
<div className="glass rounded-2xl border border-white/10 p-6 text-center">
<div className="text-3xl font-bold text-neon-purple">18.5k</div>
<div className="text-sm text-foreground/60">Total Posts</div>
</div>
<div className="glass rounded-2xl border border-white/10 p-6 text-center">
<div className="text-3xl font-bold text-neon-pink">94%</div>
<div className="text-sm text-foreground/60">Encrypted</div>
</div>
<div className="glass rounded-2xl border border-white/10 p-6 text-center">
<div className="text-3xl font-bold text-neon-green">0</div>
<div className="text-sm text-foreground/60">Data Leaks</div>
</div>
</div>
<div className="mt-8 text-center text-xs text-foreground/40">
<p>
Preview board for threads that persist in your browser, open the{" "}
<Link href="/forum" className="text-neon-cyan hover:underline">
full forum
</Link>{" "}
(dedicated .onion maps here too).
</p>
<div className="mt-6 text-center">
<Link
href="/forum"
className="inline-flex items-center gap-2 rounded-full border border-neon-cyan/30 px-6 py-2 text-sm font-bold text-neon-cyan hover:bg-neon-cyan/10"
>
Open full forum
</Link>
</div>
</div>
);
};
export default ForumBoard;
}

View File

@@ -8,7 +8,7 @@ import { useCart } from "@/contexts/CartContext";
const Navbar = () => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const { isConnected, address, connect, disconnect, luxCredits, usdStoreCredit } = useWallet();
const { luxCredits, usdStoreCredit } = useWallet();
const { user, hydrated } = useAccount();
const { itemCount, hydrated: cartReady } = useCart();
@@ -26,17 +26,10 @@ const Navbar = () => {
{ label: "Syndicate", href: "/syndicate", icon: "🕸️" },
{ label: "Void crawl", href: "/search", icon: "🔦" },
{ label: "Mirrors", href: "/account/hidden-services", icon: "🧅" },
{ label: "Add funds", href: "/account/add-funds", icon: "₿" },
{ label: "Dark web launch", href: "/launch", icon: "🔥" },
];
const handleWalletClick = () => {
if (isConnected) {
disconnect();
} else {
connect();
}
};
return (
<nav className="glass fixed top-0 left-0 right-0 z-50 mx-auto mt-4 max-w-7xl rounded-2xl border border-white/10 px-6 py-4 backdrop-blur-xl">
<div className="flex items-center justify-between">
@@ -104,12 +97,20 @@ const Navbar = () => {
</div>
{hydrated ? (
user ? (
<Link
href="/dashboard"
className="hidden items-center gap-2 rounded-full border border-neon-cyan/30 bg-neon-cyan/10 px-4 py-2 text-sm font-medium text-neon-cyan hover:bg-neon-cyan/20 sm:flex"
>
<span className="max-w-[8rem] truncate">{user.displayName}</span>
</Link>
<>
<Link
href="/account/add-funds"
className="hidden rounded-full border border-neon-green/35 bg-neon-green/10 px-3 py-2 text-xs font-bold uppercase tracking-wide text-neon-green hover:bg-neon-green/15 sm:inline-flex"
>
Add funds
</Link>
<Link
href="/dashboard"
className="hidden items-center gap-2 rounded-full border border-neon-cyan/30 bg-neon-cyan/10 px-4 py-2 text-sm font-medium text-neon-cyan hover:bg-neon-cyan/20 sm:flex"
>
<span className="max-w-[8rem] truncate">{user.displayName}</span>
</Link>
</>
) : (
<>
<Link
@@ -127,16 +128,6 @@ const Navbar = () => {
</>
)
) : null}
<button
onClick={handleWalletClick}
className={`flex items-center gap-2 rounded-full px-4 py-2 font-medium transition-all ${isConnected
? "bg-gradient-to-r from-neon-green to-neon-cyan text-background"
: "glow-border text-neon-cyan hover:bg-white/5"
}`}
>
<span className="text-lg">{isConnected ? "🟢" : "⚡"}</span>
{isConnected ? `${address?.slice(0, 6)}...${address?.slice(-4)}` : "Connect Wallet"}
</button>
<button
className="rounded-full bg-white/5 p-2 hover:bg-white/10"
aria-label="Toggle menu"