Files
dark-lord/app/search/page.tsx
drjones 78a071ba02 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
2026-04-07 21:35:52 -07:00

218 lines
9.0 KiB
TypeScript

"use client";
import Link from "next/link";
import { FormEvent, useMemo, useState } from "react";
import {
CYBERLUX_SEARCH_INDEX_SIZE,
searchCyberluxIndex,
searchZoneLabel,
VOID_LEXICON_WORDS,
type CyberluxSearchDoc,
} from "@/lib/cyberluxSearchIndex";
const LEXICON_FOG_SAMPLE = VOID_LEXICON_WORDS.slice(0, 96);
function keywordSample(doc: CyberluxSearchDoc, limit = 20): string[] {
const seen = new Set<string>();
const out: string[] = [];
for (const w of doc.keywords.split(/\s+/)) {
const t = w.trim().toLowerCase();
if (t.length < 3 || seen.has(t)) continue;
seen.add(t);
out.push(t);
if (out.length >= limit) break;
}
return out;
}
/** Fictional v3 hostnames — one per deployment “node”; paths are real in-app routes. */
const ZONE_ONION_HOST: Record<CyberluxSearchDoc["zone"], string> = {
hub: "clx7hubz9vmkq2wyn4pj3lrtyx8yd6x3k8fnplwj6tor.onion",
forum: "voidagg8n3kx2wz7m4pj6lrtyx8yd5xq9kfvnplwjtor.onion",
exchange: "clxexch4kz8vm7nq2wyn9pj3lrtyx8yd2x6kfnplwj.onion",
wiki: "clxwiki3jq8vmkq2wyn4pj6lrtyx8yd7x2k9fmwnplwj.onion",
syndicate: "clxsyn7kq2wz8vm4n9pj3lrtyx8yd4x3k6fnplwjtor.onion",
account: "clxacct9vmkq2wz8n4pj6lrtyx8yd1x7k3fnplwjtor.onion",
};
function onionDisplayLine(doc: CyberluxSearchDoc): string {
const host = ZONE_ONION_HOST[doc.zone];
const p = doc.path === "/" ? "" : doc.path;
return `http://${host}${p}`;
}
export default function VoidCrawlerSearchPage() {
const [query, setQuery] = useState("");
const [submitted, setSubmitted] = useState("");
const [busy, setBusy] = useState(false);
const results = useMemo(() => {
if (!submitted.trim()) return [];
return searchCyberluxIndex(submitted);
}, [submitted]);
const onSubmit = (e: FormEvent) => {
e.preventDefault();
const q = query.trim();
if (!q) return;
setBusy(true);
window.setTimeout(() => {
setSubmitted(q);
setBusy(false);
}, 320);
};
return (
<div className="min-h-screen bg-[#030806] text-[#c8e6d0]">
<div
className="pointer-events-none fixed inset-0 z-0 opacity-[0.12]"
style={{
backgroundImage:
"repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,255,65,0.06) 3px)",
}}
/>
<header className="relative z-10 border-b border-[#00ff4122] bg-[#020504]/95 px-4 py-10 backdrop-blur-sm">
<div className="mx-auto max-w-3xl text-center">
<p className="font-mono text-[10px] uppercase tracking-[0.45em] text-[#00ff41]/55">closed corpus · internal index</p>
<h1 className="mt-3 font-mono text-4xl font-black tracking-tight text-white md:text-5xl">
VOID<span className="text-[#00ff41]">CRAWLER</span>
</h1>
<p className="mx-auto mt-4 max-w-xl font-mono text-xs leading-relaxed text-[#6b9b78]">
Crawls <strong className="text-[#9bffb8]">{CYBERLUX_SEARCH_INDEX_SIZE}</strong> signed routes on{" "}
<strong className="text-[#9bffb8]">this</strong> ring only hub, forum, exchange, wiki, syndicate shells, and account surfaces.
Foreign onions and clearnet bridges are deliberately excluded from the corpus.
</p>
<div className="mx-auto mt-8 max-w-4xl border border-[#00ff4120] bg-[#040a06]/80 px-3 py-3 text-left">
<p className="mb-2 font-mono text-[9px] uppercase tracking-[0.4em] text-[#00ff41]/40">corpus lexicon fog · sample tokens</p>
<div className="flex flex-wrap justify-center gap-1">
{LEXICON_FOG_SAMPLE.map((w) => (
<span
key={w}
className="rounded border border-[#00ff41]/12 px-1 py-0.5 font-mono text-[8px] lowercase text-[#5a8f6a]"
>
{w}
</span>
))}
</div>
</div>
</div>
</header>
<main className="relative z-10 mx-auto max-w-3xl px-4 pb-32 pt-10">
<form onSubmit={onSubmit} className="relative">
<label htmlFor="void-query" className="sr-only">
Search CyberLux sites
</label>
<input
id="void-query"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="e.g. forum barter vault checkout atlas…"
className="w-full border-2 border-[#00ff4133] bg-black/80 py-4 pl-4 pr-28 font-mono text-sm text-[#00ff41] placeholder:text-[#00ff41]/25 focus:border-[#00ff41] focus:outline-none"
/>
<button
type="submit"
disabled={busy || !query.trim()}
className="absolute right-1 top-1 bottom-1 bg-[#00ff41] px-5 font-mono text-xs font-black uppercase text-black hover:bg-[#00cc33] disabled:opacity-40"
>
{busy ? "…" : "Crawl"}
</button>
</form>
<p className="mt-4 font-mono text-[10px] uppercase tracking-widest text-[#4a6b55]">
Tip: multi-word queries match <span className="text-[#6b9b78]">all</span> tokens (AND).
</p>
<div className="mt-10 space-y-6">
{busy ? (
<p className="animate-pulse text-center font-mono text-xs uppercase tracking-[0.3em] text-[#00ff41]/45">
Walking local routes
</p>
) : null}
{!busy && submitted && (
<p className="font-mono text-xs text-[#6b9b78]">
<span className="text-[#00ff41]">{results.length}</span> hit{results.length === 1 ? "" : "s"} for{" "}
<span className="text-[#c8e6d0]">{submitted}</span>
</p>
)}
{results.map((doc) => (
<article
key={doc.path}
className="border-l-4 border-[#00ff4140] bg-[#050c08]/90 py-4 pl-5 pr-4 shadow-[inset_0_0_0_1px_rgba(0,255,65,0.06)]"
>
<div className="flex flex-wrap items-center gap-2">
<span className="rounded border border-[#00ff4133] px-2 py-0.5 font-mono text-[9px] uppercase tracking-wider text-[#7af598]">
{searchZoneLabel(doc.zone)}
</span>
</div>
<h2 className="mt-2 font-mono text-lg font-bold">
<Link href={doc.path} className="text-[#7af598] hover:text-[#b4ffcc] hover:underline">
{doc.title}
</Link>
</h2>
<p className="mt-1 break-all font-mono text-[10px] leading-relaxed text-[#00ff41]/35">
{onionDisplayLine(doc)}
</p>
<p className="mt-2 font-mono text-xs leading-relaxed text-[#8eb89a]/95">{doc.description}</p>
<div className="mt-3 flex flex-wrap gap-1">
{keywordSample(doc).map((kw) => (
<span
key={kw}
className="rounded bg-[#00ff41]/08 px-1.5 py-0.5 font-mono text-[8px] lowercase text-[#6b9b78]"
>
{kw}
</span>
))}
</div>
<p className="mt-2 font-mono text-[10px] text-[#4a6b55]">
Route:{" "}
<Link href={doc.path} className="text-[#6b9b78] hover:text-[#00ff41]">
{doc.path}
</Link>
</p>
</article>
))}
{!busy && submitted && results.length === 0 ? (
<div className="border-2 border-dashed border-[#00ff4122] py-16 text-center">
<p className="font-mono text-sm text-[#6b9b78]">No in-app routes match that query.</p>
<p className="mt-3 font-mono text-[11px] text-[#4a6b55]">
Try <button type="button" className="text-[#00ff41] underline" onClick={() => setQuery("forum")}>forum</button>,{" "}
<button type="button" className="text-[#00ff41] underline" onClick={() => setQuery("checkout")}>
checkout
</button>
, or <button type="button" className="text-[#00ff41] underline" onClick={() => setQuery("atlas")}>atlas</button>.
</p>
</div>
) : null}
</div>
<nav className="mt-16 flex flex-wrap justify-center gap-4 border-t border-[#00ff4118] pt-10 font-mono text-[11px]">
<Link href="/" className="text-[#6b9b78] hover:text-[#00ff41]">
Hub
</Link>
<Link href="/forum" className="text-[#6b9b78] hover:text-[#00ff41]">
Forum
</Link>
<Link href="/hidden-wiki" className="text-[#6b9b78] hover:text-[#00ff41]">
Wiki
</Link>
<Link href="/darknet-atlas" className="text-[#6b9b78] hover:text-[#00ff41]">
Atlas
</Link>
<Link href="/links" className="text-[#6b9b78] hover:text-[#00ff41]">
Link garden
</Link>
</nav>
<p className="mt-10 text-center font-mono text-[9px] uppercase tracking-[0.25em] text-[#00ff41]/20">
void crawler v1 · {CYBERLUX_SEARCH_INDEX_SIZE} documents · internal index only
</p>
</main>
</div>
);
}