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

118
lib/barterState.ts Normal file
View File

@@ -0,0 +1,118 @@
export type BarterLane = "goods" | "services" | "data" | "open";
export type BarterListing = {
id: string;
author: string;
title: string;
/** What the poster brings to the table */
have: string;
/** What they want back (goods, labor, crypto, favor, etc.) */
want: string;
lane: BarterLane;
body: string;
ts: number;
};
const KEY = "cyberlux-barter-v1";
function uid(): string {
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
return `b_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
}
const seeds: BarterListing[] = [
{
id: "barter-seed-1",
author: "relay_op",
title: "Entropy dongles → signed opsec consult",
have: "Two hardware RNG sticks, sealed.",
want: "2h walkthrough on compartmentalized laptop setup (signals only).",
lane: "services",
body: "No clearnet video. Text + voice on agreed onion comms. PGP on first ping.",
ts: Date.now() - 7200000 * 10,
},
{
id: "barter-seed-2",
author: "ledger_moth",
title: "Monero for physical dead-tree cipher zines",
have: "XMR at spot-ish (fictional amount — negotiate in-thread).",
want: "High-res scans of 80s crypto zines, OCR optional.",
lane: "goods",
body: "Escrow via hub wallet credit only for this sim. Otherwise plaintext terms only.",
ts: Date.now() - 7200000 * 6,
},
{
id: "barter-seed-3",
author: "patchbay_7",
title: "Studio time ↔ exploit courseware slides",
have: "4h mixing desk + mastering chain on airgapped DAW session (fiction).",
want: "De-weaponized slide deck on heap grooming for class (no live targets).",
lane: "data",
body: "You send PDF, I send stems. Both sides verify hashes before swap.",
ts: Date.now() - 7200000 * 3,
},
{
id: "barter-seed-4",
author: "EU_shift",
title: "Shipping label templates → mirror canary text",
have: "Sanitized HTML snippets that look like phish (for training).",
want: "Latest signed canary paragraph from hub ring — compare byte-for-byte.",
lane: "open",
body: "Teaching red-team vs blue-team reading. No live credential harvesting.",
ts: Date.now() - 7200000 * 2,
},
{
id: "barter-seed-5",
author: "void_cartographer",
title: "Physical meet token ↔ CPU time",
have: "Laser-cut acrylic proof of attendance tokens (larp).",
want: "Someone to crunch log anonymization on an offline CSV (class data).",
lane: "services",
body: "Drop coordination fiction only. Real world: use your institutions lab policy.",
ts: Date.now() - 7200000 * 14,
},
];
export function loadBarter(): BarterListing[] {
if (typeof window === "undefined") return seeds;
try {
const raw = localStorage.getItem(KEY);
if (!raw) {
saveBarter(seeds);
return seeds;
}
const v = JSON.parse(raw) as BarterListing[];
if (!Array.isArray(v) || !v.length) {
saveBarter(seeds);
return seeds;
}
return v;
} catch {
return seeds;
}
}
export function saveBarter(list: BarterListing[]): void {
if (typeof window === "undefined") return;
localStorage.setItem(KEY, JSON.stringify(list));
}
const SEED_IDS = new Set(seeds.map((s) => s.id));
export function addBarterListing(
L: Omit<BarterListing, "id" | "ts">,
): BarterListing {
const row: BarterListing = {
id: uid(),
ts: Date.now(),
author: L.author.trim() || "Anonymous",
title: L.title.trim(),
have: L.have.trim(),
want: L.want.trim(),
lane: L.lane,
body: L.body.trim(),
};
const userRows = loadBarter().filter((r) => !SEED_IDS.has(r.id));
saveBarter([row, ...userRows, ...seeds]);
return row;
}

219
lib/cyberluxAccount.ts Normal file
View File

@@ -0,0 +1,219 @@
/**
* Client-side CyberLux vault accounts (no remote auth API).
* Passphrases are hashed with SHA-256 + a static pepper before storage.
*/
const ACCOUNTS_KEY = "cyberlux-accounts-v1";
const SESSION_KEY = "cyberlux-session-v1";
const PEPPER = "cyberlux-account-pepper-v1";
export type StoredAccount = {
passwordHashHex: string;
displayName: string;
createdAt: number;
};
export type SessionPayload = {
username: string;
};
export type PublicCredentials = {
username: string;
displayName: string;
createdAt: number;
};
function accountKey(username: string): string {
return username.trim().toLowerCase();
}
export async function hashPassword(password: string): Promise<string> {
const data = new TextEncoder().encode(`${PEPPER}:${password}`);
const buf = await crypto.subtle.digest("SHA-256", data);
return Array.from(new Uint8Array(buf))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}
function loadAccountMap(): Record<string, StoredAccount> {
if (typeof window === "undefined") return {};
try {
const raw = localStorage.getItem(ACCOUNTS_KEY);
if (!raw) return {};
const o = JSON.parse(raw) as Record<string, StoredAccount>;
return o && typeof o === "object" ? o : {};
} catch {
return {};
}
}
function saveAccountMap(m: Record<string, StoredAccount>): void {
if (typeof window === "undefined") return;
localStorage.setItem(ACCOUNTS_KEY, JSON.stringify(m));
}
export function validateUsername(username: string): string | null {
const u = username.trim();
if (u.length < 3 || u.length > 24) return "Handle must be 324 characters.";
if (!/^[a-zA-Z0-9_]+$/.test(u)) return "Use letters, numbers, and underscores only.";
return null;
}
export function validatePassword(password: string): string | null {
if (password.length < 8) return "Passphrase must be at least 8 characters.";
return null;
}
export async function registerAccount(
username: string,
password: string,
displayName?: string,
): Promise<{ ok: true } | { ok: false; error: string }> {
const uErr = validateUsername(username);
if (uErr) return { ok: false, error: uErr };
const pErr = validatePassword(password);
if (pErr) return { ok: false, error: pErr };
const key = accountKey(username);
const map = loadAccountMap();
if (map[key]) return { ok: false, error: "That handle is already taken." };
const dn = (displayName?.trim() || username.trim()).slice(0, 48);
const passwordHashHex = await hashPassword(password);
map[key] = {
passwordHashHex,
displayName: dn,
createdAt: Date.now(),
};
saveAccountMap(map);
return { ok: true };
}
export async function verifyCredentials(
username: string,
password: string,
): Promise<{ ok: true; profile: PublicCredentials } | { ok: false; error: string }> {
const key = accountKey(username);
const map = loadAccountMap();
const row = map[key];
if (!row) return { ok: false, error: "Unknown handle or wrong passphrase." };
const hash = await hashPassword(password);
if (hash !== row.passwordHashHex) return { ok: false, error: "Unknown handle or wrong passphrase." };
return {
ok: true,
profile: { username: key, displayName: row.displayName, createdAt: row.createdAt },
};
}
export function setSession(username: string): void {
if (typeof window === "undefined") return;
const payload: SessionPayload = { username: accountKey(username) };
localStorage.setItem(SESSION_KEY, JSON.stringify(payload));
}
export function clearSession(): void {
if (typeof window === "undefined") return;
localStorage.removeItem(SESSION_KEY);
}
export function readSession(): SessionPayload | null {
if (typeof window === "undefined") return null;
try {
const raw = localStorage.getItem(SESSION_KEY);
if (!raw) return null;
const o = JSON.parse(raw) as SessionPayload;
if (!o?.username || typeof o.username !== "string") return null;
return { username: accountKey(o.username) };
} catch {
return null;
}
}
export function getPublicProfile(username: string): PublicCredentials | null {
const key = accountKey(username);
const map = loadAccountMap();
const row = map[key];
if (!row) return null;
return { username: key, displayName: row.displayName, createdAt: row.createdAt };
}
export function updateDisplayName(username: string, displayName: string): boolean {
const key = accountKey(username);
const map = loadAccountMap();
const row = map[key];
if (!row) return false;
const dn = displayName.trim().slice(0, 48);
if (!dn) return false;
map[key] = { ...row, displayName: dn };
saveAccountMap(map);
return true;
}
/** Portable row for copying identity to another onion hostname (each host has its own localStorage). */
export type PortableAccountBundleV1 = {
v: 1;
username: string;
displayName: string;
createdAt: number;
passwordHashHex: string;
};
const HASH_HEX_RE = /^[a-f0-9]{64}$/;
function isPortableBundleV1(x: unknown): x is PortableAccountBundleV1 {
if (!x || typeof x !== "object") return false;
const o = x as Record<string, unknown>;
if (o.v !== 1) return false;
if (typeof o.username !== "string" || typeof o.displayName !== "string") return false;
if (typeof o.createdAt !== "number" || !Number.isFinite(o.createdAt)) return false;
if (typeof o.passwordHashHex !== "string" || !HASH_HEX_RE.test(o.passwordHashHex)) return false;
if (validateUsername(o.username)) return false;
return true;
}
/** Returns JSON text for the signed-in user, or null if none. Treat like a key backup — anyone with this blob can load this identity in-browser on another host. */
export function exportPortableAccountJson(): string | null {
const s = readSession();
if (!s) return null;
const map = loadAccountMap();
const row = map[s.username];
if (!row) return null;
const bundle: PortableAccountBundleV1 = {
v: 1,
username: s.username,
displayName: row.displayName,
createdAt: row.createdAt,
passwordHashHex: row.passwordHashHex,
};
return JSON.stringify(bundle);
}
/**
* Merges a bundle from another hostname into this browser. Signs in as that user on success.
* If the handle already exists here with a different passphrase hash, returns an error.
*/
export function importPortableAccountJson(json: string): { ok: true } | { ok: false; error: string } {
let parsed: unknown;
try {
parsed = JSON.parse(json) as unknown;
} catch {
return { ok: false, error: "Not valid JSON." };
}
if (!isPortableBundleV1(parsed)) return { ok: false, error: "Invalid or unsupported identity bundle." };
const key = accountKey(parsed.username);
const map = loadAccountMap();
const existing = map[key];
if (existing && existing.passwordHashHex !== parsed.passwordHashHex) {
return { ok: false, error: "This browser already has a different passphrase for that handle." };
}
const dn = parsed.displayName.trim().slice(0, 48) || key;
map[key] = {
passwordHashHex: parsed.passwordHashHex,
displayName: dn,
createdAt: parsed.createdAt,
};
saveAccountMap(map);
setSession(key);
return { ok: true };
}

18
lib/cyberluxEntry.ts Normal file
View File

@@ -0,0 +1,18 @@
/** Mirrors nginx `X-Cyberlux-Node` — which .onion hostname the user entered on. */
import {
CYBERLUX_ENTRY_VALUES,
type CyberluxEntry,
} from "@/lib/onionRoutes.generated";
export { CYBERLUX_ENTRY_VALUES, type CyberluxEntry };
export const CYBERLUX_ENTRY_COOKIE = "cyberlux-entry";
export function parseCyberluxEntry(
raw: string | undefined | null,
): CyberluxEntry {
if (raw && (CYBERLUX_ENTRY_VALUES as readonly string[]).includes(raw)) {
return raw as CyberluxEntry;
}
return "hub";
}

528
lib/cyberluxSearchIndex.ts Normal file
View File

@@ -0,0 +1,528 @@
/**
* In-app search corpus: CyberLux ring routes only (this deployment).
* Used by /search — no outbound clearnet or third-party onions in the index.
*/
import { FORUM_RINGS } from "@/lib/forumRings";
import { SHOP_SELLERS } from "@/lib/shopSellers";
import { SYNDICATE_NODES } from "@/lib/syndicateNetwork";
import { ZONE_KEYWORD_SUFFIX, type CyberluxZoneKey } from "@/lib/cyberluxZoneLexicon";
export type SearchZone = "hub" | "forum" | "exchange" | "wiki" | "syndicate" | "account";
export type CyberluxSearchDoc = {
path: string;
title: string;
description: string;
/** Extra tokens matched by search (lowercase ok) */
keywords: string;
zone: SearchZone;
};
const ZONE_LABEL: Record<SearchZone, string> = {
hub: "Hub storefront",
forum: "Forum relay",
exchange: "Exchange relay",
wiki: "Wiki mirror",
syndicate: "Syndicate shell",
account: "Session / account",
};
const STATIC_DOCS: CyberluxSearchDoc[] = [
{
path: "/",
title: "CyberLux hub",
description:
"Neon sanctuary storefront: live SKU and vendor telemetry, curated drops, category lattice, terminal vault, encryption theatre, signed mirror rail, forum preview strip, wallet fiction, and Tor-native chrome.",
keywords:
"home hub sanctuary storefront candy shop skus vendors drops vault encryption mixer checkout forum preview tor onion mirror bundle wallet ritual grid category lattice telemetry",
zone: "hub",
},
{
path: "/market",
title: "Candy market (full catalog)",
description:
"Layered market chrome: phosphor catalog grid, sidebar ops and trust rails, live SKU analytics, fulfillment pipeline prose, buyer intel annex, cart-to-checkout continuity, vendor hall links, bond-tier fiction.",
keywords:
"candy shop market catalog grid cart reviews ratings testimonials skus vendors stalls checkout layers onion analytics operations trust intel escrow pgp bond multisig dispute sla pipeline buyer vetting sku heat category rails",
zone: "hub",
},
{
path: "/market/operations",
title: "Market — operations & pipeline",
description:
"Fulfillment stack fiction: draft cart, committed checkout, vendor ack, opaque tracking hashes, dispute ladder, floor mediators, council multisig releases, SLA tiers, cold-chain QA gates, mirror-coherent order ids.",
keywords:
"market ops fulfillment pipeline order-state machine sla vendor-ack ship-window tracking-hash dispute mediator council multisig qa-gate cold-chain bond express standard timeout release moderator queue",
zone: "hub",
},
{
path: "/market/trust",
title: "Market — trust & escrow",
description:
"Four trust layers: collateral bond, chained PGP continuity, timed escrow cooling, two-of-three council; hygiene checklist for fingerprints, rotations, off-platform pressure, finalize scams, mirror bundle alignment.",
keywords:
"trust escrow pgp gpg fingerprint bond multisig settlement cold-storage hot-wallet buyer seller cooling finalize rotation signed-cleartext council arbitration key-hygiene phishing mirror scam chargeback",
zone: "hub",
},
{
path: "/market/analytics",
title: "Market — floor analytics",
description:
"Telemetry derived from shopCatalog: category mix, currency histograms, limited-drop counts, rough USD notional — CSS bars only, no chart.js, same build on every onion host.",
keywords:
"analytics telemetry histogram category mix currency btc xmr eth usd limited drops skus velocity notional floor stats shopcatalog bars",
zone: "hub",
},
{
path: "/market/intel",
title: "Market — buyer intelligence",
description:
"Threat model copy: stall signals table, hard stops, soft signals, review velocity fraud tells, mirror mismatch, rotation anomalies, cross-links to Void crawler and forum rings.",
keywords:
"intel vetting threat-model stall signals phishing scam mirror bundle hard-stop soft-signal review-velocity rating fraud opsec buyer safety crawler forum",
zone: "hub",
},
{
path: "/vendors",
title: "Vendor hall",
description: "Vendor hall with bios, ratings, sales lore, and deep links to each stalls shelf.",
keywords: "vendors sellers bazaar marketplace stalls handles merchant",
zone: "hub",
},
{
path: "/vendor/apply",
title: "Apply to vend",
description: "Vendor onboarding intake — encrypted client queue with reference ids for bond review.",
keywords: "seller apply vendor stall onboarding application become seller",
zone: "hub",
},
{
path: "/vault",
title: "The vault",
description: "Terminal-style vault for loyalty keys, receipts, and returning-buyer perks.",
keywords: "keys receipts encryption terminal loyalty",
zone: "hub",
},
{
path: "/game",
title: "Profit simulator",
description: "Clicker / profit sim tied to wallet lore.",
keywords: "game clicker profit score arcade",
zone: "hub",
},
{
path: "/mixer",
title: "Token blender",
description: "Token blender UI with session log theatre — opacity controls and hop graph.",
keywords: "mixer monero bitcoin privacy hops opacity",
zone: "hub",
},
{
path: "/drops",
title: "Limited drops",
description: "Scarcity-style limited product drops and timers.",
keywords: "drops lottery limited flash sale urgency",
zone: "hub",
},
{
path: "/checkout",
title: "Checkout ritual",
description: "Multi-step checkout flow, BTC verify hook, store credit.",
keywords: "pay cart order bitcoin crypto txid verify payment",
zone: "hub",
},
{
path: "/sanctuary",
title: "Sanctuary",
description: "Calmer landing / refuge page in the hub chrome.",
keywords: "quiet calm refuge privacy rest",
zone: "hub",
},
{
path: "/support",
title: "Offering / support",
description: "Support-adjacent ritual page, multisig notes.",
keywords: "help support contact offering ritual",
zone: "hub",
},
{
path: "/links",
title: "Link garden",
description: "Curated link rows — internal endorsements plus external slots you can vet manually.",
keywords: "links directory webring endorsements trust reviews",
zone: "hub",
},
{
path: "/webring",
title: "Webring",
description: "Retro webring hop between internal pages.",
keywords: "webring neighbors retro navigation",
zone: "hub",
},
{
path: "/reviews",
title: "Review blog",
description: "Satirical review copy about the hub.",
keywords: "reviews blog rating opinion",
zone: "hub",
},
{
path: "/trust",
title: "Trust dashboard",
description: "Heuristic trust dashboard — reputation signals and mirror alignment.",
keywords: "trust score metrics reputation verify",
zone: "hub",
},
{
path: "/comparison",
title: "Marketplace comparison",
description: "Competitive matrix — vendor-selected metrics; read the footnotes.",
keywords: "compare tables markets chart",
zone: "hub",
},
{
path: "/security-analysis",
title: "Security analysis",
description: "Pentest fantasy write-up page.",
keywords: "security audit analysis report encryption",
zone: "hub",
},
{
path: "/testimonials",
title: "Testimonials",
description: "Buyer social proof wall — quotes and scorecards.",
keywords: "testimonials quotes buyers satisfied",
zone: "hub",
},
{
path: "/presswire",
title: "Presswire",
description: "Satirical news feed tied to wallet flags.",
keywords: "news press wire rumors journalism",
zone: "hub",
},
{
path: "/trees",
title: "Trees authority",
description: "Absurdist government trees certification page.",
keywords: "trees authority government bark certification",
zone: "hub",
},
{
path: "/messages",
title: "Encrypted comms",
description: "Read-only chat fiction UI.",
keywords: "messages chat encrypted comms agent",
zone: "hub",
},
{
path: "/wallets",
title: "Wallets gallery",
description: "Hardware wallet marketing pastiche.",
keywords: "wallets hardware ledger trezor",
zone: "hub",
},
{
path: "/awards",
title: "Awards",
description: "Camp accolades page.",
keywords: "awards badges recognition",
zone: "hub",
},
{
path: "/raffle",
title: "Raffle",
description: "Lottery / raffle urgency copy.",
keywords: "raffle lottery tickets prize odds",
zone: "hub",
},
{
path: "/arb-academy",
title: "Arb academy",
description: "Trading / arbitration academy parody.",
keywords: "academy arbitrage trading learn course",
zone: "hub",
},
{
path: "/inner-circle",
title: "Inner circle",
description: "Gated inner-circle shell — passphrase lore and staged unlock.",
keywords: "inner circle gate exclusive login",
zone: "hub",
},
{
path: "/conspiracies",
title: "Conspiracies board",
description: "Conspiracy-themed lore page.",
keywords: "conspiracy board rumors theories",
zone: "hub",
},
{
path: "/red-room",
title: "Red room",
description: "Restricted horror-themed lounge — enter at your own risk.",
keywords: "red room horror fiction warning",
zone: "hub",
},
{
path: "/secret-layer",
title: "Secret layer",
description: "Hidden layer / password rumor page.",
keywords: "secret password easter egg layer",
zone: "hub",
},
{
path: "/easter-eggs",
title: "Easter eggs",
description: "Hunt hints and meta references.",
keywords: "easter eggs hunt secrets",
zone: "hub",
},
{
path: "/drop-box",
title: "Drop box",
description: "Anonymous drop fiction.",
keywords: "drop box upload leak",
zone: "hub",
},
{
path: "/search",
title: "Void crawler (internal index)",
description:
"Closed-corpus AND crawler over signed in-app routes — hub, forum, exchange, wiki, syndicate shells, account flows; lexicon fog, zone onions, foreign clearnet and third-party onions excluded by design.",
keywords:
"search crawler void index query corpus lexicon tokens zone hub forum exchange wiki syndicate account onion tor internal-only AND tokens route document hit",
zone: "hub",
},
{
path: "/forum",
title: "Void Aggregate — forum home",
description:
"Ringed rumor ledger: pinned doctrine, per-ring feeds, hot new top sorts, staff locks, submit hooks, chatter spine, Void lexicon, onion relay chrome, distributed board fiction.",
keywords:
"forum void aggregate rings board threads posts replies votes hot new top pinned moderation search filter submit chatter relay onion",
zone: "forum",
},
{
path: "/forum/submit",
title: "Void Aggregate — new post",
description: "Submit a thread to a ring.",
keywords: "submit post create thread publish",
zone: "forum",
},
{
path: "/forum/handbook",
title: "Void Aggregate — operator handbook",
description:
"Canon table of contents: ring topology, submission contract, moderation ladder cross-links, reputation weight explainer, onion deep-link targets for dedicated forum hidden services.",
keywords:
"forum handbook operators canon topology rings doctrine void aggregate moderation reputation toc deep-link onion relay",
zone: "forum",
},
{
path: "/forum/moderation",
title: "Void Aggregate — moderation doctrine",
description:
"Tiered enforcement fiction: ring-scoped freeze, handle shadow ranking sink, network-wide exile with signed rationale stubs, appeals only through support tickets preserving audit hashes.",
keywords:
"forum moderation policy janitor council freeze shadowban exile appeal audit ticket support ban tier staff lock",
zone: "forum",
},
{
path: "/forum/reputation",
title: "Void Aggregate — reputation & weight",
description:
"Hot decay windows, top log caps, reply-weight with tenure bias, report priors, ring-specific temperature, shop-session commerce bump without global clout inflation.",
keywords:
"forum reputation karma weight sorting hot top decay cap reply report prior ring temperature vote brigade",
zone: "forum",
},
{
path: "/chatter",
title: "Hub chatter archive",
description: "Paged index of seeded threads about the main storefront.",
keywords: "chatter hub archive meta shop gossip",
zone: "forum",
},
{
path: "/exchange",
title: "The Exchange",
description:
"Stranger gazette classifieds: WTS and WTB stream, submit form, guidelines route, ledger analytics, serif broadsheet chrome, localStorage-backed rows, shared wallet session fiction with hub.",
keywords:
"exchange classifieds wts wtb sell buy listings gazette broadsheet column stranger localstorage submit guidelines ledger currency mix freshness",
zone: "exchange",
},
{
path: "/exchange/guidelines",
title: "Exchange — posting guidelines",
description:
"Posting law: required headline body price fields, prohibited scraper reposts and tracker paste bins, impersonation bans, unmoderated stance with abuse funnel to support and hub widgets.",
keywords:
"exchange rules policy guidelines classifieds prohibited paste tracker impersonation abuse support unmoderated field requirements",
zone: "exchange",
},
{
path: "/exchange/ledger",
title: "Exchange — ledger view",
description:
"Aggregates cyberlux-exchange-v1 local rows: WTS WTB counts, BTC XMR USD tag tallies, newest timestamps, span fiction — identical corpus to the board without external analytics SDKs.",
keywords:
"exchange ledger stats volume localstorage analytics wts wtb btc xmr usd timestamp span rows board parity",
zone: "exchange",
},
{
path: "/barter",
title: "Ash Pit (barter)",
description: "Have/want swap floor — goods, skills, data lanes.",
keywords: "barter swap ash pit trade goods services",
zone: "exchange",
},
{
path: "/hidden-wiki",
title: "Scrapbook wiki",
description: "Scrapbook wiki skin with stub .onion rows and internal CyberLux references.",
keywords: "hidden wiki directory onion links scrapbook mirror",
zone: "wiki",
},
{
path: "/darknet-atlas",
title: "Darknet Atlas",
description: "Taxonomy atlas: markets, leaks, whistleblowing, privacy, forums — analyst layout.",
keywords: "atlas taxonomy darknet categories literacy syllabus markets cybercrime leaks",
zone: "wiki",
},
{
path: "/syndicate",
title: "Syndicate network map",
description:
"Fuchsia operator console: themed /w slug index, topology explainer, routing lore, single Next build, chrome divergence only, persistence and wallet paths unified with hub fiction.",
keywords:
"syndicate network relays skins nodes map shells topology routing slug w-route operator console ingress persistence",
zone: "syndicate",
},
{
path: "/syndicate/topology",
title: "Syndicate — topology",
description:
"Ingress presentation persistence triple: Tor hostname to dedicated root, skin picker on /w/slug, browser storage namespacing, mirror bundle documentation for users.",
keywords:
"syndicate topology ingress presentation persistence relay graph network operator hostname skin storage mirror",
zone: "syndicate",
},
{
path: "/syndicate/routing",
title: "Syndicate — routing lore",
description:
"Dedicated onion rewrites, cookie and header behavior, canonical /w paths inside Next, absolute cross-vertical links, proxy.ts alignment storytelling for status boards.",
keywords:
"syndicate routing onion headers cookies mirrors canonical proxy rewrite absolute-path cross-vertical host",
zone: "syndicate",
},
{
path: "/dashboard",
title: "Personal dashboard",
description: "Signed-in profile, wallet snapshot, forum and listing activity counts.",
keywords: "dashboard account profile stats activity signin",
zone: "account",
},
{
path: "/sign-in",
title: "Sign in",
description: "Client-side session unlock — passphrase and handle for this mirror.",
keywords: "login signin session passphrase handle",
zone: "account",
},
{
path: "/sign-up",
title: "Create account",
description: "Register a local handle and passphrase.",
keywords: "register signup create account identity provision",
zone: "account",
},
{
path: "/account/hidden-services",
title: "Hidden services — account on every mirror",
description:
"Sign up or import a portable identity so forum, exchange, and wiki onion hosts share the same handle in-browser.",
keywords: "onion mirror hostname import export portable identity signup hidden services",
zone: "account",
},
];
const RING_DOCS: CyberluxSearchDoc[] = FORUM_RINGS.map((r) => ({
path: `/forum/ring/${r.slug}`,
title: `Void Aggregate · r/${r.shortName}`,
description: `${r.title}${r.about}`,
keywords: `${r.slug} ${r.shortName} ring board subreddit forum thread posts void aggregate tor onion relay hot new top ${r.title} ${r.about}`,
zone: "forum" as const,
}));
const VENDOR_DOCS: CyberluxSearchDoc[] = SHOP_SELLERS.map((seller) => ({
path: `/vendor/${seller.slug}`,
title: `Vendor stall · ${seller.handle}`,
description: `${seller.tagline}${seller.bio}`,
keywords: `${seller.slug} ${seller.handle} vendor stall seller merchant ${seller.specialty} ${seller.tagline} ${seller.bio}`,
zone: "hub" as const,
}));
const SYNDICATE_DOCS: CyberluxSearchDoc[] = SYNDICATE_NODES.map((n) => ({
path: `/w/${n.slug}`,
title: `Syndicate relay · ${n.slug}`,
description: n.tagline,
keywords: `${n.slug} syndicate relay skin themed shell node w-route chrome ingress operator cyberlux tor onion ${n.tagline}`,
zone: "syndicate" as const,
}));
function enrichZoneKeywords(doc: CyberluxSearchDoc): CyberluxSearchDoc {
const suffix = ZONE_KEYWORD_SUFFIX[doc.zone as CyberluxZoneKey] ?? "";
return {
...doc,
keywords: `${doc.keywords} ${suffix}`.replace(/\s+/g, " ").trim(),
};
}
/** Full corpus — dedupe by path, append per-zone lexicon for richer matching */
function mergeDocs(): CyberluxSearchDoc[] {
const map = new Map<string, CyberluxSearchDoc>();
for (const d of [...STATIC_DOCS, ...RING_DOCS, ...VENDOR_DOCS, ...SYNDICATE_DOCS].map(enrichZoneKeywords)) {
map.set(d.path, d);
}
return [...map.values()].sort((a, b) => a.path.localeCompare(b.path));
}
export const CYBERLUX_SEARCH_INDEX: CyberluxSearchDoc[] = mergeDocs();
/** Unique keyword chips for Void Crawler atmosphere UI (deduped, sorted). */
export const VOID_LEXICON_WORDS: string[] = (() => {
const s = new Set<string>();
for (const d of CYBERLUX_SEARCH_INDEX) {
for (const raw of d.keywords.split(/\s+/)) {
const t = raw.trim().toLowerCase().replace(/[^\w-]/g, "");
if (t.length > 2 && t.length < 32) s.add(t);
}
}
return [...s].sort((a, b) => a.localeCompare(b));
})();
export const CYBERLUX_SEARCH_INDEX_SIZE = CYBERLUX_SEARCH_INDEX.length;
export function searchZoneLabel(zone: SearchZone): string {
return ZONE_LABEL[zone];
}
/**
* AND search on title, description, keywords, and path (all lowercased).
*/
export function searchCyberluxIndex(raw: string, limit = 80): CyberluxSearchDoc[] {
const q = raw.trim().toLowerCase();
if (!q) return [];
const tokens = q.split(/\s+/).filter((t) => t.length > 0);
if (tokens.length === 0) return [];
return CYBERLUX_SEARCH_INDEX.filter((doc) => {
const hay = `${doc.title}\n${doc.description}\n${doc.keywords}\n${doc.path}`.toLowerCase();
return tokens.every((t) => hay.includes(t));
}).slice(0, limit);
}

153
lib/cyberluxZoneLexicon.ts Normal file
View File

@@ -0,0 +1,153 @@
/**
* Rich keyword suffixes (per vertical) merged into Void Crawler index docs.
* Atmosphere tags for site chrome — diegetic vocabulary, not clearnet SEO.
*/
export type CyberluxZoneKey = "hub" | "forum" | "exchange" | "wiki" | "syndicate" | "account";
/** Appended to every indexed document in that zone for deeper AND-token matching */
export const ZONE_KEYWORD_SUFFIX: Record<CyberluxZoneKey, string> = {
hub: [
"tor onion v3 hidden-service rendezvous introduction circuit consensus relay descriptor hostname",
"mirror bundle multisig cold-storage hot-wallet watch-only xpub utxo fee bump replace-by-fee",
"pgp gpg fingerprint signed-cleartext ascii-armor key-rotation revocation dead-drop shipping",
"vendor stall sku listing catalog cart checkout ritual escrow settlement chargeback",
"opsec compartmentalize burner tails qubes airgap hardware-wallet seed phrase bip39",
"btc bitcoin xmr monero eth ethereum usd stablecoin ticker spot estimate",
"loyalty vault mixer drops raffle testimonial review trust score sanctuary",
"nginx loopback onion-service dedicated-root proxy rewrite cookie session hydration",
].join(" "),
forum: [
"void aggregate rumor ledger distributed board ring subreddit thread post reply lurker",
"moderation janitor council freeze shadowban exile appeal quorum vote hot new top pinned",
"reputation weight prior karma brigade report flag necrobump sage bump capcode tripcode",
"submit publish draft preview markdown quote greentext onion mirror tor relay",
"chatter archive meta storefront gossip staff sticky lock unlock ringmaster",
].join(" "),
exchange: [
"classifieds wts wtb wanted sell buy offer asking shipped local meetup cash",
"gazette column stranger ink broadsheet listing headline alias byline print",
"barter ash-pit swap haggle peer-to-peer liquidity slippage denomination satoshi piconero",
"localstorage persisted client-only seed listing parody fiction unmoderated caveat emptor",
"pgp contact policy terms carrier tracking opaque-hash dead-letter jurisdiction",
].join(" "),
wiki: [
"hidden wiki scrapbook atlas taxonomy syllabus literacy analyst darknet categories",
"mirror stub onion row compendium lore whistleblow opsec literacy nodes cluster tag",
"directory linkrot verified curated endorsement webring neighbor hop scrap",
].join(" "),
syndicate: [
"relay shell skin themed chrome ingress presentation persistence slug canonical-path",
"operator node map bundle deployment nextjs app-router fiction diegetic vertical",
"syndicate network fanout header host rewrite disclosure onion alignment",
].join(" "),
account: [
"signin signup session passphrase handle display-name portable identity import export",
"dashboard profile activity wallet snapshot forum exchange wiki cross-surface",
"hostname mirror hidden-service portable-browser local-identity client-side vault",
].join(" "),
};
export const MARKET_ATMOSPHERE_TAGS: string[] = [
"rendezvous",
"descriptor",
"multisig",
"coldvault",
"watch-only",
"pgp",
"fingerprint",
"dead-drop",
"ship-window",
"bond-tier",
"sku-heat",
"cart-bundle",
"cooling",
"council",
"mediator",
"opaque-hash",
"qa-gate",
"category-rail",
"mirror-signed",
"onion-split",
"xmr-lane",
"btc-verify",
"vendor-ack",
"dispute-queue",
"floor-telemetry",
"stall-rank",
"buyer-intel",
"opsec",
"rotation-chain",
"forfeit",
"timeout-release",
];
export const EXCHANGE_ATMOSPHERE_TAGS: string[] = [
"broadsheet",
"column-inch",
"wtb",
"wts",
"byline",
"ink-stain",
"deadline",
"meet-cute",
"plaintext-hazard",
"signed-listing",
"local-ledger",
"freshness",
"currency-mix",
"peer-risk",
"caveat",
"gazette-mast",
"stranger-trade",
"alias-chain",
"price-field",
"body-copy",
"no-algo-feed",
];
export const FORUM_ATMOSPHERE_TAGS: string[] = [
"ring-topology",
"void-aggregate",
"necrobump",
"sage",
"tripcode",
"sticky",
"lockfile",
"janitor-tier",
"shadow-rank",
"exile-stub",
"appeal-ticket",
"hot-decay",
"top-cap",
"report-weight",
"lore-canon",
"relay-latency",
"onion-reader",
"thread-id",
"ring-prior",
"chatter-spine",
];
export const SYNDICATE_ATMOSPHERE_TAGS: string[] = [
"ingress-map",
"presentation-layer",
"slug-canonical",
"shell-skin",
"relay-fanout",
"host-rewrite",
"bundle-hash",
"operator-console",
"node-index",
"topology-layer",
"routing-lore",
"persistence-edge",
"chrome-divergence",
"single-build",
"mirror-coherent",
];

515
lib/darknetAtlas.ts Normal file
View File

@@ -0,0 +1,515 @@
import { PLACEHOLDER_ONION_URL } from "@/lib/placeholderOnion";
export type AtlasLink = {
title: string;
note: string;
href: string;
};
export type AtlasSubsection = {
name: string;
blurb: string;
links: AtlasLink[];
};
export type AtlasCategory = {
id: string;
icon: string;
title: string;
overview: string;
literacy: string;
subsections: AtlasSubsection[];
};
const P = PLACEHOLDER_ONION_URL;
/** Classroom-safe framing: taxonomy mirrors how analysts label surface types — not an endorsement or directory of real criminal services. */
function o(title: string, note: string): AtlasLink {
return { title, note, href: P };
}
export const DARKNET_ATLAS: AtlasCategory[] = [
{
id: "illicit-marketplaces",
icon: "◆",
title: "Illicit & high-risk marketplaces",
overview:
"Threat intelligence and law-enforcement reporting clusters many underground storefronts under “illicit markets”: venues where vendors advertise contraband or fraud-adjacent goods. Real venues rotate constantly; names in reports are often stale.",
literacy:
"Students should map claims (PGP, escrow, “verified vendor”) to verifiable artifacts — never trust screenshots or forum hype alone.",
subsections: [
{
name: "Narcotics & precursor chatter (simulated slots)",
blurb: "Reporting often references opioid stimulants, novel psychoactive threads, and precursor sourcing. Placeholders only.",
links: [
o("Listing mirror slot — Western EU route fiction", "Replace with your case-study onion if applicable."),
o("Bulk listing index (placeholder)", "Typical folder taxonomy in intel writeups."),
o("Feedback escrow thread (placeholder)", "How reputation is staged in case studies."),
o("Chemical catalog fiction slot A", "Placeholder."),
o("Chemical catalog fiction slot B", "Placeholder."),
],
},
{
name: "Weapons, parts & dual-use items (simulated)",
blurb: "Analysts distinguish whole weapons vs parts, blueprints, and dual-use machining chatter.",
links: [
o("Parts vendor fiction index", "Placeholder .onion row."),
o("Blueprint dump slot (placeholder)", "Use only for discussing IP / export control angles in class."),
o("Ammo logistics rumor board (simulated)", "Placeholder."),
],
},
{
name: "Forged & counterfeit documents (simulated)",
blurb: "Passports, diplomas, utility bills for KYC fraud — common themes in FININT slide decks.",
links: [
o("Template reseller slot A", "Placeholder."),
o("Template reseller slot B", "Placeholder."),
o("Novelty vs fraud disclaimer fiction", "Teaching prompt: compare marketing language."),
],
},
{
name: "Stolen financial data & fraud kits (simulated)",
blurb: "Includes card dumps, fullz, bank logs, check templates — language from indictments, not how-to.",
links: [
o("CC dump forum fiction slot", "Placeholder."),
o("Fullz aggregator placeholder", "Discuss PII sensitivity & breach notification."),
o("Bank log rumor board (simulated)", "Placeholder."),
o("Check & wire fraud kit fiction", "Placeholder — contrast with enterprise red-team scope."),
],
},
{
name: "Stolen credentials & “logs” markets (simulated)",
blurb: "Session cookies, stealer output, combo lists — often adjacent to ATO chains.",
links: [
o("Combo list seller fiction", "Placeholder."),
o("Stealer log warehouse slot", "Placeholder."),
o("Indexed cookie marketplace fiction", "Tie to MFA + device trust lessons."),
],
},
],
},
{
id: "cybercrime-services",
icon: "⚡",
title: "Cybercrime services & tooling",
overview:
"A catch-all for commoditized offense: access brokerage, malware rental, ransom panels, and denial-of-service stressors marketed as “security tests.”",
literacy:
"When teaching, emphasize economics (subscription tiers, support chats) and detection (C2 patterns, tariffs), not replication.",
subsections: [
{
name: "Hacking-as-a-service & pentest cosplay",
blurb: "Offers to break into mailboxes, panels, or corporate VPNs — almost always fraudulent or entangled with law enforcement stings in real life.",
links: [
o("HaaS storefront fiction A", "Placeholder."),
o("HaaS storefront fiction B", "Placeholder."),
o("“Corporate espionage” copycat slot", "Compare wording to legitimate pentest SOWs."),
],
},
{
name: "Malware, loaders & crypters",
blurb: "Builders, packers, and obfuscation-as-a-service show up in malware reverse-engineering courses.",
links: [
o("Loader subscription panel (placeholder)", "Discuss OPSEC failures of panels."),
o("Crypter AS-a-service fiction", "Placeholder."),
o("RAT builder archive slot", "Placeholder."),
],
},
{
name: "Ransomware & affiliate programs",
blurb: "RaaS branding, leak blogs, negotiation portals — curriculum ties to incident response playbooks.",
links: [
o("Affiliate portal fiction", "Placeholder."),
o("Negotiation chat relay (simulated)", "Placeholder."),
o("Decryptor rumor board (simulated)", "Highlight verify-before-pay discipline."),
],
},
{
name: "Botnets, loaders & spam ops",
blurb: "Panels that rent bots for click fraud, spam, or relay abuse — contrast with academic bot lab ethics.",
links: [
o("Botnet panel fiction slot", "Placeholder."),
o("SMS pump relay fiction", "Placeholder."),
o("Proxy bot reseller fiction", "Tie to provider abuse desks."),
],
},
{
name: "DDoS, stressers & amplification kits",
blurb: "Marketed as “stress tests”; in class, connect to BCP, scrubbing providers, and legal risk.",
links: [
o("Layer-7 stresser fiction", "Placeholder."),
o("Amplification recipe mirror (simulated)", "Do not operationalize — discuss history only."),
o("“IP booter” UI clone fiction", "Placeholder."),
],
},
{
name: "Exploit brokers & 0-day chatter (simulated)",
blurb: "Where legitimate research ends is a legal line — teach export controls & responsible disclosure here.",
links: [
o("Exploit auction fiction slot", "Placeholder."),
o("Browser chain rumor board", "Placeholder."),
],
},
],
},
{
id: "data-leaks",
icon: "📂",
title: "Data leak & extortion sites",
overview:
"Ransomware crews and extortion actors sometimes host blogs or file dumps to pressure victims. URLs churn; many are FBI sinkholes or mirrors in training data.",
literacy:
"Lessons: leak authenticity, hash verification, legal obligations, and victim communication — not voyeurism.",
subsections: [
{
name: "Ransomware leak blogs (simulated slots)",
blurb: "Naming conventions often ape security blogs; compare to legitimate disclosure posts.",
links: [
o("Leak blog mirror A (placeholder)", "Replace for exercise."),
o("Leak blog mirror B (placeholder)", "Replace for exercise."),
o("Victim countdown timer fiction", "Discuss ethics of naming victims in slides."),
],
},
{
name: "Dedicated extortion & “shame” dumps",
blurb: "Harassment-adjacent tactics; good for talking corporate comms & mental health resources.",
links: [
o("Naming-and-shaming forum fiction", "Placeholder."),
o("Partial data sampler fiction", "Placeholder."),
],
},
{
name: "Dataset marketplaces abutting leaks",
blurb: "Some actors sell “exclusive” archives that overlap with public breaches — tie to Have I Been Pwned literacy.",
links: [
o("Archive reseller slot", "Placeholder."),
o("“Corporate pack” fiction index", "Placeholder."),
],
},
],
},
{
id: "whistleblowing",
icon: "◎",
title: "Whistleblowing & anonymous publishing",
overview:
"Legitimate confidential tip lines use hardened workflows (SecureDrop, encrypted forms). Separate that from random paste sites that claim anonymity.",
literacy:
"Compare source protection, legal shield laws, and newsroom infosec with “anonymous upload” scams.",
subsections: [
{
name: "SecureDrop & newsroom tip lines",
blurb: "Real organizations publish onion addresses after audit — always verify against official media sites.",
links: [
{
title: "SecureDrop (official project)",
note: "Directory of news orgs running SecureDrop — verify each orgs landing page.",
href: "https://securedrop.org/",
},
o("Fictional newsroom SecureDrop slot A", "Placeholder onion — compare key verification steps."),
o("Fictional newsroom SecureDrop slot B", "Placeholder."),
],
},
{
name: "Anonymous tip forms & dead drops",
blurb: "Mix of real civil-society tools and lures — teach cookie hygiene and Tor-only discipline.",
links: [
o("PGP-only mailbox fiction", "Placeholder."),
o("One-time dead drop scheduler (simulated)", "Placeholder."),
o("Whistleblower chat relay fiction", "Contrast with ephemeral messenger threat models."),
],
},
{
name: "Publishing mirrors & censorship circumvention",
blurb: "Some NGOs mirror banned reports via Tor; authenticity still requires out-of-band signing.",
links: [
o("Human-rights mirror fiction slot", "Placeholder."),
o("Election-monitoring scrape fiction", "Placeholder."),
],
},
],
},
{
id: "privacy-services",
icon: "🔐",
title: "Privacy-focused services (legitimate clearnet + onion mirrors)",
overview:
"Many reputable privacy tools operate clearnet first; some publish Tor mirrors for censorship resistance. Teach students to verify keys and URLs from primary domains.",
literacy:
"No tool is magic — combine Tor Browser hygiene, account compartmentalization, and endpoint security.",
subsections: [
{
name: "Encrypted email & calendar",
blurb: "Proton and similar providers publish security models openly — contrast with “anonymous mail” scams.",
links: [
{
title: "Proton (official)",
note: "Encrypted email ecosystem — read security details on the clearnet site.",
href: "https://proton.me/",
},
o("Tor mail provider mirror slot A (placeholder)", "If you add a real audited provider mirror, cite PGP proof."),
o("Tor mail provider mirror slot B (placeholder)", "Placeholder."),
],
},
{
name: "Privacy-oriented search",
blurb: "Privacy-preserving search is mostly clearnet; Tor reduces local ISP visibility but not all tracker risk.",
links: [
{
title: "DuckDuckGo",
note: "Privacy-oriented search (clearnet).",
href: "https://duckduckgo.com/",
},
{
title: "Tor Project — onion services overview",
note: "How hidden services differ from VPN marketing.",
href: "https://www.torproject.org/",
},
o("SearX onion instance fiction", "Placeholder — prefer community-vetted lists."),
],
},
{
name: "File storage & paste hygiene",
blurb: "Onion pastebins vary wildly in ethics — discuss data retention and malware risk.",
links: [
o("Encrypted file locker fiction A", "Placeholder."),
o("Encrypted file locker fiction B", "Placeholder."),
o("Ephemeral paste fiction slot", "Contrast with corporate DLP policies."),
],
},
{
name: "VPN discussion & abuse reporting",
blurb: "VPNs are not anonymity panacea; .onion “review” boards are untrustworthy.",
links: [
o("VPN rumor board (simulated)", "Placeholder."),
o("Provider abuse contact aggregator fiction", "Teach reading Terms + warrant canaries."),
],
},
],
},
{
id: "forums-chat",
icon: "💬",
title: "Forums, imageboards & chat",
overview:
"Underground forums organize by topic: OPSEC tradecraft, exploit research, politics, or market gossip — moderation quality is nil; sourcing is hearsay.",
literacy:
"Train students to read for manipulation, astroturfing, and law-enforcement artifacts.",
subsections: [
{
name: "Cryptography, OPSEC & tradecraft",
blurb: "Mix of sharp practitioners and dangerous half-truths — pair readings with formal crypto courses.",
links: [
o("PGP ritual bulletin fiction", "Placeholder."),
o("Hardware token swap fiction", "Placeholder."),
o("Tor Browser fingerprint thread fiction", "Cross-check with Tor Project docs."),
],
},
{
name: "Exploit research & malware analysis",
blurb: "Some boards parallel academic conf culture; legal exposure depends on jurisdiction and intent.",
links: [
o("RE workshop fiction slot", "Placeholder."),
o("Sandbox telemetry gossip board", "Placeholder."),
],
},
{
name: "Political, protest & censored speech",
blurb: "Tor supports dissent in authoritarian contexts — distinguish from venues that glorify violence.",
links: [
o("Regional protest logistics fiction", "Placeholder — teach proportionality & safety planning."),
o("Citizen journalism onion fiction", "Verify with offline networks."),
],
},
{
name: "Market reputation & drama boards",
blurb: "Reputation threads can be fabricated wholesale — your CyberLux forum sim explores exactly that dynamic.",
links: [
{
title: "Void Aggregate (this deployment)",
note: "Ringed forum UI on the same Next app — local persistence.",
href: "/forum",
},
o("Vendor drama archaeology fiction", "Placeholder."),
o("Scam report aggregator fiction", "Placeholder."),
],
},
{
name: "Realtime chat & IRC adjacency",
blurb: "Many groups still orbit IRC or Matrix bridges — onboarding should cover paste hygiene.",
links: [
o("Bridge relay fiction slot", "Placeholder."),
o("Invite-only Jabber fiction", "Placeholder."),
],
},
],
},
{
id: "beyond-money-laundering",
icon: "₿",
title: "Money flows, mixing & OTC",
overview:
"Intel briefings reference tumblers, chain-hopping, gift-card arbitrage, and OTC desks — laundering is criminal; teach tracing concepts via public chain analytics instead.",
literacy:
"Map KYC/AML responsibilities vs privacy coin tradeoffs without glamorizing evasion.",
subsections: [
{
name: "Mixers & privacy pools (fiction)",
blurb: "Post-mixer attribution is probabilistic — discuss FATF travel rule at high level.",
links: [
o("Mixer directory fiction A", "Placeholder."),
o("Mixer directory fiction B", "Placeholder."),
o("Coinjoin education mirror fiction", "Differentiate licit privacy practice vs laundering."),
],
},
{
name: "OTC & P2P desks (fiction)",
blurb: "Peer listings blend honest traders with scams — escrow literacy matters.",
links: [
o("OTC reputation thread fiction", "Placeholder."),
o("Stablecoin bridge chatter fiction", "Placeholder."),
],
},
],
},
{
id: "beyond-access-brokers",
icon: "🔓",
title: "Initial access, stealer economy & ATO supply",
overview:
"Initial Access Brokers sell footholds; stealer malware feeds credential pipelines — curriculum ties to detection engineering and identity threat detection.",
literacy:
"Focus on defender telemetry: EDR, IdP risky sign-ins, SaaS OAuth grants.",
subsections: [
{
name: "Access listings (fiction)",
blurb: "Screenshots of RDP, VPN, Citrix — reinforce MFA + vault rotation lessons.",
links: [
o("IAB storefront fiction", "Placeholder."),
o("VPN session resale fiction", "Placeholder."),
],
},
{
name: "Stealer logs & infostealer panels (fiction)",
blurb: "MITRE techniques + Sigma rules beat bookmarking forums.",
links: [
o("Stealer panel fiction A", "Placeholder."),
o("Log taxonomy cheat-sheet fiction", "Placeholder."),
],
},
],
},
{
id: "beyond-hosting",
icon: "🖧",
title: "Bulletproof hosting & resilient infrastructure",
overview:
"Abuse teams track hosts that ignore takedowns; fast-flux DNS and bulletproof rhetoric appear in incident narratives.",
literacy:
"Contrast with legitimate bullet-resistant journalism hosting + pro-bono CDNs.",
subsections: [
{
name: "Hosting classifieds (fiction)",
blurb: "No legitimate lab needs this — discuss peering and netblock reputation instead.",
links: [
o("Bulletproof reseller fiction", "Placeholder."),
o("Fast-flux tutorial fiction (do not follow)", "Teach detection only."),
],
},
],
},
{
id: "beyond-osint",
icon: "🔭",
title: "OSINT, counter-OSINT & mirror hunting",
overview:
"Analysts monitor scam duplicates, phishing clones, and typosquatted onions — aligns with your CyberLux mirror-verification storyline.",
literacy:
"Fingerprint HTML, TLS certs, and PGP clearsigned canaries.",
subsections: [
{
name: "Clone hunters & canary trackers (fiction)",
blurb: "Practice verifying signed messages before trusting links.",
links: [
o("Mirror diff gossip board fiction", "Placeholder."),
o("PGP watchlist fiction", "Placeholder."),
{
title: "CyberLux hub (verify signage in sim)",
note: "Main storefront skin — compare sidebar claims to reality.",
href: "/",
},
],
},
],
},
{
id: "beyond-scams",
icon: "⚠",
title: "Scams, “recovery” fraud & trust games",
overview:
"Underground economies are dense with escrow scams, fake hitmen, and “crypto recovery” lures — ideal for behavioral econ labs.",
literacy:
"Red flags: urgency, private vanity proofs, impossible guarantees.",
subsections: [
{
name: "Recovery & refund scams (fiction)",
blurb: "Victims pay twice — teach reporting to FTC/FBI IC3 equivalents.",
links: [
o("Recovery service fiction A", "Placeholder."),
o("Chargeback “expert” fiction", "Placeholder."),
],
},
{
name: "Fake escrow & multisig theater (fiction)",
blurb: "Compare multisig flows your sim shows vs real Bitcoin scripts.",
links: [
o("Escrow theater storefront fiction", "Placeholder."),
],
},
],
},
{
id: "beyond-nation-state",
icon: "🌐",
title: "Geopolitics, hacktivism & rumor ecosystems",
overview:
"Attribution is hard; many boards traffic speculation. Pair with formal CTI vendor reports and government advisories.",
literacy:
"Emphasize evidence tiers: actor aliases vs confirmed indictments.",
subsections: [
{
name: "Cyberconflict chatterboards (fiction)",
blurb: "Narratives skew nationalist — critical media literacy required.",
links: [
o("Regional conflict thread fiction A", "Placeholder."),
o("Regional conflict thread fiction B", "Placeholder."),
],
},
{
name: "Hacktivist ops channels (fiction)",
blurb: "Discuss proportionality, collateral damage, and international humanitarian law at overview level.",
links: [
o("Ops coordination fiction", "Placeholder."),
],
},
],
},
];
export function atlasLinkMatchesQuery(link: AtlasLink, q: string): boolean {
if (!q.trim()) return true;
const s = q.toLowerCase();
return link.title.toLowerCase().includes(s) || link.note.toLowerCase().includes(s);
}
export function atlasCategoryMatchesQuery(cat: AtlasCategory, q: string): boolean {
if (!q.trim()) return true;
const s = q.toLowerCase();
if (cat.title.toLowerCase().includes(s) || cat.overview.toLowerCase().includes(s)) return true;
return cat.subsections.some(
(sub) =>
sub.name.toLowerCase().includes(s) ||
sub.blurb.toLowerCase().includes(s) ||
sub.links.some((L) => atlasLinkMatchesQuery(L, q)),
);
}

78
lib/exchangeState.ts Normal file
View File

@@ -0,0 +1,78 @@
export type ExchangeListing = {
id: string;
kind: "wts" | "wtb";
title: string;
body: string;
price: string;
currency: "BTC" | "XMR" | "USD";
author: string;
ts: number;
};
const KEY = "cyberlux-exchange-v1";
function uid(): string {
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
return `e_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
}
const seed: ExchangeListing[] = [
{
id: "es-1",
kind: "wts",
title: "Hardware entropy module (parody listing)",
body: "Open-box. Local pickup on this onion only. Meet at public node.",
price: "0.015",
currency: "BTC",
author: "EntropyCorp",
ts: Date.now() - 3600000 * 20,
},
{
id: "es-2",
kind: "wtb",
title: "Looking for vintage cipher manuals (replica)",
body: "Budget flexible for the right scan quality.",
price: "120",
currency: "USD",
author: "Collector42",
ts: Date.now() - 3600000 * 8,
},
];
export function loadExchange(): ExchangeListing[] {
if (typeof window === "undefined") return seed;
try {
const raw = localStorage.getItem(KEY);
if (!raw) {
saveExchange(seed);
return seed;
}
const v = JSON.parse(raw) as ExchangeListing[];
return Array.isArray(v) && v.length ? v : seed;
} catch {
return seed;
}
}
export function saveExchange(list: ExchangeListing[]): void {
if (typeof window === "undefined") return;
localStorage.setItem(KEY, JSON.stringify(list));
}
export function addListing(
L: Omit<ExchangeListing, "id" | "ts">,
): ExchangeListing {
const row: ExchangeListing = {
id: uid(),
ts: Date.now(),
kind: L.kind,
title: L.title.trim(),
body: L.body.trim(),
price: L.price.trim(),
currency: L.currency,
author: L.author.trim() || "Anonymous",
};
const all = [row, ...loadExchange()];
saveExchange(all);
return row;
}

