Files
dark-lord/app/darknet-atlas/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

193 lines
8.4 KiB
TypeScript

"use client";
import Link from "next/link";
import { useMemo, useState } from "react";
import { PLACEHOLDER_ONION_HOST, PLACEHOLDER_ONION_URL } from "@/lib/placeholderOnion";
import {
DARKNET_ATLAS,
atlasCategoryMatchesQuery,
type AtlasCategory,
} from "@/lib/darknetAtlas";
function isClearnet(href: string): boolean {
if (href.toLowerCase().includes(".onion")) return false;
return href.startsWith("https://") || href.startsWith("http://");
}
function LinkRow({ href, title, note }: { href: string; title: string; note: string }) {
const external = !href.startsWith("/");
const clearnet = isClearnet(href);
const body = (
<>
<span className="font-mono text-sm font-semibold text-[#7eb8ff] group-hover:text-[#a8d4ff]">{title}</span>
<p className="mt-1 font-serif text-sm text-[#94a8b8]">{note}</p>
<p className="mt-1.5 font-mono text-[10px] tracking-wider text-[#5a6b82]">
{clearnet ? <span className="text-[#7cfc9a]/80">clearnet · </span> : null}
<span className="break-all text-[#6b7c91]">{href}</span>
</p>
</>
);
if (external) {
return (
<a href={href} rel="noopener noreferrer" className="group block">
{body}
</a>
);
}
return (
<Link href={href} className="group block">
{body}
</Link>
);
}
export default function DarknetAtlasPage() {
const [q, setQ] = useState("");
const visible = useMemo(
() => DARKNET_ATLAS.filter((c) => atlasCategoryMatchesQuery(c, q)),
[q],
);
return (
<div className="min-h-screen bg-[#05070c] text-[#c4cee0]">
<div className="pointer-events-none fixed inset-0 z-0 bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(59,130,246,0.12),transparent)]" />
<div
className="pointer-events-none fixed inset-0 z-0 opacity-[0.04]"
style={{
backgroundImage: `linear-gradient(rgba(120,160,255,0.35) 1px, transparent 1px),
linear-gradient(90deg, rgba(120,160,255,0.2) 1px, transparent 1px)`,
backgroundSize: "32px 32px",
}}
/>
<header className="relative z-10 border-b border-[#1e293b] bg-[#0a0f18]/95 px-4 py-8 backdrop-blur-md">
<div className="mx-auto max-w-6xl">
<p className="font-mono text-[10px] uppercase tracking-[0.45em] text-[#64748b]">reference · taxonomy</p>
<h1 className="mt-2 bg-gradient-to-r from-[#94a8ff] to-[#38bdf8] bg-clip-text font-mono text-3xl font-black tracking-tight text-transparent md:text-4xl">
Darknet Atlas
</h1>
<p className="mt-4 max-w-3xl text-sm leading-relaxed text-[#94a3b8]">
Analyst-facing taxonomy for how underground ecosystems get clustered in threat intel markets, access brokers, leak blogs,
whistleblowing rails, opsec boutiques, and rumor boards. External rows ship with boilerplate{" "}
<code className="rounded bg-[#1e293b] px-1 text-xs">{PLACEHOLDER_ONION_HOST}</code> hostnames until you paste vetted mirrors; treat
every link as unconfirmed until PGP cross-check.
</p>
<div className="mt-6 flex flex-wrap gap-3 font-mono text-xs">
<Link
href="/hidden-wiki"
className="rounded border border-[#334155] px-4 py-2 text-[#94a3b8] hover:border-[#38bdf8]/50 hover:text-[#e2e8f0]"
>
Scrapbook wiki
</Link>
<Link href="/" className="rounded border border-[#334155] px-4 py-2 text-[#94a3b8] hover:border-[#38bdf8]/50 hover:text-[#e2e8f0]">
Hub
</Link>
<Link href="/forum" className="rounded border border-[#334155] px-4 py-2 text-[#94a3b8] hover:border-[#38bdf8]/50 hover:text-[#e2e8f0]">
Forums
</Link>
<Link href="/links" className="rounded border border-[#334155] px-4 py-2 text-[#94a3b8] hover:border-[#38bdf8]/50 hover:text-[#e2e8f0]">
Link garden
</Link>
<Link href="/dashboard" className="rounded border border-[#334155] px-4 py-2 text-[#94a3b8] hover:border-[#38bdf8]/50 hover:text-[#e2e8f0]">
Dashboard
</Link>
<Link href="/sign-in" className="rounded border border-[#334155] px-4 py-2 text-[#94a3b8] hover:border-[#38bdf8]/50 hover:text-[#e2e8f0]">
Sign in
</Link>
<Link
href="/search"
className="rounded border border-[#14532d]/60 bg-[#052e16]/40 px-4 py-2 text-[#86efac] hover:border-[#22c55e]/50"
>
Void crawler
</Link>
</div>
<div className="mt-6">
<label className="sr-only" htmlFor="atlas-search">
Filter atlas
</label>
<input
id="atlas-search"
value={q}
onChange={(e) => setQ(e.target.value)}
placeholder="Filter categories, subsections, or link titles…"
className="w-full max-w-xl rounded border border-[#334155] bg-[#0f172a] px-4 py-3 font-mono text-sm text-[#e2e8f0] placeholder:text-[#475569] focus:border-[#38bdf8]/50 focus:outline-none"
/>
<p className="mt-2 font-mono text-[10px] text-[#64748b]">
{visible.length} / {DARKNET_ATLAS.length} top-level categories visible · default template host:{" "}
<span className="break-all text-[#475569]">{PLACEHOLDER_ONION_URL}</span>
</p>
</div>
</div>
</header>
<div className="relative z-10 mx-auto flex max-w-6xl flex-col gap-10 px-4 pb-24 pt-8 lg:flex-row lg:items-start">
<nav className="top-24 shrink-0 font-mono text-[11px] text-[#64748b] lg:sticky lg:w-56">
<p className="mb-3 uppercase tracking-widest text-[#475569]">Jump</p>
<ul className="max-h-[70vh] space-y-1 overflow-y-auto border border-[#1e293b] bg-[#0a0f18]/80 p-3">
{DARKNET_ATLAS.map((c) => (
<li key={c.id}>
<a href={`#${c.id}`} className="flex gap-2 break-words hover:text-[#38bdf8]">
<span className="shrink-0 opacity-60">{c.icon}</span>
<span className="leading-tight">{c.title}</span>
</a>
</li>
))}
</ul>
</nav>
<div className="min-w-0 flex-1 space-y-16">
{visible.length === 0 ? (
<p className="font-mono text-sm text-[#64748b]">No sections match that filter.</p>
) : (
visible.map((cat) => <AtlasCategorySection key={cat.id} cat={cat} query={q} />)
)}
</div>
</div>
</div>
);
}
function AtlasCategorySection({ cat, query }: { cat: AtlasCategory; query: string }) {
const subs = useMemo(() => {
if (!query.trim()) return cat.subsections;
const s = query.toLowerCase();
return cat.subsections.filter(
(sub) =>
sub.name.toLowerCase().includes(s) ||
sub.blurb.toLowerCase().includes(s) ||
sub.links.some((L) => L.title.toLowerCase().includes(s) || L.note.toLowerCase().includes(s)),
);
}, [cat.subsections, query]);
if (subs.length === 0) return null;
return (
<section id={cat.id} className="scroll-mt-28">
<div className="flex flex-wrap items-start gap-4 border-b border-[#1e293b] pb-4">
<span className="font-mono text-2xl text-[#38bdf8]/80">{cat.icon}</span>
<div>
<h2 className="font-mono text-xl font-bold text-[#f1f5f9] md:text-2xl">{cat.title}</h2>
<p className="mt-2 text-sm leading-relaxed text-[#94a3b8]">{cat.overview}</p>
<p className="mt-3 border-l-2 border-[#38bdf8]/50 pl-3 text-xs leading-relaxed text-[#7dd3fc]/90">{cat.literacy}</p>
</div>
</div>
<div className="mt-8 space-y-10">
{subs.map((sub) => (
<div key={sub.name} className="rounded-lg border border-[#1e293b] bg-[#0a0f18]/60 p-5">
<h3 className="font-mono text-sm font-bold uppercase tracking-wide text-[#7dd3fc]">{sub.name}</h3>
<p className="mt-2 text-sm text-[#94a3b8]">{sub.blurb}</p>
<ul className="mt-5 divide-y divide-[#1e293b] border border-[#1e293b] bg-[#070b12]/80">
{sub.links.map((link) => (
<li key={`${sub.name}-${link.title}`} className="p-4 transition-colors hover:bg-[#0f172a]/80">
<LinkRow href={link.href} title={link.title} note={link.note} />
</li>
))}
</ul>
</div>
))}
</div>
</section>
);
}