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,74 +1,297 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useState, useEffect, useRef, useCallback } from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function DarknetPingPong() {
|
||||
const [ballPos, setBallPos] = useState({ x: 50, y: 50 });
|
||||
const [isHaunted, setIsHaunted] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const FIELD_W = 600;
|
||||
const FIELD_H = 400;
|
||||
const BALL_R = 12;
|
||||
const PADDLE_W = 12;
|
||||
const PADDLE_H = 70;
|
||||
const CPU_SPEED = 3.2;
|
||||
const INITIAL_SPEED = 4.5;
|
||||
|
||||
type Vec = { x: number; y: number };
|
||||
type GameState = "idle" | "playing" | "paused" | "over";
|
||||
|
||||
function clamp(v: number, lo: number, hi: number) {
|
||||
return Math.max(lo, Math.min(hi, v));
|
||||
}
|
||||
|
||||
const HIGH_SCORE_KEY = "cyberlux-pong-hs";
|
||||
|
||||
function getHighScore() {
|
||||
if (typeof window === "undefined") return 0;
|
||||
return parseInt(localStorage.getItem(HIGH_SCORE_KEY) ?? "0", 10);
|
||||
}
|
||||
function setHighScore(n: number) {
|
||||
if (typeof window !== "undefined") localStorage.setItem(HIGH_SCORE_KEY, String(n));
|
||||
}
|
||||
|
||||
export default function PongPage() {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const stateRef = useRef<GameState>("idle");
|
||||
const ballRef = useRef<Vec>({ x: FIELD_W / 2, y: FIELD_H / 2 });
|
||||
const velRef = useRef<Vec>({ x: INITIAL_SPEED, y: INITIAL_SPEED });
|
||||
const playerYRef = useRef((FIELD_H - PADDLE_H) / 2);
|
||||
const cpuYRef = useRef((FIELD_H - PADDLE_H) / 2);
|
||||
const scoreRef = useRef({ player: 0, cpu: 0 });
|
||||
const rafRef = useRef<number>(0);
|
||||
const mouseYRef = useRef(FIELD_H / 2);
|
||||
|
||||
const [displayScore, setDisplayScore] = useState({ player: 0, cpu: 0 });
|
||||
const [gameState, setGameState] = useState<GameState>("idle");
|
||||
const [highScore, setHighScoreState] = useState(0);
|
||||
const [rally, setRally] = useState(0);
|
||||
const rallyRef = useRef(0);
|
||||
|
||||
// Simulate shared state with a simple interval or WebSocket-like behavior
|
||||
// In a real app, this would use Pusher, Socket.io, or a backend
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
// Random "haunted" movement to simulate other users or ghosts
|
||||
if (Math.random() > 0.95) {
|
||||
setBallPos(prev => ({
|
||||
x: Math.max(10, Math.min(90, prev.x + (Math.random() - 0.5) * 20)),
|
||||
y: Math.max(10, Math.min(90, prev.y + (Math.random() - 0.5) * 20)),
|
||||
}));
|
||||
setIsHaunted(true);
|
||||
setTimeout(() => setIsHaunted(false), 500);
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
setHighScoreState(getHighScore());
|
||||
}, []);
|
||||
|
||||
const handleBallClick = (e: React.MouseEvent) => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const rect = containerRef.current.getBoundingClientRect();
|
||||
const x = ((e.clientX - rect.left) / rect.width) * 100;
|
||||
const y = ((e.clientY - rect.top) / rect.height) * 100;
|
||||
const reset = useCallback(() => {
|
||||
ballRef.current = { x: FIELD_W / 2, y: FIELD_H / 2 };
|
||||
const angle = (Math.random() * Math.PI) / 3 - Math.PI / 6;
|
||||
const dir = Math.random() > 0.5 ? 1 : -1;
|
||||
velRef.current = {
|
||||
x: INITIAL_SPEED * dir * Math.cos(angle),
|
||||
y: INITIAL_SPEED * Math.sin(angle),
|
||||
};
|
||||
rallyRef.current = 0;
|
||||
setRally(0);
|
||||
}, []);
|
||||
|
||||
// Move ball to click position
|
||||
setBallPos({ x, y });
|
||||
|
||||
// In a real app, you'd emit this to a server:
|
||||
// socket.emit('move_ball', { x, y });
|
||||
};
|
||||
const draw = useCallback(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
// Background
|
||||
ctx.fillStyle = "#050510";
|
||||
ctx.fillRect(0, 0, FIELD_W, FIELD_H);
|
||||
|
||||
// Center line
|
||||
ctx.setLineDash([8, 8]);
|
||||
ctx.strokeStyle = "rgba(0,255,200,0.1)";
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(FIELD_W / 2, 0);
|
||||
ctx.lineTo(FIELD_W / 2, FIELD_H);
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
|
||||
// Paddles
|
||||
const drawPaddle = (x: number, y: number, color: string) => {
|
||||
ctx.shadowColor = color;
|
||||
ctx.shadowBlur = 12;
|
||||
ctx.fillStyle = color;
|
||||
ctx.fillRect(x, y, PADDLE_W, PADDLE_H);
|
||||
ctx.shadowBlur = 0;
|
||||
};
|
||||
drawPaddle(8, playerYRef.current, "#00ffcc");
|
||||
drawPaddle(FIELD_W - 8 - PADDLE_W, cpuYRef.current, "#ff00ff");
|
||||
|
||||
// Ball
|
||||
ctx.shadowColor = "#00ffcc";
|
||||
ctx.shadowBlur = 20;
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.beginPath();
|
||||
ctx.arc(ballRef.current.x, ballRef.current.y, BALL_R, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.shadowBlur = 0;
|
||||
|
||||
// Score overlay
|
||||
ctx.fillStyle = "rgba(0,255,200,0.15)";
|
||||
ctx.font = "bold 36px monospace";
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(String(scoreRef.current.player), FIELD_W / 4, 50);
|
||||
ctx.fillStyle = "rgba(255,0,255,0.15)";
|
||||
ctx.fillText(String(scoreRef.current.cpu), (FIELD_W * 3) / 4, 50);
|
||||
}, []);
|
||||
|
||||
const tick = useCallback(() => {
|
||||
if (stateRef.current !== "playing") return;
|
||||
|
||||
// Move ball
|
||||
const b = ballRef.current;
|
||||
const v = velRef.current;
|
||||
b.x += v.x;
|
||||
b.y += v.y;
|
||||
|
||||
// Wall bounce top/bottom
|
||||
if (b.y - BALL_R <= 0) { b.y = BALL_R; v.y = Math.abs(v.y); }
|
||||
if (b.y + BALL_R >= FIELD_H) { b.y = FIELD_H - BALL_R; v.y = -Math.abs(v.y); }
|
||||
|
||||
// CPU paddle AI
|
||||
const cpuCenter = cpuYRef.current + PADDLE_H / 2;
|
||||
if (cpuCenter < b.y - 4) cpuYRef.current = clamp(cpuYRef.current + CPU_SPEED, 0, FIELD_H - PADDLE_H);
|
||||
if (cpuCenter > b.y + 4) cpuYRef.current = clamp(cpuYRef.current - CPU_SPEED, 0, FIELD_H - PADDLE_H);
|
||||
|
||||
// Player paddle follow mouse
|
||||
playerYRef.current = clamp(mouseYRef.current - PADDLE_H / 2, 0, FIELD_H - PADDLE_H);
|
||||
|
||||
// Player paddle hit
|
||||
if (b.x - BALL_R <= 8 + PADDLE_W && b.y >= playerYRef.current && b.y <= playerYRef.current + PADDLE_H && v.x < 0) {
|
||||
b.x = 8 + PADDLE_W + BALL_R;
|
||||
const hitPos = (b.y - playerYRef.current) / PADDLE_H - 0.5;
|
||||
const speed = Math.min(Math.sqrt(v.x ** 2 + v.y ** 2) + 0.15, 10);
|
||||
v.x = Math.abs(speed * Math.cos(hitPos * Math.PI * 0.6));
|
||||
v.y = speed * Math.sin(hitPos * Math.PI * 0.6);
|
||||
rallyRef.current++;
|
||||
setRally(rallyRef.current);
|
||||
}
|
||||
|
||||
// CPU paddle hit
|
||||
if (b.x + BALL_R >= FIELD_W - 8 - PADDLE_W && b.y >= cpuYRef.current && b.y <= cpuYRef.current + PADDLE_H && v.x > 0) {
|
||||
b.x = FIELD_W - 8 - PADDLE_W - BALL_R;
|
||||
const hitPos = (b.y - cpuYRef.current) / PADDLE_H - 0.5;
|
||||
const speed = Math.sqrt(v.x ** 2 + v.y ** 2);
|
||||
v.x = -Math.abs(speed * Math.cos(hitPos * Math.PI * 0.6));
|
||||
v.y = speed * Math.sin(hitPos * Math.PI * 0.6);
|
||||
rallyRef.current++;
|
||||
setRally(rallyRef.current);
|
||||
}
|
||||
|
||||
// Scoring
|
||||
if (b.x < 0) {
|
||||
scoreRef.current.cpu++;
|
||||
setDisplayScore({ ...scoreRef.current });
|
||||
if (scoreRef.current.cpu >= 7) {
|
||||
stateRef.current = "over";
|
||||
setGameState("over");
|
||||
const hs = getHighScore();
|
||||
if (rallyRef.current > hs) { setHighScore(rallyRef.current); setHighScoreState(rallyRef.current); }
|
||||
} else reset();
|
||||
}
|
||||
if (b.x > FIELD_W) {
|
||||
scoreRef.current.player++;
|
||||
setDisplayScore({ ...scoreRef.current });
|
||||
if (scoreRef.current.player >= 7) {
|
||||
stateRef.current = "over";
|
||||
setGameState("over");
|
||||
const hs = getHighScore();
|
||||
if (rallyRef.current > hs) { setHighScore(rallyRef.current); setHighScoreState(rallyRef.current); }
|
||||
} else reset();
|
||||
}
|
||||
|
||||
draw();
|
||||
rafRef.current = requestAnimationFrame(tick);
|
||||
}, [draw, reset]);
|
||||
|
||||
const startGame = useCallback(() => {
|
||||
scoreRef.current = { player: 0, cpu: 0 };
|
||||
setDisplayScore({ player: 0, cpu: 0 });
|
||||
stateRef.current = "playing";
|
||||
setGameState("playing");
|
||||
reset();
|
||||
if (rafRef.current) cancelAnimationFrame(rafRef.current);
|
||||
rafRef.current = requestAnimationFrame(tick);
|
||||
}, [tick, reset]);
|
||||
|
||||
useEffect(() => {
|
||||
draw();
|
||||
return () => { if (rafRef.current) cancelAnimationFrame(rafRef.current); };
|
||||
}, [draw]);
|
||||
|
||||
const handleMouseMove = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
|
||||
const rect = canvasRef.current?.getBoundingClientRect();
|
||||
if (!rect) return;
|
||||
const scaleY = FIELD_H / rect.height;
|
||||
mouseYRef.current = (e.clientY - rect.top) * scaleY;
|
||||
}, []);
|
||||
|
||||
const handleTouchMove = useCallback((e: React.TouchEvent<HTMLCanvasElement>) => {
|
||||
e.preventDefault();
|
||||
const rect = canvasRef.current?.getBoundingClientRect();
|
||||
if (!rect || !e.touches[0]) return;
|
||||
const scaleY = FIELD_H / rect.height;
|
||||
mouseYRef.current = (e.touches[0].clientY - rect.top) * scaleY;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="relative w-full h-[600px] bg-[url('https://images.unsplash.com/photo-1533090161767-e6ffed986c88?q=80&w=2069&auto=format&fit=crop')] bg-cover bg-center border-8 border-[#3d2b1f] shadow-2xl overflow-hidden cursor-crosshair"
|
||||
style={{ backgroundColor: "#1a1a1a" }}
|
||||
>
|
||||
<div className="absolute inset-0 bg-black/40 pointer-events-none" />
|
||||
|
||||
{/* The Haunted Ball */}
|
||||
<div
|
||||
onClick={handleBallClick}
|
||||
className={`absolute w-16 h-16 rounded-full cursor-pointer transition-all duration-500 ease-out
|
||||
${isHaunted ? "scale-125 blur-sm" : "scale-100"}
|
||||
shadow-[0_0_30px_rgba(255,255,255,0.3)]
|
||||
`}
|
||||
style={{
|
||||
left: `${ballPos.x}%`,
|
||||
top: `${ballPos.y}%`,
|
||||
transform: "translate(-50%, -50%)",
|
||||
background: "radial-gradient(circle at 30% 30%, #555, #000)",
|
||||
border: "2px solid rgba(255,255,255,0.1)"
|
||||
}}
|
||||
>
|
||||
<div className="absolute inset-0 flex items-center justify-center opacity-20">
|
||||
<span className="text-white text-[10px] uppercase tracking-widest">void</span>
|
||||
<div className="flex min-h-screen flex-col items-center justify-center bg-[#050510] p-6 font-mono text-white">
|
||||
<div className="w-full max-w-[620px]">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-xl font-black uppercase tracking-widest text-neon-cyan">VOID PONG</h1>
|
||||
<p className="text-[10px] text-white/30">Move mouse over field to control paddle. First to 7.</p>
|
||||
</div>
|
||||
<div className="text-right text-xs text-white/40">
|
||||
<div>Rally record: <span className="text-neon-cyan">{highScore}</span></div>
|
||||
{rally > 0 && gameState === "playing" && (
|
||||
<div>Current rally: <span className="text-neon-purple">{rally}</span></div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-4 left-4 text-white/50 font-mono text-xs uppercase tracking-widest">
|
||||
Shared Entity #001 - Darknet Ping Pong
|
||||
{gameState !== "idle" && (
|
||||
<div className="mb-3 flex items-center justify-between text-sm">
|
||||
<span className="text-neon-cyan font-bold">YOU — {displayScore.player}</span>
|
||||
<span className="text-xs text-white/30">vs</span>
|
||||
<span className="text-neon-purple font-bold">{displayScore.cpu} — CPU</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
width={FIELD_W}
|
||||
height={FIELD_H}
|
||||
onMouseMove={handleMouseMove}
|
||||
onTouchMove={handleTouchMove}
|
||||
className="w-full rounded-lg border border-neon-cyan/20 cursor-none"
|
||||
style={{ touchAction: "none" }}
|
||||
/>
|
||||
|
||||
{gameState === "idle" && (
|
||||
<div className="mt-6 text-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={startGame}
|
||||
className="rounded-full border-2 border-neon-cyan px-8 py-3 font-black uppercase text-neon-cyan hover:bg-neon-cyan/10 transition-all"
|
||||
>
|
||||
Start Game
|
||||
</button>
|
||||
<p className="mt-3 text-xs text-white/30">Move your mouse over the field to control the left paddle.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{gameState === "playing" && (
|
||||
<div className="mt-4 text-center text-xs text-white/20">
|
||||
Rally: {rally} hits
|
||||
</div>
|
||||
)}
|
||||
|
||||
{gameState === "over" && (
|
||||
<div className="mt-6 text-center">
|
||||
<div className="mb-3 text-2xl font-black uppercase">
|
||||
{displayScore.player >= 7 ? (
|
||||
<span className="text-neon-cyan">You Win</span>
|
||||
) : (
|
||||
<span className="text-neon-purple">CPU Wins</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-4 text-sm text-white/50">
|
||||
Score: {displayScore.player} — {displayScore.cpu} · Rally: {rallyRef.current}
|
||||
{rallyRef.current > 0 && rallyRef.current >= highScore && (
|
||||
<span className="ml-2 text-neon-cyan">New record!</span>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={startGame}
|
||||
className="rounded-full border-2 border-neon-cyan px-8 py-3 font-black uppercase text-neon-cyan hover:bg-neon-cyan/10 transition-all"
|
||||
>
|
||||
Play Again
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-6 flex justify-center gap-6 text-[10px] text-white/20">
|
||||
<Link href="/" className="hover:text-white/50">Hub</Link>
|
||||
<Link href="/arcade" className="hover:text-white/50">Arcade</Link>
|
||||
<Link href="/drops" className="hover:text-white/50">Shadow Lotto</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user