12
lib/forumFeedFilter.ts Normal file
View File

@@ -0,0 +1,12 @@
import type { ForumThread } from "@/lib/forumState";
export function threadMatchesQuery(t: ForumThread, q: string): boolean {
const s = q.trim().toLowerCase();
if (!s) return true;
return (
t.title.toLowerCase().includes(s) ||
t.body.toLowerCase().includes(s) ||
(t.author || "").toLowerCase().includes(s) ||
t.topicSlug.toLowerCase().includes(s)
);
}

91
lib/forumRings.ts Normal file
View File

@@ -0,0 +1,91 @@
/** “Subreddits” for Void Aggregate — onion-style board names (rings). */
export type ForumRing = {
slug: string;
shortName: string;
title: string;
about: string;
/** Cosmetic — static for immersion */
members: number;
activeNow: number;
};
export const FORUM_RINGS: ForumRing[] = [
{
slug: "opsec",
shortName: "opsec",
title: "OPSEC & tradecraft",
about: "Burners, compartmentalization, Tor hardening, countersurveillance.",
members: 128_400,
activeNow: 2_847,
},
{
slug: "markets",
shortName: "markets",
title: "Markets & clearing",
about: "Venue stability, escrow policy, downtime watches, fee gossip.",
members: 96_200,
activeNow: 4_102,
},
{
slug: "crypto",
shortName: "chains",
title: "Settlement & chains",
about: "BTC / XMR coin control, feerates, mixers, address hygiene.",
members: 74_800,
activeNow: 1_903,
},
{
slug: "tech",
shortName: "exploit",
title: "Exploit & tooling",
about: "Research PoCs, lab setups — keep it legal in your jurisdiction.",
members: 52_100,
activeNow: 891,
},
{
slug: "reputation",
shortName: "vouch",
title: "Vouch & mirrors",
about: "PGP proofs, phishing reports, signed mirror threads.",
members: 61_300,
activeNow: 2_210,
},
{
slug: "lounge",
shortName: "lounge",
title: "Dead-drop lounge",
about: "Meta, moderation, chaos — off-topic.",
members: 204_000,
activeNow: 8_440,
},
];
const SLUG_SET = new Set(FORUM_RINGS.map((r) => r.slug));
export function getRing(slug: string): ForumRing | undefined {
return FORUM_RINGS.find((r) => r.slug === slug);
}
/** Legacy `category` strings from older saves → ring slug */
export function categoryToSlug(category: string | undefined): string {
const c = (category ?? "").trim();
const map: Record<string, string> = {
Security: "opsec",
Market: "markets",
Tech: "tech",
Crypto: "crypto",
"Off-topic": "lounge",
Reputation: "reputation",
All: "lounge",
};
const hit = map[c];
if (hit && SLUG_SET.has(hit)) return hit;
if (c && SLUG_SET.has(c.toLowerCase())) return c.toLowerCase();
return "lounge";
}
export function formatRingLabel(slug: string): string {
const r = getRing(slug);
return r ? `r/${r.shortName}` : `r/${slug}`;
}

