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

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

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

View File

@@ -35,17 +35,17 @@ const seeds: BarterListing[] = [
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).",
have: "XMR at spot-ish — 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.",
body: "Escrow via hub USD balance after Bitcoin verify, or agree plaintext terms in-thread.",
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).",
have: "4h mixing desk + mastering chain on airgapped DAW session (training scenario).",
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.",
@@ -68,7 +68,7 @@ const seeds: BarterListing[] = [
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.",
body: "Coordinate only through your institutions lab policy — this board is data, not logistics.",
ts: Date.now() - 7200000 * 14,
},
];

View File

@@ -1,5 +1,3 @@
import { PLACEHOLDER_ONION_URL } from "@/lib/placeholderOnion";
export type AtlasLink = {
title: string;
note: string;
@@ -21,11 +19,54 @@ export type AtlasCategory = {
subsections: AtlasSubsection[];
};
const P = PLACEHOLDER_ONION_URL;
/** In-app surfaces only — every row opens a real CyberLux route (same Next build). */
const ATLAS_CYBERLUX_ROUTES = [
"/forum",
"/market",
"/exchange",
"/barter",
"/chatter",
"/hidden-wiki",
"/search",
"/support",
"/checkout",
"/dashboard",
"/account/add-funds",
"/vendor/apply",
"/trust",
"/comparison",
"/links",
"/sanctuary",
"/mixer",
"/security-analysis",
"/presswire",
"/syndicate",
"/trees",
"/drop-box",
"/inner-circle",
"/testimonials",
"/awards",
"/arb-academy",
"/wallets",
"/darknet-atlas",
"/sign-up",
"/sign-in",
"/vault",
"/game",
"/webring",
"/",
"/raffle",
"/conspiracies",
"/messages",
] as const;
/** 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 };
let __atlasRouteCursor = 0;
/** Classroom-safe framing: taxonomy mirrors analyst labels — links are CyberLux teaching surfaces, not endorsements. */
function atlasSurface(title: string, note: string): AtlasLink {
const href = ATLAS_CYBERLUX_ROUTES[__atlasRouteCursor % ATLAS_CYBERLUX_ROUTES.length]!;
__atlasRouteCursor++;
return { title, note, href };
}
export const DARKNET_ATLAS: AtlasCategory[] = [
@@ -39,51 +80,51 @@ export const DARKNET_ATLAS: AtlasCategory[] = [
"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.",
name: "Narcotics & precursor chatter (intel labels)",
blurb: "Reporting often references opioid stimulants, novel psychoactive threads, and precursor sourcing — links open CyberLux teaching surfaces.",
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."),
atlasSurface("Listing mirror slot — Western EU route fiction", "Replace with your case-study onion if applicable."),
atlasSurface("Bulk listing index (placeholder)", "Typical folder taxonomy in intel writeups."),
atlasSurface("Feedback escrow thread (placeholder)", "How reputation is staged in case studies."),
atlasSurface("Chemical catalog fiction slot A", "Placeholder."),
atlasSurface("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."),
atlasSurface("Parts vendor fiction index", "Placeholder .onion row."),
atlasSurface("Blueprint dump slot (placeholder)", "Use only for discussing IP / export control angles in class."),
atlasSurface("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."),
atlasSurface("Template reseller slot A", "Placeholder."),
atlasSurface("Template reseller slot B", "Placeholder."),
atlasSurface("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."),
atlasSurface("CC dump forum fiction slot", "Placeholder."),
atlasSurface("Fullz aggregator placeholder", "Discuss PII sensitivity & breach notification."),
atlasSurface("Bank log rumor board (simulated)", "Placeholder."),
atlasSurface("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."),
atlasSurface("Combo list seller fiction", "Placeholder."),
atlasSurface("Stealer log warehouse slot", "Placeholder."),
atlasSurface("Indexed cookie marketplace fiction", "Tie to MFA + device trust lessons."),
],
},
],
@@ -101,53 +142,53 @@ export const DARKNET_ATLAS: AtlasCategory[] = [
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."),
atlasSurface("HaaS storefront fiction A", "Placeholder."),
atlasSurface("HaaS storefront fiction B", "Placeholder."),
atlasSurface("“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."),
atlasSurface("Loader subscription panel (placeholder)", "Discuss OPSEC failures of panels."),
atlasSurface("Crypter AS-a-service fiction", "Placeholder."),
atlasSurface("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."),
atlasSurface("Affiliate portal fiction", "Placeholder."),
atlasSurface("Negotiation chat relay (simulated)", "Placeholder."),
atlasSurface("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."),
atlasSurface("Botnet panel fiction slot", "Placeholder."),
atlasSurface("SMS pump relay fiction", "Placeholder."),
atlasSurface("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."),
atlasSurface("Layer-7 stresser fiction", "Placeholder."),
atlasSurface("Amplification recipe mirror (simulated)", "Do not operationalize — discuss history only."),
atlasSurface("“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."),
atlasSurface("Exploit auction fiction slot", "Placeholder."),
atlasSurface("Browser chain rumor board", "Placeholder."),
],
},
],
@@ -165,25 +206,25 @@ export const DARKNET_ATLAS: AtlasCategory[] = [
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."),
atlasSurface("Leak blog mirror A (placeholder)", "Replace for exercise."),
atlasSurface("Leak blog mirror B (placeholder)", "Replace for exercise."),
atlasSurface("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."),
atlasSurface("Naming-and-shaming forum fiction", "Placeholder."),
atlasSurface("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."),
atlasSurface("Archive reseller slot", "Placeholder."),
atlasSurface("“Corporate pack” fiction index", "Placeholder."),
],
},
],
@@ -206,25 +247,25 @@ export const DARKNET_ATLAS: AtlasCategory[] = [
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."),
atlasSurface("Fictional newsroom SecureDrop slot A", "Placeholder onion — compare key verification steps."),
atlasSurface("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."),
atlasSurface("PGP-only mailbox fiction", "Placeholder."),
atlasSurface("One-time dead drop scheduler (simulated)", "Placeholder."),
atlasSurface("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."),
atlasSurface("Human-rights mirror fiction slot", "Placeholder."),
atlasSurface("Election-monitoring scrape fiction", "Placeholder."),
],
},
],
@@ -247,8 +288,8 @@ export const DARKNET_ATLAS: AtlasCategory[] = [
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."),
atlasSurface("Tor mail provider mirror slot A (placeholder)", "If you add a real audited provider mirror, cite PGP proof."),
atlasSurface("Tor mail provider mirror slot B (placeholder)", "Placeholder."),
],
},
{
@@ -265,24 +306,24 @@ export const DARKNET_ATLAS: AtlasCategory[] = [
note: "How hidden services differ from VPN marketing.",
href: "https://www.torproject.org/",
},
o("SearX onion instance fiction", "Placeholder — prefer community-vetted lists."),
atlasSurface("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."),
atlasSurface("Encrypted file locker fiction A", "Placeholder."),
atlasSurface("Encrypted file locker fiction B", "Placeholder."),
atlasSurface("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."),
atlasSurface("VPN rumor board (simulated)", "Placeholder."),
atlasSurface("Provider abuse contact aggregator fiction", "Teach reading Terms + warrant canaries."),
],
},
],
@@ -300,25 +341,25 @@ export const DARKNET_ATLAS: AtlasCategory[] = [
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."),
atlasSurface("PGP ritual bulletin fiction", "Placeholder."),
atlasSurface("Hardware token swap fiction", "Placeholder."),
atlasSurface("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."),
atlasSurface("RE workshop fiction slot", "Placeholder."),
atlasSurface("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."),
atlasSurface("Regional protest logistics fiction", "Placeholder — teach proportionality & safety planning."),
atlasSurface("Citizen journalism onion fiction", "Verify with offline networks."),
],
},
{
@@ -330,16 +371,16 @@ export const DARKNET_ATLAS: AtlasCategory[] = [
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."),
atlasSurface("Vendor drama archaeology fiction", "Placeholder."),
atlasSurface("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."),
atlasSurface("Bridge relay fiction slot", "Placeholder."),
atlasSurface("Invite-only Jabber fiction", "Placeholder."),
],
},
],
@@ -357,17 +398,17 @@ export const DARKNET_ATLAS: AtlasCategory[] = [
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."),
atlasSurface("Mixer directory fiction A", "Placeholder."),
atlasSurface("Mixer directory fiction B", "Placeholder."),
atlasSurface("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."),
atlasSurface("OTC reputation thread fiction", "Placeholder."),
atlasSurface("Stablecoin bridge chatter fiction", "Placeholder."),
],
},
],
@@ -385,16 +426,16 @@ export const DARKNET_ATLAS: AtlasCategory[] = [
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."),
atlasSurface("IAB storefront fiction", "Placeholder."),
atlasSurface("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."),
atlasSurface("Stealer panel fiction A", "Placeholder."),
atlasSurface("Log taxonomy cheat-sheet fiction", "Placeholder."),
],
},
],
@@ -412,8 +453,8 @@ export const DARKNET_ATLAS: AtlasCategory[] = [
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."),
atlasSurface("Bulletproof reseller fiction", "Placeholder."),
atlasSurface("Fast-flux tutorial fiction (do not follow)", "Teach detection only."),
],
},
],
@@ -431,11 +472,11 @@ export const DARKNET_ATLAS: AtlasCategory[] = [
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."),
atlasSurface("Mirror diff gossip board fiction", "Placeholder."),
atlasSurface("PGP watchlist fiction", "Placeholder."),
{
title: "CyberLux hub (verify signage in sim)",
note: "Main storefront skin — compare sidebar claims to reality.",
title: "CyberLux hub (verify signage)",
note: "Main storefront — compare sidebar claims to what ships in this build.",
href: "/",
},
],
@@ -455,15 +496,15 @@ export const DARKNET_ATLAS: AtlasCategory[] = [
name: "Recovery & refund scams",
blurb: "Victims pay twice — teach reporting to FTC/FBI IC3 equivalents.",
links: [
o("Recovery service fiction A", "Placeholder."),
o("Chargeback “expert” fiction", "Placeholder."),
atlasSurface("Recovery service fiction A", "Placeholder."),
atlasSurface("Chargeback “expert” fiction", "Placeholder."),
],
},
{
name: "Escrow & multisig theater",
blurb: "Compare multisig flows your sim shows vs real Bitcoin scripts.",
links: [
o("Escrow theater storefront fiction", "Placeholder."),
atlasSurface("Escrow theater storefront fiction", "Placeholder."),
],
},
],
@@ -481,15 +522,15 @@ export const DARKNET_ATLAS: AtlasCategory[] = [
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."),
atlasSurface("Regional conflict thread fiction A", "Placeholder."),
atlasSurface("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."),
atlasSurface("Ops coordination fiction", "Placeholder."),
],
},
],

