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
76 lines
2.7 KiB
TypeScript
76 lines
2.7 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect, useRef } from "react";
|
|
|
|
export default function DarknetPingPong() {
|
|
const [ballPos, setBallPos] = useState({ x: 50, y: 50 });
|
|
const [isHaunted, setIsHaunted] = useState(false);
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
|
|
// 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);
|
|
}, []);
|
|
|
|
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;
|
|
|
|
// 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 });
|
|
};
|
|
|
|
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>
|
|
</div>
|
|
|
|
<div className="absolute bottom-4 left-4 text-white/50 font-mono text-xs uppercase tracking-widest">
|
|
Shared Entity #001 - Darknet Ping Pong
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|