22
lib/forumSort.ts Normal file
View File

@@ -0,0 +1,22 @@
import { displayScore, type ForumThread } from "@/lib/forumState";
export type ForumSortMode = "hot" | "new" | "top";
function hoursSince(ts: number): number {
return (Date.now() - ts) / 3600000;
}
export function hotRank(t: ForumThread): number {
const s = displayScore(t);
const engagement = t.replies.length * 4;
const freshness = Math.max(0, 72 - hoursSince(t.ts)) * 2;
return s + engagement + freshness;
}
export function sortThreads(list: ForumThread[], mode: ForumSortMode): ForumThread[] {
const copy = [...list];
if (mode === "new") copy.sort((a, b) => b.ts - a.ts);
else if (mode === "top") copy.sort((a, b) => displayScore(b) - displayScore(a));
else copy.sort((a, b) => hotRank(b) - hotRank(a));
return copy;
}

345
lib/forumState.ts Normal file
View File

@@ -0,0 +1,345 @@
import { categoryToSlug } from "@/lib/forumRings";
import { SHOP_CHATTER_THREADS } from "@/lib/shopChatterThreads";
export type ForumReply = {
id: string;
author: string;
body: string;
ts: number;
};
export type ForumThread = {
id: string;
/** Ring / sub-board slug, e.g. opsec */
topicSlug: string;
/** @deprecated legacy display — prefer topicSlug + formatRingLabel */
category?: string;
title: string;
author: string;
body: string;
ts: number;
replies: ForumReply[];
/** Seeded score before user votes */
baseScore?: number;
};
const KEY = "cyberlux-forum-v2";
const LEGACY_KEY = "cyberlux-forum-v1";
const VOTE_KEY = "cyberlux-forum-votechoice-v1";
export type VoteChoice = -1 | 0 | 1;
function uid(): string {
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
return `t_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
}
function normalizeThread(raw: ForumThread): ForumThread {
const topicSlug = raw.topicSlug?.trim() || categoryToSlug(raw.category);
const baseScore = typeof raw.baseScore === "number" ? raw.baseScore : 1;
const { category, ...rest } = raw;
return {
...rest,
topicSlug,
baseScore,
...(category ? { category } : {}),
};
}
const SHOP_CHATTER_FORUM: ForumThread[] = SHOP_CHATTER_THREADS.map((t) =>
normalizeThread({
id: t.id,
topicSlug: t.topicSlug,
title: t.title,
author: t.author,
body: t.body,
ts: t.ts,
baseScore: t.baseScore,
replies: t.replies,
}),
);
const seedThreads: ForumThread[] = [
{
id: "seed-1",
topicSlug: "opsec",
category: "Security",
author: "GhostLayer",
title: "Tor browser hardening checklist",
body: "Post your best practices for compartmentalization and DNS leaks.",
ts: Date.now() - 86400000 * 3,
baseScore: 214,
replies: [
{
id: "r1",
author: "NodeOp",
body: "Never mix clearnet VPN identity with onion circuits on the same profile.",
ts: Date.now() - 86400000 * 2,
},
],
},
{
id: "seed-2",
topicSlug: "markets",
category: "Market",
author: "PlainVendor",
title: "Reputation threads — read before you buy",
body: "Link proof of prior deals. No PGP = no trust.",
ts: Date.now() - 86400000,
baseScore: 892,
replies: [],
},
];
const npcThreads: ForumThread[] = [
{
id: "npc-pinned-rules",
topicSlug: "lounge",
category: "Off-topic",
author: "STAFF",
title: "PINNED — Escrow rules & mirror verification",
body:
"All first-time vendor deals route through platform escrow. Do not finalize early.\n\n" +
"Before you load any .onion, cross-check the hostname against the signed mirror list on the main link. " +
"If the signature does not verify, assume phishing.\n\n" +
"No begging, no shill chains without proof.",
ts: Date.now() - 86400000 * 21,
baseScore: 4_021,
replies: [
{
id: "npc-pinned-rules-r1",
author: "ModQueue",
body: "Reporting impersonation: include the exact onion string and a signed message if you have one.",
ts: Date.now() - 86400000 * 20,
},
],
},
{
id: "npc-mirror-check",
topicSlug: "reputation",
category: "Reputation",
author: "watchtower_09",
title: "Mirror list still matching signed post from last week?",
body:
"Cross-posting my onion fingerprint here for sanity check — want to be sure Im not on a clone before I top up wallet credit.",
ts: Date.now() - 86400000 * 5,
baseScore: 1_876,
replies: [
{
id: "npc-mirror-check-r1",
author: "PGPOrBust",
body:
"Ran gpg --verify on the hub canary + mirror block. Three v3 hosts, checksums line up. Youre good.",
ts: Date.now() - 86400000 * 5 + 3600000,
},
{
id: "npc-mirror-check-r2",
author: "EU_buyer",
body:
"Been routing orders through the main hub for a couple months. Comms lag on weekends but escrow never ghosted me.",
ts: Date.now() - 86400000 * 4,
},
],
},
{
id: "npc-volume-banter",
topicSlug: "markets",
category: "Market",
author: "ledgerline",
title: "Anyone else seeing steady checkout traffic on the hub?",
body:
"Not asking for numbers — just weird how quiet the FUD threads are when the sites been up stable. Either the complainers aged out or liquidity actually picked up.",
ts: Date.now() - 86400000 * 2,
baseScore: 3_409,
replies: [
{
id: "npc-volume-banter-r1",
author: "quietBag",
body:
"Big spenders dont brag in public. Youll see it in voucher turnover and vendor queue times.",
ts: Date.now() - 86400000 * 2 + 7200000,
},
],
},
{
id: "npc-ui-thread",
topicSlug: "tech",
category: "Tech",
author: "saltfade",
title: "New neon skin on main link — performance feels tighter",
body:
"Old build used to hitch on the product grid. Whatever they shipped last cycle, Lighthouse numbers on my side look cleaner. Anyone benchmark on low-RAM tails?",
ts: Date.now() - 86400000,
baseScore: 667,
replies: [
{
id: "npc-ui-thread-r1",
author: "tab_hoarder",
body: "Same. Also the checkout step doesnt double-fire submit anymore — small but huge.",
ts: Date.now() - 86400000 + 1800000,
},
],
},
];
const READONLY_THREAD_IDS = new Set(
[...seedThreads, ...npcThreads, ...SHOP_CHATTER_FORUM].map((t) => t.id),
);
function mergeSystemThreads(userThreads: ForumThread[]): ForumThread[] {
const have = new Set(userThreads.map((t) => t.id));
const systemPools = [...npcThreads, ...SHOP_CHATTER_FORUM].map(normalizeThread);
const extra = systemPools.filter((t) => !have.has(t.id));
const merged = [...extra, ...userThreads.map(normalizeThread)];
merged.sort((a, b) => b.ts - a.ts);
return merged;
}
function migrateLegacyStorage(): ForumThread[] | null {
if (typeof window === "undefined") return null;
try {
const raw = localStorage.getItem(LEGACY_KEY);
if (!raw) return null;
const v = JSON.parse(raw) as ForumThread[];
if (!Array.isArray(v)) return null;
localStorage.removeItem(LEGACY_KEY);
return v.map((t) => normalizeThread({ ...t, topicSlug: t.topicSlug || categoryToSlug(t.category) }));
} catch {
return null;
}
}
function loadRawUserThreads(): ForumThread[] {
if (typeof window === "undefined") return [];
const migrated = migrateLegacyStorage();
if (migrated?.length) {
const clean = migrated.filter((t) => t && typeof t.id === "string" && !READONLY_THREAD_IDS.has(t.id));
if (clean.length) localStorage.setItem(KEY, JSON.stringify(clean));
}
try {
const raw = localStorage.getItem(KEY);
if (!raw) return [];
const v = JSON.parse(raw) as ForumThread[];
if (!Array.isArray(v)) return [];
return v
.filter((t) => t && typeof t.id === "string" && !READONLY_THREAD_IDS.has(t.id))
.map(normalizeThread);
} catch {
return [];
}
}
export function loadForum(): ForumThread[] {
if (typeof window === "undefined") {
return mergeSystemThreads(seedThreads.map(normalizeThread));
}
const user = loadRawUserThreads();
const base = user.length ? user : seedThreads.map(normalizeThread);
return mergeSystemThreads(base);
}
export function saveForum(threads: ForumThread[]): void {
if (typeof window === "undefined") return;
const persist = threads.filter((t) => !READONLY_THREAD_IDS.has(t.id)).map(normalizeThread);
localStorage.setItem(KEY, JSON.stringify(persist));
}
export function addThread(
t: Omit<ForumThread, "id" | "ts" | "replies"> & { replies?: ForumReply[] },
): ForumThread {
const thread: ForumThread = normalizeThread({
id: uid(),
ts: Date.now(),
replies: t.replies ?? [],
topicSlug: t.topicSlug,
category: t.category,
title: t.title.trim(),
author: t.author.trim() || "Anonymous",
body: t.body.trim(),
baseScore: 1,
});
saveForum([thread, ...loadRawUserThreads()]);
return thread;
}
export function addReply(threadId: string, author: string, body: string): boolean {
if (READONLY_THREAD_IDS.has(threadId)) return false;
const trimmed = body.trim();
if (!trimmed) return false;
const user = loadRawUserThreads();
const i = user.findIndex((x) => x.id === threadId);
if (i < 0) return false;
const reply: ForumReply = {
id: uid(),
author: author.trim() || "Anonymous",
body: trimmed,
ts: Date.now(),
};
const next = [...user];
next[i] = { ...next[i], replies: [...next[i].replies, reply] };
saveForum(next);
return true;
}
export function getThread(id: string): ForumThread | undefined {
const hit = loadForum().find((t) => t.id === id);
return hit ? normalizeThread(hit) : undefined;
}
function loadVoteMap(): Record<string, VoteChoice> {
if (typeof window === "undefined") return {};
try {
const raw = localStorage.getItem(VOTE_KEY);
if (!raw) return {};
const o = JSON.parse(raw) as Record<string, number>;
const out: Record<string, VoteChoice> = {};
for (const [k, v] of Object.entries(o)) {
if (v === -1 || v === 0 || v === 1) out[k] = v;
}
return out;
} catch {
return {};
}
}
function saveVoteMap(m: Record<string, VoteChoice>): void {
if (typeof window === "undefined") return;
localStorage.setItem(VOTE_KEY, JSON.stringify(m));
}
export function getVoteForThread(threadId: string): VoteChoice {
return loadVoteMap()[threadId] ?? 0;
}
/** Set user vote (-1 down, 0 clear, 1 up). Reddit-style toggle handled in UI. */
export function setVoteForThread(threadId: string, next: VoteChoice): void {
const m = loadVoteMap();
m[threadId] = next;
saveVoteMap(m);
}
export function displayScore(t: ForumThread): number {
const base = t.baseScore ?? 1;
const v = typeof window !== "undefined" ? getVoteForThread(t.id) : 0;
return base + v;
}
/** System-seeded threads — users cannot append replies (open a new thread instead). */
export function isReadOnlyThread(threadId: string): boolean {
return READONLY_THREAD_IDS.has(threadId);
}
const PINNED_THREAD_IDS = new Set(["npc-pinned-rules"]);
export function isPinnedThread(t: ForumThread): boolean {
return PINNED_THREAD_IDS.has(t.id) || /^PINNED\b/i.test(t.title.trim());
}
export function sortPinnedThreads(list: ForumThread[]): ForumThread[] {
return [...list].sort((a, b) => {
const pa = PINNED_THREAD_IDS.has(a.id) ? 0 : 1;
const pb = PINNED_THREAD_IDS.has(b.id) ? 0 : 1;
if (pa !== pb) return pa - pb;
return b.ts - a.ts;
});
}

14
lib/merchantBtc.ts Normal file
View File

@@ -0,0 +1,14 @@
/**
* Set NEXT_PUBLIC_MERCHANT_BTC_ADDRESS (and MERCHANT_BTC_ADDRESS for server verify) in .env.local
* to your Bitcoin receiving address (e.g. bc1q...).
*/
export function getMerchantBtcAddress(): string {
if (typeof window !== "undefined") {
return (process.env.NEXT_PUBLIC_MERCHANT_BTC_ADDRESS || "").trim();
}
return (process.env.MERCHANT_BTC_ADDRESS || process.env.NEXT_PUBLIC_MERCHANT_BTC_ADDRESS || "").trim();
}
export function isMerchantBtcConfigured(): boolean {
return getMerchantBtcAddress().length > 0;
}

83
lib/metaGame.ts Normal file
View File

@@ -0,0 +1,83 @@
export type VaultReceipt = {
id: string;
title: string;
desc: string;
date: string;
severity: "normal" | "weird" | "redacted";
};
export type VaultState = {
version: 1;
keys: string[];
receipts: VaultReceipt[];
flags: Record<string, boolean>;
};
const STORAGE_KEY = "cyberlux:vault:v1";
export function defaultVaultState(): VaultState {
return {
version: 1,
keys: [],
receipts: [
{ id: "8821", title: "Receipt #8821", desc: "For 1x Used Password", date: "2026-03-12", severity: "normal" },
{ id: "9044", title: "Receipt #9044", desc: "For 3x Liquid Motivation", date: "2026-03-15", severity: "normal" },
{ id: "X-0000", title: "Mystery File", desc: "Unknown executable. DO NOT OPEN.", date: "1970-01-01", severity: "redacted" },
],
flags: {},
};
}
export function safeParseVaultState(raw: string | null): VaultState {
if (!raw) return defaultVaultState();
try {
const v = JSON.parse(raw) as Partial<VaultState>;
const base = defaultVaultState();
const keys = Array.isArray(v.keys) ? v.keys.filter((k) => typeof k === "string") : base.keys;
const receipts = Array.isArray(v.receipts)
? (v.receipts as any[]).filter(Boolean).map((r) => ({
id: String(r.id ?? ""),
title: String(r.title ?? "Receipt"),
desc: String(r.desc ?? ""),
date: String(r.date ?? ""),
severity: (r.severity === "weird" || r.severity === "redacted" || r.severity === "normal") ? r.severity : "normal",
}))
: base.receipts;
const flags = v.flags && typeof v.flags === "object" ? (v.flags as Record<string, boolean>) : base.flags;
return { version: 1, keys, receipts, flags };
} catch {
return defaultVaultState();
}
}
export function loadVaultState(): VaultState {
if (typeof window === "undefined") return defaultVaultState();
return safeParseVaultState(window.localStorage.getItem(STORAGE_KEY));
}
export function saveVaultState(state: VaultState) {
if (typeof window === "undefined") return;
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
}
export function addKey(state: VaultState, key: string): VaultState {
if (!key) return state;
if (state.keys.includes(key)) return state;
return { ...state, keys: [...state.keys, key] };
}
export function addReceipt(state: VaultState, receipt: VaultReceipt): VaultState {
if (!receipt?.id) return state;
if (state.receipts.some((r) => r.id === receipt.id)) return state;
return { ...state, receipts: [receipt, ...state.receipts].slice(0, 24) };
}
export function setFlag(state: VaultState, flag: string, value: boolean): VaultState {
if (!flag) return state;
return { ...state, flags: { ...state.flags, [flag]: value } };
}
export function hasFlag(state: VaultState, flag: string) {
return Boolean(state.flags?.[flag]);
}

16
lib/nodeSkinChrome.ts Normal file
View File

@@ -0,0 +1,16 @@
/** Outer chrome for `/w/[slug]` relay pages — eight visual buckets. */
export const NODE_SKIN_CHROME: string[] = [
"min-h-screen bg-[#1a0505] text-[#ff7b54] selection:bg-[#ff7b54] selection:text-black font-mono border-x-[14px] border-[#3d0c02]",
"min-h-screen bg-[#0b132b] text-[#bee9e8] font-serif tracking-wide bg-[radial-gradient(ellipse_at_top,_#1c2541_0%,_#0b132b_65%)]",
"min-h-screen bg-[#f4f1de] text-[#1d3557] font-mono border-[12px] border-dashed border-[#1d3557]",
"min-h-screen bg-[#10002b] text-[#e0aaff] bg-[linear-gradient(165deg,_120deg,_#240046_0%,_#10002b_40%,_#3c096c_100%)] font-sans",
"min-h-screen bg-[#2b2d42] text-[#edf2f4] font-sans uppercase tracking-widest text-sm border-y-[16px] border-[#ef233c]",
"min-h-screen bg-[#0d2818] text-[#95d5b2] font-serif border-double border-4 border-[#40916c] m-0 p-0 outline outline-4 outline-[#2d6a4f]",
"min-h-screen bg-black text-[#fcee0a] font-mono skew-y-0 [text-shadow:2px_2px_0_#ff00ff]",
"min-h-screen bg-[#fffcf2] text-[#220c10] font-serif [background-image:repeating-linear-gradient(0deg,transparent,transparent_23px,#e8d5b5_24px)]",
];
export function nodeSkinClass(skinIndex: number): string {
return NODE_SKIN_CHROME[((skinIndex % NODE_SKIN_CHROME.length) + NODE_SKIN_CHROME.length) % NODE_SKIN_CHROME.length];
}

View File

@@ -0,0 +1,92 @@
// AUTO-GENERATED by scripts/generate-onion-config.cjs — do not edit
export const CYBERLUX_ENTRY_VALUES = [
"hub",
"wiki",
"w",
"account",
"arb-academy",
"awards",
"barter",
"chatter",
"checkout",
"comparison",
"conspiracies",
"darknet-atlas",
"dashboard",
"drop-box",
"drops",
"easter-eggs",
"exchange",
"forum",
"game",
"inner-circle",
"links",
"market",
"messages",
"mixer",
"presswire",
"raffle",
"red-room",
"reviews",
"sanctuary",
"search",
"secret-layer",
"security-analysis",
"sign-in",
"sign-up",
"support",
"syndicate",
"testimonials",
"trees",
"trust",
"vault",
"vendors",
"wallets",
"webring",
] as const;
export type CyberluxEntry = (typeof CYBERLUX_ENTRY_VALUES)[number];
export const DEDICATED_ROOT = {
"account": "/account",
"arb-academy": "/arb-academy",
"awards": "/awards",
"barter": "/barter",
"chatter": "/chatter",
"checkout": "/checkout",
"comparison": "/comparison",
"conspiracies": "/conspiracies",
"darknet-atlas": "/darknet-atlas",
"dashboard": "/dashboard",
"drop-box": "/drop-box",
"drops": "/drops",
"easter-eggs": "/easter-eggs",
"exchange": "/exchange",
"forum": "/forum",
"game": "/game",
"inner-circle": "/inner-circle",
"links": "/links",
"market": "/market",
"messages": "/messages",
"mixer": "/mixer",
"presswire": "/presswire",
"raffle": "/raffle",
"red-room": "/red-room",
"reviews": "/reviews",
"sanctuary": "/sanctuary",
"search": "/search",
"secret-layer": "/secret-layer",
"security-analysis": "/security-analysis",
"sign-in": "/sign-in",
"sign-up": "/sign-up",
"support": "/support",
"syndicate": "/syndicate",
"testimonials": "/testimonials",
"trees": "/trees",
"trust": "/trust",
"vault": "/vault",
"vendors": "/vendors",
"wallets": "/wallets",
"webring": "/webring",
} as const;

3
lib/placeholderOnion.ts Normal file
View File

@@ -0,0 +1,3 @@
/** Fictional v3-style placeholder for all simulated .onion URLs — replace when curating for class. */
export const PLACEHOLDER_ONION_HOST = "111111111111111111111.onion";
export const PLACEHOLDER_ONION_URL = `http://${PLACEHOLDER_ONION_HOST}`;

333
lib/shopCatalog.ts Normal file
View File

@@ -0,0 +1,333 @@
/**
* CyberLux storefront catalog — in-universe listings (see site footer for real-world status).
*
* Images: each SKU gets a stable Unsplash URL via `resolveProductImage` (hashed by id).
* To use your own file: put it in `public/shop-assets/` and set `assetFile: "my-photo.webp"` on that SKU.
*/
import { sellerIdForProduct } from "@/lib/shopSellers";
export type ShopCurrency = "BTC" | "ETH" | "USD" | "XMR";
export type ShopProduct = {
id: string;
name: string;
description: string;
price: number;
currency: ShopCurrency;
category: string;
/** Assigned in SHOP_PRODUCTS from sellerIdForProduct — stable per SKU */
sellerId: string;
limited?: boolean;
glowColor?: string;
/** Drop a file at public/shop-assets/{assetFile} to override the remote image */
assetFile?: string;
};
/** Unsplash photo paths — royalty-free hotlink; rotate by product id */
const UNSPLASH_ROTATOR = [
"photo-1563013544-824ae1b704d3",
"photo-1553062407-98eeb64c6a62",
"photo-1523275335684-37898b6baf30",
"photo-1505740420928-5e560c06d30e",
"photo-1542291026-7eec264c27ff",
"photo-1526170375885-4d8ecf77b99f",
"photo-1518544801979-152483f7e79e",
"photo-1550751827-4bd374c3f58b",
"photo-1618005188914-3c8315f33e6f",
"photo-1558618047-3c8c76ca7d13",
"photo-1551288049-bebda4e38f71",
"photo-1635070041078-e363dbe005cb",
"photo-1517694712202-14dd9538aa97",
"photo-1526374965328-7f61d4dc18c5",
"photo-1555949963-aa79dcee981c",
"photo-1614854262312-209b1c0b2c7b",
"photo-1592503254549-83d24b4fc4a6",
"photo-1593359677876-a0bb1c3a8095",
"photo-1611162616305-c69b3fa7fbe0",
"photo-1581091226825-a6a2a5aee158",
"photo-1518770660439-4636190af475",
"photo-1460925895917-afdab827c52f",
"photo-1451187580459-43490279c0fa",
"photo-1582719478250-c89cae4dc85b",
"photo-1559757148-5c350d0d3c56",
"photo-1584433144859-1fc3ab64a54f",
"photo-1512941937669-90a1b58e7e9c",
"photo-1605812860427-4024433a70fd",
"photo-1563986768609-322da13575f3",
"photo-1639762681485-074b7f938ba0",
] as const;
function hashId(id: string): number {
let h = 0;
for (let i = 0; i < id.length; i++) h = (h + id.charCodeAt(i) * (i + 1)) % 2147483647;
return Math.abs(h);
}
/** Remote image URL (internet) or your file under public/shop-assets/ */
export function resolveProductImage(p: ShopProduct): string {
if (p.assetFile?.trim()) {
return `/shop-assets/${p.assetFile.replace(/^\/+/, "")}`;
}
const idx = hashId(p.id) % UNSPLASH_ROTATOR.length;
const path = UNSPLASH_ROTATOR[idx]!;
return `https://images.unsplash.com/${path}?w=800&q=85&auto=format&fit=crop`;
}
const GLOWS = [
"neon-cyan",
"neon-purple",
"neon-pink",
"neon-green",
"neon-yellow",
"neon-red",
"neon-blue",
] as const;
type Seed = Omit<ShopProduct, "id" | "glowColor" | "category" | "sellerId"> & { id?: string };
type ProductDraft = Omit<ShopProduct, "sellerId">;
function block(category: string, items: Seed[]): ProductDraft[] {
return items.map((raw, j) => {
const id = raw.id ?? `${category.slice(0, 3)}-${hashId(`${category}-${raw.name}-${j}`).toString(36)}`;
const { id: _drop, ...rest } = raw;
return {
id,
glowColor: GLOWS[hashId(id) % GLOWS.length],
...rest,
category,
};
});
}
/** Everything offered in the shop — expand by adding more `block(...)` rows */
const ALL_BLOCKS: ProductDraft[] = [
...block("BioEnhancements", [
{ name: "Neural Stimulant Injector", description: "Pre-filled autoinjector trainers — packaging mirrors EU grey-market wording; discuss harm reduction with your crew before use.", price: 0.085, currency: "BTC", limited: true },
{ name: "Cognitive patch kit (12h)", description: "Transdermal placebo narrative for discussing FDA vs grey-market copy.", price: 0.019, currency: "XMR" },
{ name: "Synaptic dampener v2", description: "Plot device for too much focus cyberpunk arcs — pure chrome.", price: 0.042, currency: "ETH" },
{ name: "RNA mimic cream (theatrical)", description: "Salon-grade tub with aggressive RNA branding — parse the ingredient deck before you trust the claims.", price: 124, currency: "USD" },
{ name: "Dream-state diffuser oil", description: "Diffuser blend marketed for lucid downtime — ships as 30ml amber bottle, COA on request.", price: 0.007, currency: "BTC" },
{ name: "Circadian hijack glasses", description: "Blue-shift fable hardware — compare to real sleep science slides.", price: 0.011, currency: "ETH" },
{ name: "Quad-core adrenaline tabs", description: "Press-fit tabs, stim-forward copy — contraindicated if you run hot BP; novelty packaging only where law allows.", price: 0.003, currency: "BTC" },
{ name: "Empathy rollback serum", description: "Satirical item naming — discuss manipulation in vendor prose.", price: 0.028, currency: "XMR", limited: true },
{ name: "Vagus nerve tickler wand", description: "Machined aluminum grip, wellness-grade marketing — verify medical claims independently.", price: 89, currency: "USD" },
{ name: "Mitochondria vanity stack", description: "Stacks-Sigma meme inventory for listing trust workshops.", price: 0.015, currency: "ETH" },
{ name: "CRISPR cosplay pipette set", description: "Plastic props only; lab safety tangent.", price: 0.008, currency: "BTC" },
{ name: "Nano-lattice bandage roll", description: "Tactical-wrap roll with lattice weave claims — IFAK-adjacent staging prop.", price: 0.031, currency: "XMR" },
{ name: "Hormone whisperer lozenges", description: "Wild claims section — annotate red flags.", price: 42, currency: "USD" },
{ name: "Deep-sleep coffin pod (mini)", description: "Showpiece prop; ergonomics vs marketing photos.", price: 1.12, currency: "BTC", limited: true },
{ name: "Bio-firewall nasal gel", description: "Saline-forward gel marketed as bio-firewall — novelty wellness SKU with aggressive copy.", price: 0.006, currency: "ETH" },
]),
...block("Financial", [
{ name: "Quantum Counterfeit Notes", description: "Euro/USD facsimile narrative — contrast legal tender training.", price: 1.25, currency: "ETH", limited: true },
{ name: "QuantumForged Euros brick", description: "UV-reactive novelty brick facsimile — training prop for banknote exam teams.", price: 2.4, currency: "BTC" },
{ name: "Wash-trade tutorial ledger", description: "Ledger skinsheet pack demonstrating wash arcs — compliance training desks only.", price: 350, currency: "USD" },
{ name: "Tornado nostalgia mixer tile", description: "Ceramic coaster etched with retro tumbler motif — office lore for chain-hopping traders.", price: 0.018, currency: "XMR" },
{ name: "Bearer bond hologram folio", description: "Hologram theatre for verifying embossing vs phishing scans.", price: 0.055, currency: "BTC" },
{ name: "Flash-loan bedtime story PDF", description: "Illustrated DeFi risk fable — onboarding gift for desk traders; watermarked PDF drop.", price: 0.002, currency: "ETH" },
{ name: "OTC desk stress toy", description: "Squeeze toy shaped like a bid/ask spread arrow.", price: 19, currency: "USD" },
{ name: "Ponzi pyramid desk lamp", description: "Conversation starter for MLM vs crypto scams.", price: 0.009, currency: "BTC" },
{ name: "ISO-4217 mood ring set", description: "Currency-code jewelry — macroeconomics icebreaker.", price: 0.014, currency: "ETH" },
{ name: "Dark-pool goggles (UV)", description: "UV-party wraparounds with liquidity-pool etch — con-floor flex piece.", price: 76, currency: "USD" },
{ name: "Satoshi séance board", description: "Ouija-but-for-txids gag merch for history-of-Bitcoin week.", price: 0.022, currency: "XMR" },
{ name: "Rug-pull rip cord keychain", description: "Pull cord plays airhorn.wav — memes in risk lectures.", price: 12, currency: "USD" },
{ name: "Synthetic stablecoin snow globe", description: "Glitter settles slowly — talk peg mechanics.", price: 0.033, currency: "ETH" },
{ name: "Wire-instructions rubber stamp", description: "Rubber stamp of a phishing wire template — redact exercise.", price: 28, currency: "USD" },
{ name: "Credit-default swap aromatherapy", description: "Scented candles named after 2008 instruments — dark humor.", price: 0.004, currency: "BTC" },
]),
...block("Identity", [
{ name: "Ghost Identity Pack", description: "Novelty document starter folio — customs-safe props only; chain-of-custody card included.", price: 3.8, currency: "BTC", limited: true },
{ name: "Burner SSN bingo card", description: "Bingo of invalid patterns; teaches checksum thinking.", price: 0.012, currency: "ETH" },
{ name: "Deepfake voice coupon (fake)", description: "Coupon promising cloned voice — discuss consent frameworks.", price: 54, currency: "USD" },
{ name: "Rubber face mesh MVP", description: "Theatrical mask — biometrics vs presentation attack discussion.", price: 0.19, currency: "XMR" },
{ name: "Nom de guerre generator dial", description: "Spinner toy producing alias suggestions — OPSEC naming.", price: 22, currency: "USD" },
{ name: "PGP key birthday card", description: "Paper card with mini public key block — party trick.", price: 0.005, currency: "BTC" },
{ name: "SIM-swap panic button (prop)", description: "Big red prop — carrier escalation roleplay.", price: 15, currency: "USD" },
{ name: "Synthetic genealogy tome", description: "Fake family tree book for social-engineering awareness.", price: 0.041, currency: "ETH" },
{ name: "Iris-print latte art stencil", description: "Coffee stencil of an iris — biometrics humor.", price: 9, currency: "USD" },
{ name: "Deadname incinerator USB shell", description: "Empty USB case labeled dramatically — data hygiene talk.", price: 0.017, currency: "XMR" },
{ name: "Notary cosplay embosser", description: "Manual embosser with comic sans seal — trust UX jokes.", price: 31, currency: "USD" },
{ name: "Ghost resume parchment", description: "Aged paper stock for redacted CV workshop.", price: 0.008, currency: "BTC" },
{ name: "Witness protection hoodie", description: "Blank hoodie with detachable name tag — anonymity chat.", price: 0.024, currency: "ETH" },
{ name: "Forged signature practice pad", description: "Legal-only handwriting exercise pad — anti-fraud angle.", price: 17, currency: "USD" },
]),
...block("Security", [
{ name: "ZeroDay Exploit Kit (sandbox)", description: "Lab-only PoC bundle narrative — legal use disclaimers printed on sleeve.", price: 12.5, currency: "ETH", limited: true },
{ name: "Darknet VPN Router", description: "Hardware VLAN folklore; compare to real router hardening checklists.", price: 0.5, currency: "XMR" },
{ name: "Air-gapped Faraday lunchbox", description: "Metal mesh tin for phones — teaches Faraday basics.", price: 44, currency: "USD" },
{ name: "EDR evasion board game", description: "Roll dice to patch telemetry — tabletop IR prep.", price: 0.026, currency: "BTC" },
{ name: "Tor-shaped stress squishy", description: "Slow-rise foam onion — desk lounge swag for relay operators.", price: 11, currency: "USD" },
{ name: "Memory-safe tea cozy", description: "Knitted cozy labeled no buffer overflows here.", price: 0.013, currency: "ETH" },
{ name: "ROP-chain friendship bracelet", description: "Stainless charms stamped with gadget glyphs — wear-stack for con-floor insiders.", price: 7, currency: "USD" },
{ name: "Purple-team piñata", description: "Filled with paper findings slips — team-building prop.", price: 33, currency: "USD" },
{ name: "Canary token confetti cannon", description: "Party cannon shoots paper canary strings — debut alerting.", price: 0.021, currency: "XMR" },
{ name: "SIEM-themed scented candle", description: "Smells like log rotation — joke SKU for SOC night shift.", price: 18, currency: "USD" },
{ name: "Firewall prayer candle", description: "Saints of iptables — discuss faith vs config management.", price: 0.004, currency: "BTC" },
{ name: "Quantum-safe bumper sticker", description: "Bumper sticker pack hyping PQC migration timelines.", price: 0.009, currency: "ETH" },
{ name: "Bug-bounty bingo night kit", description: "Cards with OWASP categories — socialize triage vocabulary.", price: 26, currency: "USD" },
{ name: "Yubikey trench coat lining", description: "Sew-in loops for 12 keys — physical key mgmt humor.", price: 0.038, currency: "BTC" },
{ name: "Password-spray supersoaker (empty)", description: "Bright plastic supersoaker — discuss rate limits metaphorically.", price: 14, currency: "USD" },
{ name: "Red-team nerf darts (foam)", description: "Foam darts tagged lateral movement — safe play fights.", price: 0.006, currency: "XMR" },
]),
...block("Data Leaks", [
{ name: "Breach-era hard drive paperweight", description: "Resin block with fake platters — data destruction talking point.", price: 0.016, currency: "BTC" },
{ name: "CSV of emotions (parody)", description: "Joke spreadsheet about feelings — privacy vs oversharing.", price: 3, currency: "USD" },
{ name: "Anonymized pet photos dataset", description: "Synthetic dog shots, metadata-stripped tarball — ML sandbox starter.", price: 0.011, currency: "ETH" },
{ name: "Pastebin snowshoes", description: "Foam snowshoes with expiring URL stickers — opsec metaphor.", price: 55, currency: "USD" },
{ name: "Doxxing dodgeball (foam)", description: "Foam stress ball stenciled PII — conference giveaway for privacy talks.", price: 12, currency: "USD" },
{ name: "Hash-slinging souvenir spatula", description: "Kitchen spatula etched with SHA-256 joke.", price: 0.007, currency: "BTC" },
{ name: "Journalists USB condom charm", description: "Tiny rubber USB blocker on keyring — supply-chain hygiene.", price: 8, currency: "USD" },
{ name: "DLC for older leaks (empty case)", description: "Empty steel case promising expansion packs — hype satire.", price: 0.029, currency: "XMR" },
{ name: "Redaction marker stadium pack", description: "Black markers labeled for FOIA fantasy workshops.", price: 19, currency: "USD" },
{ name: "Telemetry piñata (no network)", description: "Offline piñata with paper events — logging trivia.", price: 27, currency: "USD" },
{ name: "GDPR meditation tape", description: "Cassette of white noise labeled right to be forgotten.", price: 0.005, currency: "ETH" },
{ name: "Metadata shedding lint roller", description: "Gag lint roller removes EXIF — teach real exiftool after.", price: 6, currency: "USD" },
{ name: "Leak-themed escape room clue pack", description: "Chain-of-custody puzzle cards for escape-room operators — sealed deck.", price: 0.02, currency: "BTC" },
{ name: "Ransom note magnetic poetry", description: "Fridge magnets of cliché ransom phrases — decrypt jokes.", price: 15, currency: "USD" },
]),
...block("Weapons", [
{ name: "Decommissioned prop receiver (solid resin)", description: "Inert replica for armourer paperwork exercises — no moving parts.", price: 0.32, currency: "XMR", limited: true },
{ name: "Orange-tip training carbine (airsoft)", description: "Clear plastic training piece — jurisdiction checklist included.", price: 199, currency: "USD" },
{ name: "Ballistic gel dessert mold", description: "Silicone mold shaped like gel block — baking + terminal ballistics pun.", price: 24, currency: "USD" },
{ name: "Blade geometry slide rule", description: "Plastic slide rule for grind angles — metallurgy intro.", price: 0.014, currency: "ETH" },
{ name: "Non-firing museum rack mount", description: "Wall mount for decommissioned props — discuss secure storage law.", price: 48, currency: "USD" },
{ name: "Rubber training bayonet", description: "Soft bayonet for stage combat class.", price: 32, currency: "USD" },
{ name: "Blueprints wall art (demil)", description: "Poster schematics with demilled watermark — ITAR conversation.", price: 0.018, currency: "BTC" },
{ name: "Snap-cap choir set", description: "Harmless snap caps for chorus-line dry-fire drills (supervised).", price: 41, currency: "USD" },
{ name: "Range officer clipboard folio", description: "Clipboard with safety checklist prompts.", price: 17, currency: "USD" },
{ name: "Kevlar-thread friendship bracelet", description: "Braided accent band — marketing references aramid; tensile spec on vendor sheet.", price: 0.009, currency: "XMR" },
]),
...block("Forgery", [
{ name: "Intaglio practice plate (blank)", description: "Blank zinc intaglio plate for printmaking practice — keep usage lawful.", price: 67, currency: "USD" },
{ name: "UV counterfeit reveal postcard set", description: "Postcards with hidden UV layers — doc verification labs.", price: 0.012, currency: "BTC" },
{ name: "Microprint magnifier chain", description: "Jeweler loupe necklace — inspect fine printing legitimately.", price: 21, currency: "USD" },
{ name: "Hologram sticker starter pack", description: "generic shiny stickers — compare to banknote holograms.", price: 0.008, currency: "ETH" },
{ name: "Passport photo booth curtain (prop)", description: "Curtain fabric roll for mock booth setup.", price: 56, currency: "USD" },
{ name: "Raised-seal cosplay wax sticks", description: "Assorted wax sticks for hobby embossing — not for government documents.", price: 13, currency: "USD" },
{ name: "Typography-of-banknotes poster", description: "Serif anatomy poster for design-history class.", price: 0.015, currency: "XMR" },
{ name: "Frank Abagnale movie night kit", description: "Popcorn + discussion guide — fraud history framed responsibly.", price: 9, currency: "USD" },
{ name: "Watermark cotton paper sampler", description: "Sampler pack of cotton bond — conservation chemistry tangent.", price: 34, currency: "USD" },
{ name: "Forgery panic flowchart scroll", description: "Wall scroll for if you find a fake, then… decisions.", price: 0.006, currency: "BTC" },
{ name: "Embossing press desk toy", description: "Desktop lever press for foil hobby work — keep off official parchment.", price: 88, currency: "USD" },
{ name: "Counterfeit color-match Pantone joke book", description: "Parody swatch book — teach perceptual tricks.", price: 0.022, currency: "ETH" },
{ name: "Notarization rubber duck", description: "Duck with stamp hat — debug your trust assumptions.", price: 5, currency: "USD" },
]),
...block("Chemicals", [
{ name: "Synthetic Euphoria Pills (inert chalk)", description: "Chalk press labeled for rhetoric class — discuss DARE vs harm reduction.", price: 0.024, currency: "BTC" },
{ name: "Liquid Dream Serum (colored water)", description: "Glass dram with dye — prop only, MSDS sheet is a poem.", price: 0.18, currency: "XMR", limited: true },
{ name: "Round-bottom flask stress orb", description: "Squishy flask — lab safety mascot.", price: 10, currency: "USD" },
{ name: "Periodic table shower curtain", description: "Actually useful chemistry dorm decor.", price: 29, currency: "USD" },
{ name: "Nitrogen dewar shot glass (steel)", description: "Steel mini cup shaped like dewar — LN₂ jokes, not included.", price: 0.011, currency: "ETH" },
{ name: "Fume hood sash height sticker pack", description: "Vinyl stickers reminding sash position — lab tours.", price: 7, currency: "USD" },
{ name: "GHS pictogram cookie cutters", description: "Bake hazard-symbol cookies — safety comms.", price: 16, currency: "USD" },
{ name: "Precursor-themed spice labels (joke)", description: "Labels for cumin jar saying not a precursor — legal humor.", price: 0.004, currency: "BTC" },
{ name: "Affinity chromatography plush column", description: "Stuffed toy shaped like a column — biochem softness.", price: 37, currency: "USD" },
{ name: "CRC Handbook coaster set", description: "Coasters printed with fake constants — trivia nights.", price: 0.019, currency: "XMR" },
{ name: "Ampoule snap practice kit (sugar)", description: "Sugar ampoules for snapping motion drills — no glass cuts.", price: 23, currency: "USD" },
{ name: "Titration mood light", description: "Lamp that shifts color like pH — aesthetics of indicators.", price: 44, currency: "USD" },
{ name: "Beaker creature plush", description: "Monster made of stacked beakers — PPE still required.", price: 0.013, currency: "ETH" },
{ name: "Lab notebook with waterproof lies", description: "Notebook advertising indestructible claims — epistemology gag.", price: 18, currency: "USD" },
]),
...block("Confections", [
{ name: "Blue Raspberry Bubblegum brick", description: "Bulk gum for candy-market grid — sticker claims encrypted flavor.", price: 0.002, currency: "BTC" },
{ name: "Sour Watermelon wedge bag", description: "Sour belts in Mylar — discuss packaging OPSEC as metaphor.", price: 0.0015, currency: "BTC" },
{ name: "Mystery Mix Cryptobag", description: "Assorted odds; QR on label resolves to nutrition facts PDF.", price: 0.005, currency: "BTC", limited: true },
{ name: "Glow-in-the-Dark Taffy rope", description: "UV-reactive taffy for blacklight display case only.", price: 0.003, currency: "ETH" },
{ name: "Tor onion gummy mold", description: "Silicone mold shaped like Tor logo onions — make gummies legally.", price: 14, currency: "USD" },
{ name: "Monero-mint chocolate bar", description: "Dark-mint bar in foil printed with ring-sig artwork — ships cold-packed.", price: 0.007, currency: "XMR" },
{ name: "Hash-brownie mix (literal potato)", description: "Potato hash brown mix — pun SKU for blockchain week.", price: 8, currency: "USD" },
{ name: "PGP-signed lollipop", description: "Lollipop with wrapper showing fake ASCII armor — unwrap carefully.", price: 0.004, currency: "BTC" },
{ name: "Dead-drop jawbreaker", description: "Layers of color like nested containers — tradecraft candy metaphor.", price: 6, currency: "USD" },
{ name: "Honey-pot honey jar", description: "Real jar of honey labeled honeypot — Infosec picnic.", price: 17, currency: "USD" },
{ name: "Zero-knowledge nougat", description: "Nougat bar claiming you taste it but cant prove it — ZKP joke.", price: 0.006, currency: "ETH" },
{ name: "Bitcoin-orange rock candy geode", description: "Rock candy geode dyed BTC orange — geology + crypto pun.", price: 11, currency: "USD" },
{ name: "Cold-storage ice pop molds", description: "Popsicle molds shaped like hardware wallets — summer syllabus.", price: 13, currency: "USD" },
{ name: "Mixer-themed cotton candy floss sugar", description: "Pastel sugars labeled hop1/hop2/hop3 — sweet talk about mixers.", price: 20, currency: "USD" },
]),
...block("Hardware", [
{ name: "Solder smoke extractor plush fan", description: "Toy fan with googly eyes — fume awareness for makers.", price: 36, currency: "USD" },
{ name: "JTAG duck debugger", description: "Rubber duck with labeled test points — hardware RE humor.", price: 0.017, currency: "BTC" },
{ name: "TPM tamper-evident sticker sheet", description: "Destructive void labels for chassis sealing — audits love these.", price: 12, currency: "USD" },
{ name: "Flipper Zero cosplay foam", description: "Foam replica — discuss responsible RF rules.", price: 0.01, currency: "ETH" },
{ name: "Airgap Ethernet cable (sewn shut)", description: "Cable sewn closed — literal air gap joke.", price: 9, currency: "USD" },
{ name: "SD-card sorting tray", description: "3D-print tray for forensic imaging class prop sorting.", price: 25, currency: "USD" },
{ name: "Logic analyzer hair clip (prop)", description: "Hair clip shaped like LA probes — signal integrity vibes.", price: 7, currency: "USD" },
{ name: "Raspberry Pi oven mitt", description: "Oven mitt with GPIO pinout print — kitchen cluster jokes.", price: 18, currency: "USD" },
{ name: "KVM switch fidget cube", description: "Cube with tiny clicky KVM toggles — focus toy for admins.", price: 0.014, currency: "XMR" },
{ name: "PDU power-strip storybook", description: "Kids book about not overloading circuits — datacenter bedtime.", price: 15, currency: "USD" },
]),
...block("Digital goods", [
{ name: "Lifetime license to our thoughts (PDF)", description: "Blank PDF with splash page — EULA literacy.", price: 0.001, currency: "ETH" },
{ name: "NFT of this paragraph (screenshot)", description: "PNG screenshot — discuss provenance vs possession.", price: 0.008, currency: "BTC" },
{ name: "Vaporware roadmap deck template", description: "Keynote template full of vague quarters.", price: 5, currency: "USD" },
{ name: "SaaS subscription to nothing", description: "Stripe-looking receipt for $0/mo — dark patterns talk.", price: 0, currency: "USD" },
{ name: "Cracked screen wallpaper pack", description: "PNG wallpapers — social engineering bait awareness.", price: 3, currency: "USD" },
{ name: "ISO27001 compliance cat video", description: "MP4 of cat knocking audit binder off desk.", price: 0.003, currency: "XMR" },
{ name: "Phishing email mad-libs HTML", description: "Template with blanks for grammar of scams.", price: 0.006, currency: "BTC" },
{ name: "API rate-limit lullaby MP3", description: "Lo-fi beats for waiting on 429 responses.", price: 7, currency: "USD" },
{ name: "Kubernetes yaml horoscope", description: "Text files mapping star signs to misconfig jokes.", price: 0.004, currency: "ETH" },
{ name: "Dark-mode-only whitepaper (empty)", description: "White PDF named ironically — accessibility tangent.", price: 2, currency: "USD" },
]),
...block("Services", [
{ name: "Remote exorcism of legacy PHP", description: "Zoom session where we shame your `mysql_*` calls (roleplay).", price: 0.09, currency: "BTC" },
{ name: "Tarot for TLS certificate expiry", description: "Performative reading of cert timelines — automate after.", price: 45, currency: "USD" },
{ name: "On-site donut-driven threat modeling", description: "Bring donuts, draw stride threats on napkins.", price: 120, currency: "USD" },
{ name: "ASMR packet capture whisper stream", description: "1hr ambient `.pcap` readout — sleep for nerds.", price: 0.016, currency: "XMR" },
{ name: "Corporate values alignment séance", description: "Parody workshop aligning synergy with firewall rules.", price: 0.027, currency: "ETH" },
{ name: "Retrospective on your retrospective", description: "Facilitator block booking — billed in 0.25 agile-day increments.", price: 88, currency: "USD" },
{ name: "Perl poetry code review", description: "Critique of badly written haiku in Perl — nostalgia service.", price: 0.012, currency: "BTC" },
{ name: "Blockchain-of-custody bakery pickup", description: "We sign a paper chain while you pick up muffins.", price: 35, currency: "USD" },
]),
];
function dedupeById(products: ProductDraft[]): ProductDraft[] {
const seen = new Set<string>();
const out: ProductDraft[] = [];
for (const p of products) {
if (seen.has(p.id)) continue;
seen.add(p.id);
out.push(p);
}
return out;
}
export const SHOP_PRODUCTS: ShopProduct[] = dedupeById(ALL_BLOCKS).map((p) => ({
...p,
sellerId: sellerIdForProduct(p.id),
}));
export function shopProductsForSeller(sellerId: string): ShopProduct[] {
return SHOP_PRODUCTS.filter((p) => p.sellerId === sellerId);
}
export const SHOP_PRODUCT_COUNT = SHOP_PRODUCTS.length;
export function shopCategoryCounts(): { name: string; count: number; icon: string }[] {
const icons: Record<string, string> = {
"BioEnhancements": "🧬",
Financial: "💰",
Identity: "🎭",
Security: "🛡️",
"Data Leaks": "💾",
Weapons: "🔫",
Forgery: "🖨️",
Chemicals: "🧪",
Confections: "🍬",
Hardware: "🔧",
"Digital goods": "📀",
Services: "🛎️",
};
const m = new Map<string, number>();
for (const p of SHOP_PRODUCTS) {
m.set(p.category, (m.get(p.category) ?? 0) + 1);
}
return [...m.entries()]
.map(([name, count]) => ({ name, count, icon: icons[name] ?? "⬡" }))
.sort((a, b) => a.name.localeCompare(b.name));
}
export function featuredShopProducts(n = 8): ShopProduct[] {
return [...SHOP_PRODUCTS].sort((a, b) => (b.limited ? 1 : 0) - (a.limited ? 1 : 0) || hashId(a.id) - hashId(b.id)).slice(0, n);
}

292
lib/shopChatterThreads.ts Normal file
View File

@@ -0,0 +1,292 @@
/**
* Read-only threads: in-world discussion about the CyberLux main storefront / hub.
* Deterministic timestamps for stable SSR. Not imported by types from forumState (no cycles).
*/
const ANCHOR_MS = 1_738_368_000_000; // fixed epoch for reproducible ordering
const RINGS = ["markets", "reputation", "lounge", "opsec", "tech", "crypto"] as const;
const AUTHORS = [
"void_walker",
"ledger_rat",
"PGP_moth",
"EU_shift",
"tab_hoarder",
"saltfade",
"watchtower_09",
"quietBag",
"mirror_drifter",
"canary_flip",
"neon_sick",
"GhostLayer",
"patch_only",
"tor_baby",
"xmr_only",
"checkout_sigh",
"grid_griefer",
"VendorPM_me",
"dead_drop_9",
"OPSEC_dad",
"liquidity_lurker",
"v3_only",
"signed_or_bust",
"tails_8gb",
"slow_ship_ok",
"finalize_never",
"FUD_archivist",
"UI_snob",
"wallet_scratch",
"btc_fee_woe",
"monero_monk",
"proof_of_receive",
"browser_leak",
"hub_regular",
"clone_scare",
"support_ticket",
"cart_abandon",
"wishlist_only",
"shipping_zone_B",
"late_mod_reply",
] as const;
const TITLE_STEMS = [
"CyberLux hub — who else lives on the neon grid?",
"Main storefront load times tonight?",
"That checkout flow is *smooth* compared to last year",
"Mirror check: does your hub onion match the signed block?",
"Product cards: gimmick or actually readable on Tails?",
"Wallet credit top-up — anyone else watching mempool?",
"Is the hubs luxury theme hurting OPSEC or just funny?",
"Vendor queue feels shorter this month (hub)",
"Final thought: never finalize early on the hub",
"Main link escrow wording — did they tighten the copy?",
"CyberLux cart: how many tabs before your RAM cries?",
"Off-topic but the hub particle bg is chefs kiss",
"Seriously though, verify the hub PGP every session",
"Which syndicate relay do you hit before the hub?",
"Hub search vs endless scroll — preferences?",
"Checkout address confirmation — triple-check bytes",
"Anyone benchmark hub on low-RAM tails?",
"The hub footer links — which ones are Easter eggs?",
"Marketplace comparison threads always dunk on UI — not CyberLux tho",
"I showed the hub to a friend — they thought it was a game",
"Main store vs forum onion — same wallet session is convenient",
"Scam clones watch: typography on fakes is always off",
"Hub testimonials page reads like SEO — still addictive",
"Support page multisig note — matches my saved address",
"Raffle page on hub: gimmick or real engagement?",
"Trust score sites mention CyberLux — grain of salt",
"Reviews blog is over the top but fun to skim",
"Encrypted comms page — lore or pipeline tease?",
"I only use the hub for window shopping (I know, I know)",
"Neural stimulant listing copy is wild — pure cyberpunk mall",
"Ghost identity pack — fictional but the warning text is solid",
"Router listing reads like a tech blog ad",
"Zero-day kit blurb — classroom fiction energy",
"Mixer page linked from hub — anyone use it?",
"Comparison chart puts CyberLux first — surprise surprise",
"Testimonials archived somewhere off-hub?",
"Security analysis PDF fantasy hits different",
"Vault page terminal aesthetic >>> hub neon (fight me)",
"I want a boring mode for the hub (accessibility)",
"Canary string rotation — who watches?",
"Hub down? check syndicate relays first",
"Exchange onion vs hub — classifieds vibe is quieter",
"Hidden wiki from hub ring — good onboarding",
"Void Aggregate mentions CyberLux in passing — accurate",
"Anyone else refreshes hub just for the animation timing",
"BTC verify endpoint — educational or operational?",
"LocalStorage wallet — fine for demo, not for real funds",
"Professor said analyze the hub like a phishing trainer",
"If this were real, Id still verify PGP twice",
"Hub feels expensive — thats the point of the skin",
"Night mode when?",
"The hub is a meme but the mirror discipline is real",
"Stop posting main link screenshots without redacting",
"Opsec: separate browser profile for hub vs clearnet",
"Mobile tor: hub usable or nah?",
"I teach friends to read Terms-looking footers on fake DN sites",
"CyberLux naming is on the nose — students will get it",
"Main shop categories — which would you rename?",
"Presswire page sounds like Breitbart for hackers",
"Awards page is camp — I love it",
"Tree section on hub is unhinged in the good way",
"Sanctuary link dead or hidden?",
"Drops page urgency tricks — discuss",
"Chat widget copy — encrypted but obviously local",
"Account creation wizard is theatrical",
"Encryption demo reverses string — cute",
"I still click every webring link like its 2003",
"Red room page is obviously fiction — good restraint",
"Inner circle gatekeeping — does it add immersion?",
"Game page — anyone beat the high score rumor?",
"Arb academy sounds like a pyramid scheme parody",
"Wallets page — hardware flex thread when",
"Messages read-only — intentional frustration?",
"Mixer lore matches old SR nostalgia threads",
"Conspiracies board is where hub leaks truth lol",
"Raffle legal text — read before you meme",
"Easter eggs hunt — collaboration thread",
"Secret layer password rumors — no spoilers",
"Profit sim — classroom economics tie-in?",
"Market page vs hub home — redundant or layered?",
"Search page index hits vendor stalls now — still better than dead 404",
"Trees countdown — someone explain the bit",
] as const;
const BODY_STEMS = [
"Ive been poking at the main storefront on the hub onion. The grid is heavy but it settles after a second — curious if that matches what others see.",
"Not stanning anything — just noting the UX reads more premium theme park than pastebin. Useful if youre training people what polished phishing aspires to.",
"Mirror list cross-check: gpg says OK. If yours fails, assume clone and restart from a source you trust.",
"Checkout flow is the part I show new folks: watch how it asks for confirmation. Compare to random Telegram bots.",
"Anyone else splitting sessions — one tails stick for browsing the hub demo, another for real work?",
"The product blurbs are obviously fiction for class, but the *shape* of them (claims, urgency, trust cues) is what I annotate.",
"If the hub ever rotated mirrors, wed see panic threads here first. Good exercise for students to watch wording.",
"I like that the forum onion and hub share wallet state in the sim — makes the one operator many doors lesson stick.",
"Tiny gripe: I wish the hub had a plaintext this is a teaching skin smaller — but I get why instructors want immersion.",
"Posting from the ring board because the lounge is noisy. Still about the storefront though.",
"Syndicate skins are a fun way to show same backend, different faces — main shop is just the brightest face.",
"Mobile Tor users chime in: scrolling the hub is… a workout. Still readable.",
"Im cataloging every trust microcopy on the main page for a slides deck. CyberLux is a goldmine of examples.",
"Dont screenshot your balance for clout — blur your ops.",
"The neon palette is memorable. Thats branding students remember — good and bad.",
"Is it weird I want a boring.gov alternate CSS for accessibility demos?",
"Hub → hidden wiki handoff feels like old hiddenwiki sidebars. Nostalgia hit.",
"Exchange classifieds + hub checkout = nice story about surface area. More doors, more phishing templates.",
"If youre teaching verification, use the hub as what a slick lie can look like — then compare to Tor Project docs.",
"Im only here for the chatter about layout shifts after deploy. Anyone notice?",
"Foot Traffic on the sim is zero — but the fiction of steady checkout is part of the exercise.",
"Please dont paste real addresses or seeds. Keep it classroom-clean.",
"I keep the hub open in a window while grading — reminds me what good dark-pattern homework looks like.",
"The raffle copy tries urgency. Worth dissecting sentence by sentence.",
"Vault terminal theme is my favorite detour from the storefront. Still same deployment.",
"Someone said the hub feels expensive — thats aesthetic manipulation 101.",
"Comparison page is obviously tilted; good lesson on review blogs with incentives.",
"Reviews thread exaggerates crypto jargon — perfect red-flag practice for non-experts.",
"Trust page metrics look scientific — ask students whats *not* measured.",
"Security analysis page claims pentest — discuss epistemic humility.",
"Testimonials read like fanfic — label the persuasion techniques.",
"Presswire satire hits if youve read real leak blogs.",
"I want a thread compiling every internal link from hub nav — completeness check.",
"DROP section timing: discuss scarcity cues without moralizing.",
"Chat widget promises ephemeral — compare to real protocol guarantees.",
"Encryption demo is toy crypto — highlight why toy ≠ real.",
"Webring is a cute anachronism — main shop still the anchor property.",
"If your students think its real, your debrief worked. If they think its fake immediately, ask why the cues failed.",
"Main shop hero headline is doing a lot of work in one sentence.",
"CyberLux wording overload is intentional — noise as design.",
"Someone should map hub routes to learning objectives. I started a doc.",
"Im impressed how cohesive the fiction is across forum + wiki + exchange.",
"Please cross-link good mirror hygiene posts from r/opsec when hub threads get sloppy.",
"BTC verify copy: good place to talk oracle trust & explorers.",
"Local-only wallet in the sim — emphasize browser storage isnt a bank.",
"The hub isnt claiming illegal service — its claiming *vibes*. Important distinction for legal review.",
"Night-mode wishlist: lower contrast path for photocopy-gray realism.",
"Ive run this on projector — neon pops, students squint, lesson lands.",
"If clones existed, theyd fix typos last — tell students to grep for weird punctuation on signed posts.",
"Main grid cards use glow to guide eye — track click heatmaps in class discussion hypothetically.",
"Footer escrow mandatory line is doing legal-ish theater — compare to real market rules of use.",
"I want more threads about *why* the hub uses luxury signaling — status and underground economies 101.",
"Exchange onion quieter — hub is the loud neighbor. Both instructive.",
"Hidden wiki placeholder onions are obviously placeholders — good seed for dont trust paste.",
"Void Aggregate rename fits — aggregates rumors about venues including this one.",
"Syndicate slugs are a lesson in namespace squatting aesthetics.",
"Dont ship ethics complaints to me — Im just archiving chatter.",
"Posting so search indexes this ring as people talk about the hub — meta, sorry.",
"If your jam is OPSEC, skim hub marketing and count identity promises. Tally the lies of omission.",
"Students asked if CyberLux is satire — I said pedagogical dress-up.",
"Main shop particle layer: GPU tax — discuss performance as anonymity tradeoff indirectly.",
"I keep a slide: spot the difference hub vs phishing kit screenshots from APWG.",
"Love how the sim keeps BTC flow imaginary — focus stays on trust UX.",
"Rumor: new seasonal skin drops on hub — probably false, sounds cool.",
"Thread purpose: stocking the search results for CyberLux chatter assignments.",
"Carrying discussion from class — hubs trust badges are decorative; list what would make them meaningful.",
"If you mirror this demo for your uni, sync your debrief slide — consistency helps.",
"Im logging anachronisms between hub pages for a continuity errors game.",
"Someone write a glossary of neon color hex codes on the hub — nerd snipe.",
"Main link bookmark hygiene: rotate folders, dont trust autocomplete.",
"Okay Ill stop — but seriously the storefront is the course protagonist.",
] as const;
function replyBundle(i: number): { id: string; author: string; body: string; ts: number }[] {
if (i % 4 === 0) return [];
const a = AUTHORS[(i + 7) % AUTHORS.length]!;
const b = AUTHORS[(i + 19) % AUTHORS.length]!;
const ts0 = ANCHOR_MS - i * 7_200_000 - 400_000;
const ts1 = ts0 + 180_000;
const bodies = [
"Same experience on my end — grid settles fast after first paint.",
"Hub is loud but the mirror discipline threads are louder. Good.",
"Showed the checkout confirmation step in lab — students nodded.",
"If gpg fails, I walk — no exceptions.",
"Agree its a sim — still useful surface for critique.",
"The neon is doing affect work — not my taste but coherent.",
"Cross-posting to r/opsec: verify signed blocks before you meme.",
"Im here for the typography roasts. Hit me.",
];
const one = {
id: `chatter-${i}-r0`,
author: a,
body: bodies[i % bodies.length]!,
ts: ts0,
};
if (i % 4 === 1) return [one];
return [
one,
{
id: `chatter-${i}-r1`,
author: b,
body: bodies[(i + 3) % bodies.length]!,
ts: ts1,
},
];
}
function buildThreads() {
const out: Array<{
id: string;
topicSlug: string;
title: string;
author: string;
body: string;
ts: number;
baseScore: number;
replies: Array<{ id: string; author: string; body: string; ts: number }>;
}> = [];
const total = 140;
for (let i = 0; i < total; i++) {
const ring = RINGS[i % RINGS.length]!;
const title = TITLE_STEMS[i % TITLE_STEMS.length]! + (i >= TITLE_STEMS.length ? ` (#${i})` : "");
const body =
BODY_STEMS[i % BODY_STEMS.length]! +
(i >= BODY_STEMS.length ? `\n\n— thread index ${i}, ring /r/${ring}` : "");
out.push({
id: `chatter-${i}`,
topicSlug: ring,
title,
author: AUTHORS[i % AUTHORS.length]!,
body,
ts: ANCHOR_MS - i * 7_200_000 - (i % 9) * 450_000,
baseScore: 24 + ((i * 97) % 5_800),
replies: replyBundle(i),
});
}
return out;
}
export const SHOP_CHATTER_THREADS = buildThreads();
export const SHOP_CHATTER_COUNT = SHOP_CHATTER_THREADS.length;
export function getChatterSlice(page: number, pageSize: number) {
const p = Math.max(1, page);
const start = (p - 1) * pageSize;
return {
items: SHOP_CHATTER_THREADS.slice(start, start + pageSize),
page: p,
totalPages: Math.max(1, Math.ceil(SHOP_CHATTER_THREADS.length / pageSize)),
total: SHOP_CHATTER_THREADS.length,
};
}

26
lib/shopFx.ts Normal file
View File

@@ -0,0 +1,26 @@
import type { ShopCurrency } from "@/lib/shopCatalog";
/**
* Rough USD notional for cart/checkout when only BTC spot is loaded from API.
* ETH/XMR are pegged off BTC for display totals (sim storefront).
*/
export function estimateUsdForCryptoAmount(
amount: number,
currency: ShopCurrency,
btcUsd: number,
): number {
if (currency === "USD") return Math.round(amount * 100) / 100;
if (!(btcUsd > 0)) return 0;
const ethUsd = btcUsd / 28;
const xmrUsd = btcUsd / 520;
switch (currency) {
case "BTC":
return Math.round(amount * btcUsd * 100) / 100;
case "ETH":
return Math.round(amount * ethUsd * 100) / 100;
case "XMR":
return Math.round(amount * xmrUsd * 100) / 100;
default:
return 0;
}
}

115
lib/shopProductSocial.ts Normal file
View File

@@ -0,0 +1,115 @@
/**
* Deterministic “live” metrics per product id — stable across refresh/build.
*/
export type ProductTestimonial = {
handle: string;
text: string;
rating: number;
daysAgo: number;
};
export type ProductSocialMetrics = {
avgRating: number;
reviewCount: number;
purchaseCount: number;
/** Decorative hook — looks like marketplace activity */
boughtLast24h: number;
testimonials: ProductTestimonial[];
};
function hash32(s: string): number {
let h = 2166136261;
for (let i = 0; i < s.length; i++) {
h ^= s.charCodeAt(i);
h = Math.imul(h, 16777619);
}
return h >>> 0;
}
function pick<T>(arr: T[], seed: number): T {
return arr[seed % arr.length]!;
}
const HANDLE_SUFFIXES = [
"_void",
"Buyer",
"anon_op",
"ledger_rat",
"EU_mix",
"PacificMesh",
"tor_only",
"no_logs_99",
"ghost_cart",
"relay_7",
];
const SNIPPETS: { text: string; r: number }[] = [
{ text: "Sealed exactly as pictured. Seller answered in under an hour.", r: 5 },
{ text: "Third order from this stall — consistent every time.", r: 5 },
{ text: "A bit slow on weekend but tracking matched mirror policy.", r: 4 },
{ text: "PGP match checked out. Product matches batch notes.", r: 5 },
{ text: "Pricey vs clearnet dupes but opsec packaging is on point.", r: 4 },
{ text: "Had a dispute; escrow released after proof — fair outcome.", r: 4 },
{ text: "Arrived faster than quoted. Will re-up next drop.", r: 5 },
{ text: "Not for everyone but description was honest — no BS.", r: 4 },
{ text: "Comms terse but professional. Goods matched listing.", r: 5 },
{ text: "Verified canary before order — same fingerprint as hub post.", r: 5 },
{ text: "Mixed feelings on shipping lane but item intact.", r: 4 },
{ text: "Solid vendor thread history. SKU lived up to hype.", r: 5 },
];
export function getProductSocialMetrics(productId: string): ProductSocialMetrics {
const h = hash32(productId);
const h2 = hash32(`${productId}:reviews`);
const h3 = hash32(`${productId}:buyers`);
const avgRating = Math.round((4.05 + (h % 90) / 100) * 100) / 100;
const reviewCount = 24 + (h2 % 1_247);
const purchaseCount = 180 + (h3 % 24_500);
const boughtLast24h = 8 + (h2 % 214);
let i1 = h % SNIPPETS.length;
let i2 = (h2 + 5) % SNIPPETS.length;
if (i2 === i1) i2 = (i2 + 1) % SNIPPETS.length;
let i3 = (h3 + 11) % SNIPPETS.length;
if (i3 === i1 || i3 === i2) i3 = (i3 + 1) % SNIPPETS.length;
if (i3 === i1) i3 = (i3 + 1) % SNIPPETS.length;
const t1 = SNIPPETS[i1]!;
const t2 = SNIPPETS[i2]!;
const t3 = SNIPPETS[i3]!;
const mkHandle = (seed: number) => {
const base = pick(["x", "q", "v", "k", "m", "s", "z", "r"], seed);
return `${base}${(seed % 900) + 100}${pick(HANDLE_SUFFIXES, seed + 11)}`;
};
const testimonials: ProductTestimonial[] = [
{
handle: mkHandle(h),
text: t1.text,
rating: t1.r,
daysAgo: 1 + (h % 6),
},
{
handle: mkHandle(h2),
text: t2.text,
rating: t2.r,
daysAgo: 7 + (h2 % 20),
},
{
handle: mkHandle(h3),
text: t3.text,
rating: t3.r,
daysAgo: 14 + (h3 % 45),
},
];
return {
avgRating,
reviewCount,
purchaseCount,
boughtLast24h,
testimonials,
};
}

85
lib/shopSellers.ts Normal file
View File

@@ -0,0 +1,85 @@
/**
* Fictional marketplace vendors — every shop SKU resolves to one via sellerId + hash fallback.
* Thirty-six+ sellers for a busy vendor hall (synthetic marketplace data).
*/
export type ShopSeller = {
id: string;
/** URL segment: /vendor/{slug} */
slug: string;
/** Public handle */
handle: string;
/** Onetwo sentence pitch */
bio: string;
/** Flair line under the name */
tagline: string;
since: number;
/** Theater stats */
completedSales: number;
rating: number;
/** Busy indicator: “online” lore */
responseMins: number;
specialty: string;
};
export const SHOP_SELLERS: ShopSeller[] = [
{ id: "v1", slug: "sugarrush-vault", handle: "SugarRush_Vault", bio: "Started on bubblegum arbitrage; now runs a neon-lit confection aisle with same-day dead-drop fiction.", tagline: "Sweet logistics, sour opsec jokes", since: 2019, completedSales: 4280, rating: 4.92, responseMins: 14, specialty: "Confections" },
{ id: "v2", slug: "candyman-99", handle: "CandyMan_99", bio: "Greybeard vendor who still hand-writes batch numbers on Mylar; refuses clearnet DMs.", tagline: "Old school drops, new school memes", since: 2016, completedSales: 8912, rating: 4.78, responseMins: 42, specialty: "Confections" },
{ id: "v3", slug: "entropy-corp", handle: "EntropyCorp", bio: "Hardware RNG evangelist selling props that teach students what random actually costs.", tagline: "Noise you can weigh", since: 2020, completedSales: 2104, rating: 4.85, responseMins: 55, specialty: "Hardware" },
{ id: "v4", slug: "ghost-layer-trades", handle: "GhostLayerTrades", bio: "Identity-pack cosplayer; every listing reads like a cautionary fable for journalism labs.", tagline: "Proof-of-personhood theater", since: 2018, completedSales: 1567, rating: 4.88, responseMins: 33, specialty: "Identity" },
{ id: "v5", slug: "neon-apotheke", handle: "NeonApotheke", bio: "Labels everything for rhetoric class only; partners with instructors for debrief worksheets.", tagline: "Inert props, loud disclaimers", since: 2021, completedSales: 3890, rating: 4.71, responseMins: 22, specialty: "Chemicals" },
{ id: "v6", slug: "opsec-omakase", handle: "OpsecOmakase", bio: "Bundles tor-themed desk toys with checklist PDFs; spammed mirror links so you learn verification.", tagline: "Compartmentalized clutter", since: 2017, completedSales: 5022, rating: 4.95, responseMins: 19, specialty: "Security" },
{ id: "v7", slug: "mixer-memory-lane", handle: "MixerMemoryLane", bio: "Sells nostalgia coasters and long threads about tumbler UIs — pedagogy with glitter.", tagline: "Hop jokes, real risk talk", since: 2019, completedSales: 1766, rating: 4.63, responseMins: 67, specialty: "Financial" },
{ id: "v8", slug: "ledger-moth", handle: "LedgerMoth", bio: "Attracted to cold-storage keychains and warm takes on multisig ceremony.", tagline: "Loves ledgers, hates ledger drama", since: 2015, completedSales: 12033, rating: 4.9, responseMins: 28, specialty: "Financial" },
{ id: "v9", slug: "cipherkids-collective", handle: "CipherKidsCollective", bio: "Student co-op listing meme PGP accessories; proceeds fund club pizza (fiction).", tagline: "ASCII armor, Unicode heart", since: 2022, completedSales: 942, rating: 4.55, responseMins: 120, specialty: "Digital goods" },
{ id: "v10", slug: "zero-k-knits", handle: "ZeroKKnits", bio: "Hand-knits proof scarves — wearable ZKP jokes for compsci fashion crimes.", tagline: "Stitch, dont snitch", since: 2020, completedSales: 884, rating: 4.82, responseMins: 48, specialty: "Services" },
{ id: "v11", slug: "patchbay-seven", handle: "Patchbay7", bio: "Audio engineer turned barter poet; swaps studio time for syllabi about copyright friction.", tagline: "TRS snakes & trust anchors", since: 2016, completedSales: 2633, rating: 4.77, responseMins: 36, specialty: "Hardware" },
{ id: "v12", slug: "eu-shift-goods", handle: "EU_shiftGoods", bio: "Time-zone hopping vendor shipping sanitized scandal props for EU privacy curricula.", tagline: "GDPR-themed desk toys", since: 2018, completedSales: 3311, rating: 4.69, responseMins: 51, specialty: "Data Leaks" },
{ id: "v13", slug: "void-cartographer", handle: "void_cartographer", bio: "Maps imaginary nodes onto real cork boards; sells pins, string, and paranoia.", tagline: "Latitude: unknown", since: 2014, completedSales: 9011, rating: 4.93, responseMins: 24, specialty: "Identity" },
{ id: "v14", slug: "saltside-forge", handle: "SaltSideForge", bio: "Printmaker obsessed with microprint pedagogy — everything demilled, everything didactic.", tagline: "Chalcography, not counterfeits", since: 2017, completedSales: 1444, rating: 4.84, responseMins: 73, specialty: "Forgery" },
{ id: "v15", slug: "quiet-bag-trading", handle: "quietBagTrading", bio: "Minimal listings, maximal packing peanuts; teaches less metadata, more mischief.", tagline: "Plain boxes, loud lessons", since: 2021, completedSales: 2109, rating: 4.66, responseMins: 91, specialty: "Confections" },
{ id: "v16", slug: "watchtower-null", handle: "watchtower_09", bio: "Former helpdesk griefer; now sells IR playbooks printed on fireproof-ish paper.", tagline: "Pager-duty poetry", since: 2019, completedSales: 4566, rating: 4.8, responseMins: 31, specialty: "Security" },
{ id: "v17", slug: "neon-sick-labs", handle: "neon_sick_labs", bio: "Biohacker aesthetic without bio samples — gel pours, chalk presses, ethics handouts.", tagline: "Wetware cosplay, dry enforcement", since: 2020, completedSales: 2988, rating: 4.72, responseMins: 44, specialty: "BioEnhancements" },
{ id: "v18", slug: "tor-baby-bazaar", handle: "tor_baby", bio: "First-time vendor energy; over-explains PGP in every package insert.", tagline: "Signed with love & line breaks", since: 2023, completedSales: 511, rating: 4.58, responseMins: 15, specialty: "Digital goods" },
{ id: "v19", slug: "checkout-sigh", handle: "checkout_sigh", bio: "Specializes in cart-abandonment empathy merch — stress balls shaped like loading spinners.", tagline: "One-click zen", since: 2018, completedSales: 6722, rating: 4.74, responseMins: 38, specialty: "Services" },
{ id: "v20", slug: "grid-griefer-wares", handle: "grid_griefer", bio: "Sells fidget friendly exploit desk toys; banned three times, re-listed with better disclaimers.", tagline: "Lag is a lifestyle", since: 2016, completedSales: 8877, rating: 4.61, responseMins: 60, specialty: "Security" },
{ id: "v21", slug: "vendor-pm-me", handle: "VendorPM_me", bio: "Satire vendor whose entire FAQ is about not sliding into DMs without signed clearsigned intro.", tagline: "Inbox closed, mailbox mythic", since: 2017, completedSales: 1922, rating: 4.87, responseMins: 84, specialty: "Identity" },
{ id: "v22", slug: "dead-drop-nine", handle: "dead_drop_9", bio: "Geocache enthusiast selling magnetic tins labeled training dead drops only.", tagline: "Latitude redacted responsibly", since: 2015, completedSales: 5544, rating: 4.9, responseMins: 27, specialty: "Hardware" },
{ id: "v23", slug: "opsec-dad-outfitters", handle: "OPSEC_dad", bio: "Dad jokes × threat modeling; bumper stickers about passphrase rotation.", tagline: "Because I said verify", since: 2014, completedSales: 14002, rating: 4.96, responseMins: 11, specialty: "Security" },
{ id: "v24", slug: "liquidity-lurker", handle: "liquidity_lurker", bio: "Market-maker cosplay: sells LED tickers that only scroll educational disclaimers.", tagline: "Spreads tight, stories wider", since: 2019, completedSales: 3777, rating: 4.68, responseMins: 47, specialty: "Financial" },
{ id: "v25", slug: "v3-only-club", handle: "v3_only_club", bio: "Purist about onion v3 links; mousepads printed with address-length rulers.", tagline: "56 chars or bust", since: 2020, completedSales: 2233, rating: 4.83, responseMins: 29, specialty: "Digital goods" },
{ id: "v26", slug: "signed-or-bust", handle: "signed_or_bust", bio: "Only ships if your key cross-signs their joke manifesto — instructors get a bypass code.", tagline: ".sig or swim", since: 2018, completedSales: 6110, rating: 4.94, responseMins: 18, specialty: "Forgery" },
{ id: "v27", slug: "tails-eight-gb", handle: "tails_8gb", bio: "USB cases with tiny ventilation jokes; bundles Tails release posters (fan art).", tagline: "Live OS, die laughing", since: 2016, completedSales: 9881, rating: 4.76, responseMins: 35, specialty: "Hardware" },
{ id: "v28", slug: "finalize-never-co", handle: "finalize_never_co", bio: "Escrow-education vendor: enamel pins that say multisig first in tiny letters.", tagline: "Early is wrong here", since: 2021, completedSales: 1888, rating: 4.7, responseMins: 52, specialty: "Financial" },
{ id: "v29", slug: "fud-archivist", handle: "FUD_archivist", bio: "Archives sensational headlines as coaster sets — history of panic syllabus tie-in.", tagline: "Primary sources of drama", since: 2017, completedSales: 3055, rating: 4.64, responseMins: 63, specialty: "Data Leaks" },
{ id: "v30", slug: "ui-snob-studio", handle: "UI_snob_studio", bio: "Typography snob selling holographic UI roasts; heatmaps reproduced on mousepads.", tagline: "Pixels with prejudice", since: 2022, completedSales: 1201, rating: 4.59, responseMins: 88, specialty: "Services" },
{ id: "v31", slug: "wallet-scratch-pad", handle: "wallet_scratch", bio: "Paper notebooks for seed-phrase *practice* with nonsense words only.", tagline: "Writes friction, not seeds", since: 2019, completedSales: 4404, rating: 4.81, responseMins: 26, specialty: "Financial" },
{ id: "v32", slug: "btc-fee-woe", handle: "btc_fee_woe", bio: "Therapist-chic stickers about mempool grief; bundles feerate graphs as wrapping paper.", tagline: "Sat/vB & sob stories", since: 2020, completedSales: 2677, rating: 4.73, responseMins: 41, specialty: "Financial" },
{ id: "v33", slug: "monero-monk", handle: "monero_monk", bio: "Ring-sig mystique vendor; every parcel includes a ring-sized diagram coaster.", tagline: "Privacy incense not included", since: 2018, completedSales: 5122, rating: 4.89, responseMins: 30, specialty: "Financial" },
{ id: "v34", slug: "proof-of-receive", handle: "proof_of_receive", bio: "Packaging fetishist obsessed with signed delivery receipts — sells wax-seal stickers.", tagline: "Chain of custody cosplay", since: 2015, completedSales: 7288, rating: 4.91, responseMins: 23, specialty: "Forgery" },
{ id: "v35", slug: "browser-leak-boutique", handle: "browser_leak_boutique", bio: "Fingerprint-themed soaps and WebRTC fear candles for blue-team spa nights.", tagline: "Scrub headers, not ethics", since: 2021, completedSales: 1644, rating: 4.67, responseMins: 57, specialty: "Security" },
{ id: "v36", slug: "hub-regular-supply", handle: "hub_regular_supply", bio: "Catch-all reseller mirroring whatever the hub hero section spotlights — chaotic neutral inventory.", tagline: "If it glows, I list it", since: 2013, completedSales: 22410, rating: 4.62, responseMins: 99, specialty: "BioEnhancements" },
];
const BY_SLUG = new Map(SHOP_SELLERS.map((s) => [s.slug, s]));
const BY_ID = new Map(SHOP_SELLERS.map((s) => [s.id, s]));
export const SHOP_SELLER_COUNT = SHOP_SELLERS.length;
export function getSellerBySlug(slug: string): ShopSeller | undefined {
return BY_SLUG.get(slug);
}
export function getSellerById(id: string): ShopSeller | undefined {
return BY_ID.get(id);
}
/** Deterministic seller per product — spreads listings across the bazaar */
export function sellerIdForProduct(productId: string): string {
let h = 0;
for (let i = 0; i < productId.length; i++) {
h = (h + productId.charCodeAt(i) * (i + 3)) % 2147483647;
}
const idx = Math.abs(h) % SHOP_SELLERS.length;
return SHOP_SELLERS[idx]!.id;
}

38
lib/storeCreditStorage.ts Normal file
View File

@@ -0,0 +1,38 @@
const USD_KEY = "cyberlux-usd-store-credit";
const TX_KEY = "cyberlux-btc-claimed-txids";
export function loadUsdStoreCredit(): number {
if (typeof window === "undefined") return 0;
try {
const raw = localStorage.getItem(USD_KEY);
if (!raw) return 0;
const n = Number.parseFloat(raw);
return Number.isFinite(n) ? Math.round(n * 100) / 100 : 0;
} catch {
return 0;
}
}
export function saveUsdStoreCredit(usd: number): void {
if (typeof window === "undefined") return;
const safe = Math.max(0, Math.round(usd * 100) / 100);
localStorage.setItem(USD_KEY, String(safe));
}
export function loadClaimedTxids(): string[] {
if (typeof window === "undefined") return [];
try {
const raw = localStorage.getItem(TX_KEY);
if (!raw) return [];
const parsed = JSON.parse(raw) as unknown;
if (!Array.isArray(parsed)) return [];
return parsed.map((x) => String(x)).filter((t) => /^[a-fA-F0-9]{64}$/.test(t));
} catch {
return [];
}
}
export function saveClaimedTxids(txids: string[]): void {
if (typeof window === "undefined") return;
localStorage.setItem(TX_KEY, JSON.stringify([...new Set(txids)]));
}

52
lib/syndicateNetwork.ts Normal file
View File

@@ -0,0 +1,52 @@
/** Themed relay slugs — each `/w/[slug]` applies a different chrome; routing stays in-app. */
export type SyndicateEntry = {
slug: string;
tagline: string;
/** Visual bucket for /w/[slug] chrome */
skin: number;
};
/** Relay catalogue — each `/w/[slug]` maps to a layout skin bucket. */
export const SYNDICATE_NODES: SyndicateEntry[] = [
{ slug: "voidmail", tagline: "Dead-letter drop UI prototype", skin: 0 },
{ slug: "glasshouse", tagline: "Mirror node (empty room)", skin: 1 },
{ slug: "rustbucket", tagline: "FTP cosplay terminal", skin: 2 },
{ slug: "neonvault", tagline: "Savings vaporware vault", skin: 3 },
{ slug: "papertrail", tagline: "Audit log aesthetic", skin: 4 },
{ slug: "ghostcart", tagline: "Abandoned cart shrine", skin: 5 },
{ slug: "cipherkids", tagline: "PGP meme archive", skin: 6 },
{ slug: "midnightbazaar", tagline: "Tile mosaic lobby", skin: 7 },
{ slug: "nullsector", tagline: "404 ritual chamber", skin: 0 },
{ slug: "echochamber", tagline: "Forum screenshot museum", skin: 1 },
{ slug: "tarpit", tagline: "Honeypot styleguide", skin: 2 },
{ slug: "liquidsky", tagline: "Gradient overdose", skin: 3 },
{ slug: "hardcopy", tagline: "Xerox noir", skin: 4 },
{ slug: "softlaunch", tagline: "Beta forever landing", skin: 5 },
{ slug: "wiremesh", tagline: "Brutalist wireframe", skin: 6 },
{ slug: "dustcover", tagline: "Old hardcover intro", skin: 7 },
{ slug: "parallelcart", tagline: "Alternate checkout skin", skin: 0 },
{ slug: "sidechannel", tagline: "Noise generator page", skin: 1 },
{ slug: "loworbit", tagline: "Satellite status board", skin: 2 },
{ slug: "deepfreeze", tagline: "Cold storage copy", skin: 3 },
{ slug: "burnerline", tagline: "Disposable number UI", skin: 4 },
{ slug: "opalgrid", tagline: "Art deco grids", skin: 5 },
{ slug: "inkwell", tagline: "Fountain pen terminal", skin: 6 },
{ slug: "cobaltroom", tagline: "Blue chamber index", skin: 7 },
{ slug: "sleeveNotes", tagline: "Album liner notes as site", skin: 0 },
{ slug: "rollCredits", tagline: "End-credits scroll", skin: 1 },
{ slug: "junkdrawer", tagline: "Misc links bucket", skin: 2 },
{ slug: "hourglass", tagline: "Timer demo", skin: 3 },
{ slug: "patchbay", tagline: "Audio patch console", skin: 4 },
{ slug: "snowfield", tagline: "Whitespace maximalism", skin: 5 },
{ slug: "redindex", tagline: "Red-string board", skin: 6 },
{ slug: "greenroom", tagline: "Backstage pass pasteboard", skin: 7 },
];
export function syndicateSkinForSlug(slug: string): number {
const hit = SYNDICATE_NODES.find((n) => n.slug === slug);
if (hit) return hit.skin;
let h = 0;
for (let i = 0; i < slug.length; i++) h = (h + slug.charCodeAt(i) * (i + 1)) % 8;
return h;
}

34
lib/userActivityStats.ts Normal file
View File

@@ -0,0 +1,34 @@
import { loadBarter } from "@/lib/barterState";
import { loadExchange } from "@/lib/exchangeState";
import { loadForum } from "@/lib/forumState";
/** Match forum / listings when the user types the same handle (case-insensitive). */
function matchesHandle(author: string, handles: string[]): boolean {
const a = author.trim().toLowerCase();
if (!a) return false;
const set = new Set(handles.map((h) => h.trim().toLowerCase()).filter(Boolean));
return set.has(a);
}
export type UserActivityStats = {
forumThreads: number;
forumReplies: number;
exchangeListings: number;
barterListings: number;
};
export function computeUserActivityStats(username: string, displayName: string): UserActivityStats {
const handles = [username, displayName];
const forum = loadForum();
let forumThreads = 0;
let forumReplies = 0;
for (const t of forum) {
if (matchesHandle(t.author, handles)) forumThreads++;
for (const r of t.replies) {
if (matchesHandle(r.author, handles)) forumReplies++;
}
}
const exchangeListings = loadExchange().filter((e) => matchesHandle(e.author, handles)).length;
const barterListings = loadBarter().filter((b) => matchesHandle(b.author, handles)).length;
return { forumThreads, forumReplies, exchangeListings, barterListings };
}

View File

@@ -0,0 +1,59 @@
/**
* Client-side vendor intake queue (no remote submission endpoint in this build).
*/
const KEY_QUEUE = "cyberlux-vendor-applications-v1";
export type VendorApplication = {
id: string;
ts: number;
/** Signed-in account handle if any */
accountUsername: string | null;
desiredHandle: string;
stallName: string;
specialtyCategory: string;
pitch: string;
pgpFingerprint: string;
deadDropNotes: string;
acceptedSimulatorTerms: boolean;
};
function uid(): string {
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
return `va_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
}
export function loadVendorApplications(): VendorApplication[] {
if (typeof window === "undefined") return [];
try {
const raw = localStorage.getItem(KEY_QUEUE);
if (!raw) return [];
const v = JSON.parse(raw) as unknown;
if (!Array.isArray(v)) return [];
return v.filter(
(x): x is VendorApplication =>
x &&
typeof x === "object" &&
typeof (x as VendorApplication).id === "string" &&
typeof (x as VendorApplication).desiredHandle === "string",
);
} catch {
return [];
}
}
export function appendVendorApplication(entry: Omit<VendorApplication, "id" | "ts">): VendorApplication {
const row: VendorApplication = {
...entry,
id: uid(),
ts: Date.now(),
};
if (typeof window === "undefined") return row;
const next = [row, ...loadVendorApplications()].slice(0, 50);
localStorage.setItem(KEY_QUEUE, JSON.stringify(next));
return row;
}
export function countVendorApplications(): number {
return loadVendorApplications().length;
}

40
lib/webring.ts Normal file
View File

@@ -0,0 +1,40 @@
export type WebringSeed = {
slug: string;
title: string;
tagline: string;
};
export const featuredWebringSeeds: WebringSeed[] = [
{ slug: "trees", title: "Trees (Uncensored)", tagline: "The only site brave enough to talk about trees." },
{ slug: "teapot-index", title: "The Teapot Index", tagline: "Independent reviews of teapots that may or may not be sentient." },
{ slug: "museum-of-dust", title: "Museum of Dust", tagline: "Curated particles. Rare specimens. Absolutely not haunted." },
{ slug: "cloud-tax", title: "Cloud Tax Authority", tagline: "Pay your cloud taxes. Or face sky penalties." },
{ slug: "receipt-poetry", title: "Receipt Poetry", tagline: "We turn questionable purchases into sonnets." },
{ slug: "night-noodles", title: "Night Noodles Hotline", tagline: "Anonymous noodles. Encrypted broth." },
{ slug: "sandwich-law", title: "Sandwich Law Review", tagline: "Case law, deli precedent, mustard jurisprudence." },
{ slug: "worms", title: "Worms & Governance", tagline: "Elected officials. Unelected worms. Same energy." },
];
export function hashSlugToInt(slug: string) {
let h = 2166136261;
for (let i = 0; i < slug.length; i++) {
h ^= slug.charCodeAt(i);
h = Math.imul(h, 16777619);
}
return h >>> 0;
}
export function pick<T>(arr: T[], seed: number) {
return arr[seed % arr.length];
}
export function pseudoRand(seed: number) {
let x = seed || 123456789;
return () => {
x ^= x << 13;
x ^= x >>> 17;
x ^= x << 5;
return (x >>> 0) / 4294967296;
};
}