View File

@@ -1,6 +1,8 @@
/**
* Set NEXT_PUBLIC_MERCHANT_BTC_ADDRESS (and MERCHANT_BTC_ADDRESS for server verify) in .env.local
* to your Bitcoin receiving address (e.g. bc1q...).
*
* Optional: NEXT_PUBLIC_BITCOIN_CHECKOUT_URL — BTCPay / hosted checkout; surfaced on /account/add-funds.
*/
export function getMerchantBtcAddress(): string {
if (typeof window !== "undefined") {

View File

@@ -13,51 +13,69 @@ export type VaultState = {
flags: Record<string, boolean>;
};
const STORAGE_KEY = "cyberlux:vault:v1";
/** Legacy single-vault bucket (preper-account). */
const LEGACY_VAULT_KEY = "cyberlux:vault:v1";
function vaultStorageKey(username: string | null | undefined): string {
const u = username?.trim().toLowerCase();
if (!u) return LEGACY_VAULT_KEY;
return `cyberlux:vault:v2:${u}`;
}
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: {},
};
return { version: 1, keys: [], receipts: [], 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;
const keys = Array.isArray(v.keys) ? v.keys.filter((k) => typeof k === "string") : [];
const receipts: VaultReceipt[] = Array.isArray(v.receipts)
? (v.receipts as unknown[]).filter(Boolean).map((r) => {
const x = r as Record<string, unknown>;
const sev = x.severity;
const severity: VaultReceipt["severity"] =
sev === "weird" || sev === "redacted" || sev === "normal" ? sev : "normal";
return {
id: String(x.id ?? ""),
title: String(x.title ?? "Receipt"),
desc: String(x.desc ?? ""),
date: String(x.date ?? ""),
severity,
};
})
: [];
const flags = v.flags && typeof v.flags === "object" ? (v.flags as Record<string, boolean>) : {};
return { version: 1, keys, receipts, flags };
} catch {
return defaultVaultState();
}
}
export function loadVaultState(): VaultState {
/** Load vault for a signed-in handle, or the legacy anonymous bucket when username is omitted. */
export function loadVaultState(username?: string | null): VaultState {
if (typeof window === "undefined") return defaultVaultState();
return safeParseVaultState(window.localStorage.getItem(STORAGE_KEY));
const u = username?.trim() ? username.trim().toLowerCase() : null;
const key = vaultStorageKey(u);
let raw = localStorage.getItem(key);
if (u && raw == null) {
const legacy = localStorage.getItem(LEGACY_VAULT_KEY);
if (legacy) {
localStorage.setItem(key, legacy);
raw = legacy;
}
}
return safeParseVaultState(raw);
}
export function saveVaultState(state: VaultState) {
export function saveVaultState(username: string | null | undefined, state: VaultState): void {
if (typeof window === "undefined") return;
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
const key = vaultStorageKey(username?.trim() ? username.trim().toLowerCase() : null);
localStorage.setItem(key, JSON.stringify(state));
if (username?.trim() && localStorage.getItem(LEGACY_VAULT_KEY)) {
localStorage.removeItem(LEGACY_VAULT_KEY);
}
}
export function addKey(state: VaultState, key: string): VaultState {
@@ -74,10 +92,9 @@ export function addReceipt(state: VaultState, receipt: VaultReceipt): VaultState
export function setFlag(state: VaultState, flag: string, value: boolean): VaultState {
if (!flag) return state;
return { ...state, flags: { ...state.flags, [flag]: value } };
return { ...state, flags: { ...state.flags, [flag]: Boolean(value) } };
}
export function hasFlag(state: VaultState, flag: string) {
return Boolean(state.flags?.[flag]);
}

View File

@@ -1,38 +1,114 @@
const USD_KEY = "cyberlux-usd-store-credit";
const TX_KEY = "cyberlux-btc-claimed-txids";
/**
* Per-account USD balance (from verified Bitcoin deposits), LUX loyalty points,
* and claimed txids — all keyed by CyberLux handle (same origin = one account).
*/
export function loadUsdStoreCredit(): number {
if (typeof window === "undefined") return 0;
export type AccountLedgerRow = {
usd: number;
lux: number;
claimedTxids: string[];
};
const LEDGER_KEY = "cyberlux-account-ledger-v1";
/** Preper-account migration keys (device-wide). */
const LEGACY_USD_KEY = "cyberlux-usd-store-credit";
const LEGACY_TX_KEY = "cyberlux-btc-claimed-txids";
function emptyRow(): AccountLedgerRow {
return { usd: 0, lux: 0, claimedTxids: [] };
}
function loadLedgerMap(): Record<string, AccountLedgerRow> {
if (typeof window === "undefined") return {};
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;
const raw = localStorage.getItem(LEDGER_KEY);
if (!raw) return {};
const o = JSON.parse(raw) as Record<string, unknown>;
if (!o || typeof o !== "object") return {};
const out: Record<string, AccountLedgerRow> = {};
for (const [k, v] of Object.entries(o)) {
if (!k || typeof v !== "object" || v === null) continue;
const row = v as Record<string, unknown>;
const usd = typeof row.usd === "number" && Number.isFinite(row.usd) ? Math.max(0, Math.round(row.usd * 100) / 100) : 0;
const lux = typeof row.lux === "number" && Number.isFinite(row.lux) ? Math.max(0, Math.floor(row.lux)) : 0;
const claimedTxids = Array.isArray(row.claimedTxids)
? row.claimedTxids.map((t) => String(t)).filter((t) => /^[a-fA-F0-9]{64}$/.test(t))
: [];
out[k.toLowerCase()] = { usd, lux, claimedTxids: [...new Set(claimedTxids)] };
}
return out;
} catch {
return 0;
return {};
}
}
export function saveUsdStoreCredit(usd: number): void {
function saveLedgerMap(m: Record<string, AccountLedgerRow>): void {
if (typeof window === "undefined") return;
const safe = Math.max(0, Math.round(usd * 100) / 100);
localStorage.setItem(USD_KEY, String(safe));
localStorage.setItem(LEDGER_KEY, JSON.stringify(m));
}
export function loadClaimedTxids(): string[] {
if (typeof window === "undefined") return [];
function accountKey(username: string): string {
return username.trim().toLowerCase();
}
export function getLedgerRow(username: string | null): AccountLedgerRow {
if (!username) return emptyRow();
const map = loadLedgerMap();
return map[accountKey(username)] ?? emptyRow();
}
export function setLedgerRow(username: string, row: AccountLedgerRow): void {
const map = loadLedgerMap();
map[accountKey(username)] = {
usd: Math.max(0, Math.round(row.usd * 100) / 100),
lux: Math.max(0, Math.floor(row.lux)),
claimedTxids: [...new Set(row.claimedTxids.filter((t) => /^[a-fA-F0-9]{64}$/.test(t)))],
};
saveLedgerMap(map);
}
/**
* One-time: move old device-wide USD + txids into this handles ledger, then clear legacy keys.
*/
export function migrateLegacyDeviceLedgerIfNeeded(username: string): void {
if (typeof window === "undefined" || !username.trim()) return;
const key = accountKey(username);
const map = loadLedgerMap();
const existing = map[key] ?? emptyRow();
if (existing.usd > 0 || existing.claimedTxids.length > 0) return;
let legacyUsd = 0;
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));
const raw = localStorage.getItem(LEGACY_USD_KEY);
if (raw) {
const n = Number.parseFloat(raw);
if (Number.isFinite(n)) legacyUsd = Math.max(0, Math.round(n * 100) / 100);
}
} catch {
return [];
/* ignore */
}
}
export function saveClaimedTxids(txids: string[]): void {
if (typeof window === "undefined") return;
localStorage.setItem(TX_KEY, JSON.stringify([...new Set(txids)]));
let legacyTx: string[] = [];
try {
const raw = localStorage.getItem(LEGACY_TX_KEY);
if (raw) {
const parsed = JSON.parse(raw) as unknown;
if (Array.isArray(parsed)) {
legacyTx = parsed.map((t) => String(t)).filter((t) => /^[a-fA-F0-9]{64}$/.test(t));
}
}
} catch {
/* ignore */
}
if (legacyUsd <= 0 && legacyTx.length === 0) return;
map[key] = {
usd: legacyUsd,
lux: 0,
claimedTxids: [...new Set(legacyTx)],
};
saveLedgerMap(map);
localStorage.removeItem(LEGACY_USD_KEY);
localStorage.removeItem(LEGACY_TX_KEY);
}