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

@@ -1,71 +1,244 @@
"use client";
import { useState, useEffect } from "react";
import { useState, useEffect, useCallback } from "react";
import Link from "next/link";
import { useAccount } from "@/contexts/AccountContext";
const NUMBERS_COUNT = 6;
const TICKET_KEY = "cyberlux-lotto-tickets-v1";
const DRAW_INTERVAL_MS = 24 * 60 * 60 * 1000;
const DRAW_EPOCH = new Date("2026-04-14T12:00:00Z").getTime();
function getNextDraw() {
const now = Date.now();
const elapsed = now - DRAW_EPOCH;
const cycles = Math.floor(elapsed / DRAW_INTERVAL_MS);
return DRAW_EPOCH + (cycles + 1) * DRAW_INTERVAL_MS;
}
function formatRemaining(ms: number) {
if (ms <= 0) return "DRAWING SOON";
const h = Math.floor(ms / 3600000);
const m = Math.floor((ms % 3600000) / 60000);
const s = Math.floor((ms % 60000) / 1000);
return `${h.toString().padStart(2, "0")}h ${m.toString().padStart(2, "0")}m ${s.toString().padStart(2, "0")}s`;
}
type Ticket = { nums: number[]; handle: string; ts: number };
function loadTickets(): Ticket[] {
if (typeof window === "undefined") return [];
try { return JSON.parse(localStorage.getItem(TICKET_KEY) ?? "[]") as Ticket[]; }
catch { return []; }
}
function saveTickets(t: Ticket[]) {
if (typeof window === "undefined") return;
localStorage.setItem(TICKET_KEY, JSON.stringify(t.slice(-50)));
}
export default function LotteryPage() {
const { user } = useAccount();
const [numbers, setNumbers] = useState<number[]>([]);
const [isDrawing, setIsDrawing] = useState(false);
const [lastWinner, setLastWinner] = useState<string>("0x7a...f2e1");
const [jackpot, setJackpot] = useState("1.24 BTC");
const [myTickets, setMyTickets] = useState<Ticket[]>([]);
const [remaining, setRemaining] = useState(0);
const [picked, setPicked] = useState<number[]>([]);
const [disclaimer, setDisclaimer] = useState(false);
const drawNumbers = () => {
useEffect(() => {
setMyTickets(loadTickets());
const tick = () => setRemaining(getNextDraw() - Date.now());
tick();
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []);
const togglePick = (n: number) => {
if (picked.includes(n)) {
setPicked((p) => p.filter((x) => x !== n));
} else if (picked.length < NUMBERS_COUNT) {
setPicked((p) => [...p, n]);
}
};
const quickPick = () => {
const pool = Array.from({ length: 49 }, (_, i) => i + 1);
const result: number[] = [];
while (result.length < NUMBERS_COUNT) {
const idx = Math.floor(Math.random() * pool.length);
result.push(pool.splice(idx, 1)[0]!);
}
setPicked(result.sort((a, b) => a - b));
};
const submitTicket = useCallback(() => {
if (picked.length !== NUMBERS_COUNT) return;
const ticket: Ticket = {
nums: [...picked].sort((a, b) => a - b),
handle: user?.username ?? "anon",
ts: Date.now(),
};
const updated = [...myTickets, ticket];
setMyTickets(updated);
saveTickets(updated);
setPicked([]);
}, [picked, myTickets, user]);
const drawNumbers = useCallback(() => {
if (!disclaimer) { setDisclaimer(true); return; }
setIsDrawing(true);
setNumbers([]);
let count = 0;
const used = new Set<number>();
const interval = setInterval(() => {
setNumbers(prev => [...prev, Math.floor(Math.random() * 99) + 1]);
let n: number;
do { n = Math.floor(Math.random() * 49) + 1; } while (used.has(n));
used.add(n);
setNumbers((prev) => [...prev, n]);
count++;
if (count >= 6) {
if (count >= NUMBERS_COUNT) {
clearInterval(interval);
setIsDrawing(false);
}
}, 500);
};
}, 400);
}, [disclaimer]);
const drawn = numbers.length === NUMBERS_COUNT;
const myMatches = drawn
? myTickets.map((t) => ({ ticket: t, matches: t.nums.filter((n) => numbers.includes(n)).length }))
: [];
return (
<div className="min-h-screen bg-[#050505] text-[#ff00ff] font-mono p-8 flex flex-col items-center justify-center">
<div className="max-w-2xl w-full border-2 border-[#ff00ff]/30 bg-[#111] p-12 shadow-[0_0_50px_rgba(255,0,255,0.1)]">
<h1 className="text-5xl font-black text-center mb-4 tracking-widest uppercase italic">Shadow Lotto</h1>
<p className="text-center text-[#ff00ff]/60 mb-12">Provably fair. Completely anonymous. High stakes.</p>
<div className="min-h-screen bg-[#050505] text-[#ff00ff] font-mono p-6 flex flex-col items-center">
<div className="w-full max-w-2xl">
<div className="border-2 border-[#ff00ff]/30 bg-[#111] p-10 shadow-[0_0_50px_rgba(255,0,255,0.08)] mb-6">
<div className="mb-3 text-center text-[10px] uppercase tracking-widest text-[#ff00ff]/40">
Demo lotto prizes are LUX credits · no real-money payouts
</div>
<h1 className="text-4xl font-black text-center mb-2 tracking-widest uppercase italic">Shadow Lotto</h1>
<p className="text-center text-[#ff00ff]/50 text-xs mb-8">
Pick 6 numbers (149) or Quick Pick. Daily demo draw matches earn LUX credits.
</p>
<div className="grid grid-cols-2 gap-8 mb-12">
<div className="text-center border border-[#ff00ff]/20 p-4">
<div className="text-xs uppercase opacity-50 mb-1">Current Jackpot</div>
<div className="text-3xl font-bold text-white">{jackpot}</div>
<div className="grid grid-cols-2 gap-6 mb-8 text-center">
<div className="border border-[#ff00ff]/20 p-4">
<div className="text-xs uppercase opacity-50 mb-1">Next Draw</div>
<div className="text-lg font-bold text-white">{formatRemaining(remaining)}</div>
</div>
<div className="border border-[#ff00ff]/20 p-4">
<div className="text-xs uppercase opacity-50 mb-1">Your Tickets</div>
<div className="text-lg font-bold text-white">{myTickets.length}</div>
</div>
</div>
<div className="text-center border border-[#ff00ff]/20 p-4">
<div className="text-xs uppercase opacity-50 mb-1">Last Winner</div>
<div className="text-sm font-bold text-white">{lastWinner}</div>
{/* Number picker */}
<div className="mb-6">
<div className="mb-3 flex items-center justify-between">
<span className="text-xs uppercase tracking-wider text-[#ff00ff]/70">
Pick {NUMBERS_COUNT} numbers · selected: {picked.length}/{NUMBERS_COUNT}
</span>
<button
type="button"
onClick={quickPick}
className="border border-[#ff00ff]/40 px-3 py-1 text-xs font-bold uppercase hover:bg-[#ff00ff]/10 transition-all"
>
Quick Pick
</button>
</div>
<div className="grid grid-cols-7 gap-1.5">
{Array.from({ length: 49 }, (_, i) => i + 1).map((n) => {
const isSelected = picked.includes(n);
const isDrawn = numbers.includes(n);
return (
<button
key={n}
type="button"
onClick={() => togglePick(n)}
className={`h-9 w-full border text-xs font-bold transition-all ${
isDrawn && drawn
? "border-[#ff00ff] bg-[#ff00ff]/30 text-white"
: isSelected
? "border-[#ff00ff] bg-[#ff00ff]/20 text-[#ff00ff]"
: "border-[#ff00ff]/15 bg-transparent text-[#ff00ff]/50 hover:border-[#ff00ff]/40"
}`}
>
{n}
</button>
);
})}
</div>
</div>
<div className="flex gap-3 mb-6">
<button
type="button"
onClick={submitTicket}
disabled={picked.length !== NUMBERS_COUNT}
className="flex-1 border-2 border-[#ff00ff] py-3 text-sm font-black uppercase transition-all hover:bg-[#ff00ff]/15 disabled:opacity-30"
>
Lock In Ticket ({picked.join("-") || "select 6"})
</button>
<button
type="button"
onClick={drawNumbers}
disabled={isDrawing}
className={`flex-1 py-3 text-sm font-black uppercase transition-all ${
isDrawing ? "bg-[#333] text-[#666] cursor-not-allowed" : "bg-[#ff00ff] text-black hover:bg-[#cc00cc]"
}`}
>
{isDrawing ? "Drawing…" : "Run Demo Draw"}
</button>
</div>
{disclaimer && !drawn && (
<div className="mb-4 border border-amber-500/50 bg-amber-950/30 p-4 text-xs text-amber-200 text-center">
This is a demo draw no real prizes are awarded. LUX credit prizes are in-app only.
<button type="button" onClick={drawNumbers} className="ml-3 underline font-bold">
OK, Draw Anyway
</button>
</div>
)}
{/* Draw result */}
{numbers.length > 0 && (
<div className="border border-[#ff00ff]/30 bg-[#ff00ff]/5 p-6">
<div className="mb-3 text-xs uppercase text-[#ff00ff]/60">Draw Result</div>
<div className="flex justify-center gap-3 flex-wrap">
{numbers.map((n, i) => (
<div
key={i}
className={`flex h-12 w-12 items-center justify-center border-2 font-black text-lg ${
isDrawing
? "border-[#ff00ff]/40 text-[#ff00ff]/60 animate-pulse"
: "border-[#ff00ff] text-white bg-[#ff00ff]/20"
}`}
>
{n}
</div>
))}
</div>
{drawn && myMatches.length > 0 && (
<div className="mt-5 space-y-2">
<div className="text-xs uppercase text-[#ff00ff]/60">Your Ticket Results</div>
{myMatches.map((m, i) => (
<div key={i} className="flex items-center justify-between text-xs">
<span className="text-[#ff00ff]/60">{m.ticket.nums.join("-")}</span>
<span className={m.matches >= 3 ? "text-[#ff00ff] font-bold" : "text-white/40"}>
{m.matches} match{m.matches !== 1 ? "es" : ""}
{m.matches >= 6 ? " — JACKPOT (LUX prize)" : m.matches >= 4 ? " — LUX reward" : ""}
</span>
</div>
))}
</div>
)}
</div>
)}
</div>
<div className="flex justify-center gap-4 mb-12 h-16">
{numbers.map((n, i) => (
<div key={i} className="w-16 h-16 border-2 border-[#ff00ff] flex items-center justify-center text-2xl font-bold bg-[#ff00ff]/10 animate-pulse">
{n}
</div>
))}
{Array.from({ length: 6 - numbers.length }).map((_, i) => (
<div key={i} className="w-16 h-16 border-2 border-[#ff00ff]/20 flex items-center justify-center text-2xl font-bold opacity-20">
?
</div>
))}
</div>
<button
onClick={drawNumbers}
disabled={isDrawing}
className={`w-full py-4 text-xl font-bold uppercase tracking-widest transition-all
${isDrawing ? "bg-[#333] text-[#666] cursor-not-allowed" : "bg-[#ff00ff] text-black hover:bg-[#cc00cc] hover:shadow-[0_0_30px_#ff00ff]/40"}
`}
>
{isDrawing ? "Drawing..." : "Buy Ticket (0.0001 BTC)"}
</button>
<div className="mt-12 text-[10px] text-[#ff00ff]/40 leading-relaxed">
* All draws are finalized via the Shadow Protocol. Tickets are non-refundable.
Winners are paid out within 3 confirmations. Good luck, traveler.
<div className="text-center text-[10px] text-[#ff00ff]/25 leading-relaxed">
Shadow Lotto is a demonstration game. No real-money gambling. LUX credits are in-app only.{" "}
<Link href="/inner-circle" className="text-[#ff00ff]/40 underline hover:text-[#ff00ff]/60">Inner Circle </Link>
</div>
</div>
</div>