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

142
app/chatter/page.tsx Normal file
View File

@@ -0,0 +1,142 @@
"use client";
import Link from "next/link";
import { useMemo } from "react";
import { useSearchParams } from "next/navigation";
import { formatRingLabel } from "@/lib/forumRings";
import { SHOP_CHATTER_COUNT, getChatterSlice } from "@/lib/shopChatterThreads";
const PAGE_SIZE = 12;
function formatAgo(ts: number): string {
const s = Math.floor((Date.now() - ts) / 1000);
if (s < 60) return `${s}s ago`;
const m = Math.floor(s / 60);
if (m < 60) return `${m}m ago`;
const h = Math.floor(m / 60);
if (h < 48) return `${h}h ago`;
const d = Math.floor(h / 24);
return `${d}d ago`;
}
export default function HubChatterArchivePage() {
const sp = useSearchParams();
const page = Math.max(1, parseInt(sp.get("page") ?? "1", 10) || 1);
const { items, totalPages, total } = useMemo(
() => getChatterSlice(page, PAGE_SIZE),
[page],
);
const pages = useMemo(() => {
const window = 2;
const lo = Math.max(1, page - window);
const hi = Math.min(totalPages, page + window);
const out: number[] = [];
for (let i = lo; i <= hi; i++) out.push(i);
return out;
}, [page, totalPages]);
return (
<div className="min-h-screen bg-[#050403] text-[#d4c4b0]">
<div
className="pointer-events-none fixed inset-0 z-0 opacity-[0.08]"
style={{
backgroundImage:
"repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(255,180,120,0.12) 3px)",
}}
/>
<header className="relative z-10 border-b border-[#3d3028] bg-[#0c0906]/95 px-4 py-8">
<div className="mx-auto max-w-3xl">
<p className="font-mono text-[10px] uppercase tracking-[0.4em] text-[#c45c26]/90">archive</p>
<h1 className="mt-2 font-mono text-2xl font-black text-[#ffcb8a] md:text-3xl">Hub chatter index</h1>
<p className="mt-3 max-w-2xl text-sm leading-relaxed text-[#9a8d7c]">
Scraped-style threads about the <strong className="text-[#ffcb8a]">CyberLux main storefront</strong> mirror
checks, checkout banter, UI roasts, class-lab meta. Each row opens in Void Aggregate for full comments.
</p>
<nav className="mt-6 flex flex-wrap gap-3 font-mono text-xs">
<Link
href="/forum"
className="rounded border border-[#c45c26] bg-[#c45c26]/15 px-4 py-2 font-bold text-[#ffcb8a] hover:bg-[#c45c26]/25"
>
Void Aggregate forum
</Link>
<Link href="/" className="rounded border border-[#3d3028] px-4 py-2 text-[#b8a995] hover:border-[#c45c26]/50">
Main hub
</Link>
</nav>
</div>
</header>
<main className="relative z-10 mx-auto max-w-3xl px-4 pb-24 pt-8">
<p className="mb-6 font-mono text-xs text-[#6b6358]">
Showing {(page - 1) * PAGE_SIZE + 1}{Math.min(page * PAGE_SIZE, total)} of {total} indexed posts ·{" "}
{PAGE_SIZE} per page
</p>
<ul className="space-y-4">
{items.map((row) => (
<li key={row.id} className="rounded-lg border border-[#2a2218] bg-[#0a0705]/95 p-4">
<div className="flex flex-wrap items-baseline justify-between gap-2 font-mono text-[10px] uppercase tracking-wider text-[#7a6e62]">
<span className="text-[#ff9a3c]/90">{formatRingLabel(row.topicSlug)}</span>
<span>
{row.author} · {formatAgo(row.ts)}
</span>
</div>
<Link
href={`/forum/post/${row.id}`}
className="mt-2 block font-mono text-base font-bold leading-snug text-[#ffcb8a] hover:underline"
>
{row.title}
</Link>
<p className="mt-2 line-clamp-3 text-sm leading-relaxed text-[#b0a090]">{row.body}</p>
<div className="mt-3 flex flex-wrap gap-3 font-mono text-[11px] text-[#6b6358]">
<span>{row.replies.length} comments</span>
<span>score ~{row.baseScore ?? 1}</span>
<Link href={`/forum/post/${row.id}`} className="text-[#ff9a3c] hover:underline">
open thread
</Link>
</div>
</li>
))}
</ul>
<nav className="mt-12 flex flex-wrap items-center justify-center gap-2 font-mono text-xs">
{page > 1 ? (
<Link
href={`/chatter?page=${page - 1}`}
className="rounded border border-[#3d3028] px-3 py-2 text-[#ffcb8a] hover:border-[#c45c26]"
>
Newer
</Link>
) : (
<span className="rounded border border-transparent px-3 py-2 text-[#4a433c]"> Newer</span>
)}
{pages.map((n) => (
<Link
key={n}
href={`/chatter?page=${n}`}
className={`rounded border px-3 py-2 ${
n === page ? "border-[#c45c26] bg-[#c45c26]/20 text-[#ffcb8a]" : "border-[#3d3028] text-[#b8a995] hover:border-[#c45c26]/40"
}`}
>
{n}
</Link>
))}
{page < totalPages ? (
<Link
href={`/chatter?page=${page + 1}`}
className="rounded border border-[#3d3028] px-3 py-2 text-[#ffcb8a] hover:border-[#c45c26]"
>
Older
</Link>
) : (
<span className="rounded border border-transparent px-3 py-2 text-[#4a433c]">Older </span>
)}
</nav>
<p className="mt-10 text-center font-mono text-[10px] text-[#4a4338]">Total catalogued: {SHOP_CHATTER_COUNT} threads</p>
</main>
</div>
);
}