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:
@@ -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 (1–49) 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>
|
||||
|
||||
Reference in New Issue
Block a user