This commit is contained in:
gpt-engineer-app[bot] 2025-12-09 16:42:55 +00:00
parent 21238dec45
commit 850630fbc6
3 changed files with 273 additions and 448 deletions

View File

@ -9,23 +9,22 @@ interface HumanVerificationProps {
onVerified: () => void; onVerified: () => void;
} }
interface PuzzlePiece {
targetX: number;
}
const HumanVerification = ({ onVerified }: HumanVerificationProps) => { const HumanVerification = ({ onVerified }: HumanVerificationProps) => {
const canvasRef = useRef<HTMLCanvasElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null);
const [puzzle, setPuzzle] = useState<PuzzlePiece | null>(null);
const [isDragging, setIsDragging] = useState(false); const [isDragging, setIsDragging] = useState(false);
const [sliderX, setSliderX] = useState(0); const [sliderX, setSliderX] = useState(0);
const [isVerified, setIsVerified] = useState(false); const [isVerified, setIsVerified] = useState(false);
const [error, setError] = useState(false); const [error, setError] = useState(false);
const [puzzleImage, setPuzzleImage] = useState<ImageData | null>(null);
const [targetX, setTargetX] = useState(0);
const trackRef = useRef<HTMLDivElement>(null); const trackRef = useRef<HTMLDivElement>(null);
const CANVAS_WIDTH = 320; const CANVAS_WIDTH = 300;
const CANVAS_HEIGHT = 160; const CANVAS_HEIGHT = 150;
const PIECE_SIZE = 44; const PIECE_WIDTH = 42;
const TOLERANCE = 10; const PIECE_HEIGHT = 42;
const NOTCH_SIZE = 10;
const TOLERANCE = 6;
// Check for bypass in URL on mount // Check for bypass in URL on mount
useEffect(() => { useEffect(() => {
@ -36,9 +35,156 @@ const HumanVerification = ({ onVerified }: HumanVerificationProps) => {
} }
}, [onVerified]); }, [onVerified]);
const drawPuzzlePiecePath = (ctx: CanvasRenderingContext2D, x: number, y: number) => {
const w = PIECE_WIDTH;
const h = PIECE_HEIGHT;
const n = NOTCH_SIZE;
ctx.beginPath();
ctx.moveTo(x, y);
// Top edge
ctx.lineTo(x + w, y);
// Right edge with notch
ctx.lineTo(x + w, y + h * 0.35);
ctx.arc(x + w + n * 0.5, y + h * 0.5, n, -Math.PI * 0.5, Math.PI * 0.5, false);
ctx.lineTo(x + w, y + h);
// Bottom edge
ctx.lineTo(x, y + h);
// Left edge
ctx.lineTo(x, y);
ctx.closePath();
};
const generatePuzzle = useCallback(() => { const generatePuzzle = useCallback(() => {
const targetX = Math.floor(Math.random() * (CANVAS_WIDTH - PIECE_SIZE - 100)) + 80; const canvas = canvasRef.current;
setPuzzle({ targetX }); if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
// Get theme color
const computedStyle = getComputedStyle(document.documentElement);
const primaryHsl = computedStyle.getPropertyValue('--primary').trim();
const hslParts = primaryHsl.split(' ');
const h = parseFloat(hslParts[0]) || 120;
const s = parseFloat(hslParts[1]) || 100;
const l = parseFloat(hslParts[2]) || 50;
// Clear canvas
ctx.fillStyle = '#0d0d0d';
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// Create a visually interesting background pattern
// Gradient base
const gradient = ctx.createLinearGradient(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
gradient.addColorStop(0, `hsla(${h}, ${s}%, ${l * 0.15}%, 1)`);
gradient.addColorStop(0.5, `hsla(${h}, ${s}%, ${l * 0.08}%, 1)`);
gradient.addColorStop(1, `hsla(${h}, ${s}%, ${l * 0.12}%, 1)`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// Draw circuit-like patterns
ctx.strokeStyle = `hsla(${h}, ${s}%, ${l}%, 0.15)`;
ctx.lineWidth = 1;
for (let i = 0; i < 12; i++) {
const startX = Math.random() * CANVAS_WIDTH;
const startY = Math.random() * CANVAS_HEIGHT;
ctx.beginPath();
ctx.moveTo(startX, startY);
// Draw horizontal then vertical line
const midX = startX + (Math.random() - 0.5) * 80;
ctx.lineTo(midX, startY);
ctx.lineTo(midX, startY + (Math.random() - 0.5) * 60);
ctx.stroke();
// Draw node dots
ctx.fillStyle = `hsla(${h}, ${s}%, ${l}%, 0.3)`;
ctx.beginPath();
ctx.arc(startX, startY, 2, 0, Math.PI * 2);
ctx.fill();
}
// Matrix-style falling characters
ctx.font = '11px monospace';
ctx.fillStyle = `hsla(${h}, ${s}%, ${l}%, 0.2)`;
const chars = '01アイウエオカキクケコ田由甲申電网';
for (let i = 0; i < 40; i++) {
const cx = Math.random() * CANVAS_WIDTH;
const cy = Math.random() * CANVAS_HEIGHT;
ctx.fillText(chars[Math.floor(Math.random() * chars.length)], cx, cy);
}
// Generate target position (where the piece should go)
const newTargetX = Math.floor(Math.random() * (CANVAS_WIDTH - PIECE_WIDTH - 100)) + 80;
const targetY = (CANVAS_HEIGHT - PIECE_HEIGHT) / 2;
setTargetX(newTargetX);
// Draw the target slot (dark cutout with glow)
ctx.save();
drawPuzzlePiecePath(ctx, newTargetX, targetY);
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
ctx.fill();
ctx.strokeStyle = `hsla(${h}, ${s}%, ${l}%, 0.5)`;
ctx.lineWidth = 1.5;
ctx.stroke();
// Inner shadow effect
ctx.shadowColor = `hsla(${h}, ${s}%, ${l}%, 0.3)`;
ctx.shadowBlur = 4;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.stroke();
ctx.restore();
// Extract the puzzle piece image from a slightly different position for variation
const extractX = 10;
const extractY = targetY;
// Create puzzle piece appearance
const pieceCanvas = document.createElement('canvas');
pieceCanvas.width = PIECE_WIDTH + NOTCH_SIZE + 4;
pieceCanvas.height = PIECE_HEIGHT + 4;
const pieceCtx = pieceCanvas.getContext('2d');
if (pieceCtx) {
// Draw piece background with pattern
pieceCtx.fillStyle = `hsla(${h}, ${s}%, ${l}%, 0.9)`;
drawPuzzlePiecePath(pieceCtx, 2, 2);
pieceCtx.fill();
// Add texture
pieceCtx.strokeStyle = `hsla(${h}, ${s}%, ${l * 1.3}%, 0.6)`;
pieceCtx.lineWidth = 1.5;
drawPuzzlePiecePath(pieceCtx, 2, 2);
pieceCtx.stroke();
// Inner pattern
pieceCtx.strokeStyle = 'rgba(0, 0, 0, 0.3)';
pieceCtx.lineWidth = 1;
pieceCtx.strokeRect(8, 8, PIECE_WIDTH - 12, PIECE_HEIGHT - 12);
// Arrow indicator
pieceCtx.fillStyle = 'rgba(0, 0, 0, 0.5)';
pieceCtx.beginPath();
const arrowCx = PIECE_WIDTH / 2 + 2;
const arrowCy = PIECE_HEIGHT / 2 + 2;
pieceCtx.moveTo(arrowCx - 6, arrowCy - 5);
pieceCtx.lineTo(arrowCx + 6, arrowCy);
pieceCtx.lineTo(arrowCx - 6, arrowCy + 5);
pieceCtx.closePath();
pieceCtx.fill();
setPuzzleImage(pieceCtx.getImageData(0, 0, pieceCanvas.width, pieceCanvas.height));
}
// Draw scanlines
ctx.fillStyle = 'rgba(0, 0, 0, 0.06)';
for (let y = 0; y < CANVAS_HEIGHT; y += 2) {
ctx.fillRect(0, y, CANVAS_WIDTH, 1);
}
// Border
ctx.strokeStyle = `hsla(${h}, ${s}%, ${l}%, 0.4)`;
ctx.lineWidth = 2;
ctx.strokeRect(1, 1, CANVAS_WIDTH - 2, CANVAS_HEIGHT - 2);
setSliderX(0); setSliderX(0);
setError(false); setError(false);
setIsVerified(false); setIsVerified(false);
@ -48,9 +194,20 @@ const HumanVerification = ({ onVerified }: HumanVerificationProps) => {
generatePuzzle(); generatePuzzle();
}, [generatePuzzle]); }, [generatePuzzle]);
// Draw the puzzle piece at current position
useEffect(() => { useEffect(() => {
if (!puzzle || !canvasRef.current) return; if (!canvasRef.current || !puzzleImage) return;
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
if (!ctx) return;
// Regenerate base image
generatePuzzle();
}, []);
// Redraw canvas with piece at current slider position
const redrawWithPiece = useCallback(() => {
if (!canvasRef.current || !puzzleImage) return;
const canvas = canvasRef.current; const canvas = canvasRef.current;
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
if (!ctx) return; if (!ctx) return;
@ -62,121 +219,118 @@ const HumanVerification = ({ onVerified }: HumanVerificationProps) => {
const h = parseFloat(hslParts[0]) || 120; const h = parseFloat(hslParts[0]) || 120;
const s = parseFloat(hslParts[1]) || 100; const s = parseFloat(hslParts[1]) || 100;
const l = parseFloat(hslParts[2]) || 50; const l = parseFloat(hslParts[2]) || 50;
const primaryColor = `hsl(${h}, ${s}%, ${l}%)`;
const dimColor = `hsla(${h}, ${s}%, ${l}%, 0.3)`;
const bgColor = `hsla(${h}, ${s}%, ${l}%, 0.05)`;
// Clear canvas with dark background // Clear and redraw background
ctx.fillStyle = '#0a0a0a'; ctx.fillStyle = '#0d0d0d';
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// Draw matrix grid const gradient = ctx.createLinearGradient(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
ctx.strokeStyle = dimColor; gradient.addColorStop(0, `hsla(${h}, ${s}%, ${l * 0.15}%, 1)`);
ctx.lineWidth = 0.5; gradient.addColorStop(0.5, `hsla(${h}, ${s}%, ${l * 0.08}%, 1)`);
for (let x = 0; x < CANVAS_WIDTH; x += 16) { gradient.addColorStop(1, `hsla(${h}, ${s}%, ${l * 0.12}%, 1)`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// Circuit patterns
ctx.strokeStyle = `hsla(${h}, ${s}%, ${l}%, 0.15)`;
ctx.lineWidth = 1;
for (let i = 0; i < 12; i++) {
const startX = (i * 37) % CANVAS_WIDTH;
const startY = (i * 23) % CANVAS_HEIGHT;
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(x, 0); ctx.moveTo(startX, startY);
ctx.lineTo(x, CANVAS_HEIGHT); ctx.lineTo(startX + 40, startY);
ctx.stroke(); ctx.lineTo(startX + 40, startY + 30);
}
for (let y = 0; y < CANVAS_HEIGHT; y += 16) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(CANVAS_WIDTH, y);
ctx.stroke(); ctx.stroke();
} }
// Draw random matrix code in background // Matrix chars
ctx.font = '10px monospace'; ctx.font = '11px monospace';
ctx.fillStyle = `hsla(${h}, ${s}%, ${l}%, 0.15)`; ctx.fillStyle = `hsla(${h}, ${s}%, ${l}%, 0.2)`;
const chars = '01アイウエオ田由甲申';
for (let i = 0; i < 30; i++) { for (let i = 0; i < 30; i++) {
const x = Math.random() * CANVAS_WIDTH; ctx.fillText(chars[i % chars.length], (i * 17) % CANVAS_WIDTH, (i * 13) % CANVAS_HEIGHT);
const y = Math.random() * CANVAS_HEIGHT;
const chars = '01アイウエオカキクケコサシスセソ';
ctx.fillText(chars[Math.floor(Math.random() * chars.length)], x, y);
} }
// Target slot position const pieceY = (CANVAS_HEIGHT - PIECE_HEIGHT) / 2;
const slotY = (CANVAS_HEIGHT - PIECE_SIZE) / 2;
// Draw target slot with glow // Draw target slot
ctx.shadowColor = primaryColor; ctx.save();
ctx.shadowBlur = 8; drawPuzzlePiecePath(ctx, targetX, pieceY);
ctx.fillStyle = bgColor; ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
ctx.strokeStyle = dimColor;
ctx.lineWidth = 2;
// Draw slot shape with puzzle notch
ctx.beginPath();
ctx.roundRect(puzzle.targetX, slotY, PIECE_SIZE, PIECE_SIZE, 4);
ctx.fill(); ctx.fill();
ctx.strokeStyle = `hsla(${h}, ${s}%, ${l}%, 0.5)`;
ctx.lineWidth = 1.5;
ctx.stroke(); ctx.stroke();
ctx.restore();
// Inner slot pattern // Calculate piece position from slider
ctx.shadowBlur = 0; const maxSlide = trackRef.current ? trackRef.current.clientWidth - 48 : 240;
ctx.strokeStyle = `hsla(${h}, ${s}%, ${l}%, 0.2)`; const pieceX = (sliderX / maxSlide) * (CANVAS_WIDTH - PIECE_WIDTH - NOTCH_SIZE - 10) + 5;
ctx.lineWidth = 1;
ctx.strokeRect(puzzle.targetX + 8, slotY + 8, PIECE_SIZE - 16, PIECE_SIZE - 16);
ctx.strokeRect(puzzle.targetX + 14, slotY + 14, PIECE_SIZE - 28, PIECE_SIZE - 28);
// Calculate piece position based on slider // Draw draggable piece with shadow/glow
const maxSlide = trackRef.current ? trackRef.current.clientWidth - 48 : 280; ctx.save();
const pieceX = (sliderX / maxSlide) * (CANVAS_WIDTH - PIECE_SIZE - 10) + 5;
const pieceY = slotY; let pieceColor = `hsla(${h}, ${s}%, ${l}%, 0.95)`;
let borderColor = `hsla(${h}, ${s}%, ${l * 1.2}%, 1)`;
let glowColor = `hsla(${h}, ${s}%, ${l}%, 0.6)`;
// Determine piece color
let pieceColor = primaryColor;
let pieceBorderColor = primaryColor;
if (error) { if (error) {
pieceColor = 'hsl(0, 70%, 50%)'; pieceColor = 'hsla(0, 70%, 50%, 0.95)';
pieceBorderColor = 'hsl(0, 70%, 60%)'; borderColor = 'hsl(0, 70%, 60%)';
glowColor = 'hsla(0, 70%, 50%, 0.6)';
} else if (isVerified) { } else if (isVerified) {
pieceColor = 'hsl(142, 76%, 36%)'; pieceColor = 'hsla(142, 76%, 40%, 0.95)';
pieceBorderColor = 'hsl(142, 76%, 50%)'; borderColor = 'hsl(142, 76%, 55%)';
glowColor = 'hsla(142, 76%, 40%, 0.6)';
} }
// Draw puzzle piece with glow // Glow effect
ctx.shadowColor = pieceBorderColor; ctx.shadowColor = glowColor;
ctx.shadowBlur = 12; ctx.shadowBlur = 12;
ctx.fillStyle = pieceColor;
ctx.strokeStyle = pieceBorderColor;
ctx.lineWidth = 2;
ctx.beginPath(); drawPuzzlePiecePath(ctx, pieceX, pieceY);
ctx.roundRect(pieceX, pieceY, PIECE_SIZE, PIECE_SIZE, 4); ctx.fillStyle = pieceColor;
ctx.fill(); ctx.fill();
ctx.strokeStyle = borderColor;
ctx.lineWidth = 2;
ctx.stroke(); ctx.stroke();
ctx.shadowBlur = 0; ctx.shadowBlur = 0;
// Draw inner pattern on piece // Inner pattern on piece
ctx.strokeStyle = 'rgba(0, 0, 0, 0.4)'; ctx.strokeStyle = 'rgba(0, 0, 0, 0.3)';
ctx.lineWidth = 1; ctx.lineWidth = 1;
ctx.strokeRect(pieceX + 8, pieceY + 8, PIECE_SIZE - 16, PIECE_SIZE - 16); ctx.strokeRect(pieceX + 6, pieceY + 6, PIECE_WIDTH - 12, PIECE_HEIGHT - 12);
// Draw arrow on piece // Arrow
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)'; ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.beginPath(); ctx.beginPath();
const arrowX = pieceX + PIECE_SIZE / 2; const arrowX = pieceX + PIECE_WIDTH / 2;
const arrowY = pieceY + PIECE_SIZE / 2; const arrowY = pieceY + PIECE_HEIGHT / 2;
ctx.moveTo(arrowX - 6, arrowY - 5); ctx.moveTo(arrowX - 5, arrowY - 4);
ctx.lineTo(arrowX + 6, arrowY); ctx.lineTo(arrowX + 5, arrowY);
ctx.lineTo(arrowX - 6, arrowY + 5); ctx.lineTo(arrowX - 5, arrowY + 4);
ctx.closePath(); ctx.closePath();
ctx.fill(); ctx.fill();
ctx.restore();
// Draw scanlines // Scanlines
ctx.fillStyle = 'rgba(0, 0, 0, 0.08)'; ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
for (let y = 0; y < CANVAS_HEIGHT; y += 2) { for (let y = 0; y < CANVAS_HEIGHT; y += 2) {
ctx.fillRect(0, y, CANVAS_WIDTH, 1); ctx.fillRect(0, y, CANVAS_WIDTH, 1);
} }
// Draw border // Border
ctx.strokeStyle = dimColor; ctx.strokeStyle = `hsla(${h}, ${s}%, ${l}%, 0.4)`;
ctx.lineWidth = 1; ctx.lineWidth = 2;
ctx.strokeRect(0.5, 0.5, CANVAS_WIDTH - 1, CANVAS_HEIGHT - 1); ctx.strokeRect(1, 1, CANVAS_WIDTH - 2, CANVAS_HEIGHT - 2);
}, [puzzle, sliderX, error, isVerified]); }, [sliderX, error, isVerified, targetX, puzzleImage]);
useEffect(() => {
redrawWithPiece();
}, [sliderX, error, isVerified, redrawWithPiece]);
const handleMouseDown = () => { const handleMouseDown = () => {
setIsDragging(true); setIsDragging(true);
@ -191,23 +345,23 @@ const HumanVerification = ({ onVerified }: HumanVerificationProps) => {
}; };
const handleMouseUp = () => { const handleMouseUp = () => {
if (!isDragging || !puzzle || !trackRef.current) return; if (!isDragging || !trackRef.current) return;
setIsDragging(false); setIsDragging(false);
const maxSlide = trackRef.current.clientWidth - 48; const maxSlide = trackRef.current.clientWidth - 48;
const pieceX = (sliderX / maxSlide) * (CANVAS_WIDTH - PIECE_SIZE - 10) + 5; const pieceX = (sliderX / maxSlide) * (CANVAS_WIDTH - PIECE_WIDTH - NOTCH_SIZE - 10) + 5;
const diff = Math.abs(pieceX - puzzle.targetX); const diff = Math.abs(pieceX - targetX);
if (diff <= TOLERANCE) { if (diff <= TOLERANCE) {
setIsVerified(true); setIsVerified(true);
localStorage.setItem(VERIFIED_KEY, 'true'); localStorage.setItem(VERIFIED_KEY, 'true');
setTimeout(() => onVerified(), 600); setTimeout(() => onVerified(), 500);
} else { } else {
setError(true); setError(true);
setTimeout(() => { setTimeout(() => {
setSliderX(0); setSliderX(0);
setError(false); setError(false);
}, 400); }, 350);
} }
}; };
@ -229,40 +383,35 @@ const HumanVerification = ({ onVerified }: HumanVerificationProps) => {
<motion.div <motion.div
initial={{ opacity: 0, scale: 0.95 }} initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }} animate={{ opacity: 1, scale: 1 }}
className="w-full max-w-sm space-y-4" className="w-full max-w-[340px] space-y-3"
> >
{/* Header */} {/* Header */}
<div className="text-center border border-primary/40 bg-primary/5 py-3 px-4"> <div className="text-center border border-primary/40 bg-primary/5 py-2.5 px-4">
<div className="text-primary font-mono text-sm tracking-wider font-bold"> <div className="text-primary font-mono text-sm tracking-wider font-bold">
SECURITY VERIFICATION v3.0 SECURITY VERIFICATION
</div> </div>
<div className="text-primary/60 font-mono text-xs"> <div className="text-primary/50 font-mono text-[10px]">
ANTI-BOT PROTOCOL ANTI-BOT PROTOCOL v3.0
</div> </div>
</div> </div>
{/* Instructions */}
<div className="text-primary font-mono text-sm">
{'>'} Slide the piece to complete the puzzle
</div>
{/* Canvas puzzle area */} {/* Canvas puzzle area */}
<div className="border border-primary/40 bg-background overflow-hidden relative"> <div className="border border-primary/30 overflow-hidden">
<canvas <canvas
ref={canvasRef} ref={canvasRef}
width={CANVAS_WIDTH} width={CANVAS_WIDTH}
height={CANVAS_HEIGHT} height={CANVAS_HEIGHT}
className="w-full" className="w-full block"
/> />
</div> </div>
{/* Slider track */} {/* Slider track */}
<div <div
ref={trackRef} ref={trackRef}
className={`relative h-12 border-2 ${ className={`relative h-11 border-2 ${
error ? 'border-destructive bg-destructive/10' : error ? 'border-destructive bg-destructive/10' :
isVerified ? 'border-green-500 bg-green-500/10' : isVerified ? 'border-green-500 bg-green-500/10' :
'border-primary/40 bg-primary/5' 'border-primary/30 bg-primary/5'
} transition-colors duration-200 cursor-pointer select-none`} } transition-colors duration-200 cursor-pointer select-none`}
onMouseMove={handleMouseMove} onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp} onMouseUp={handleMouseUp}
@ -272,10 +421,10 @@ const HumanVerification = ({ onVerified }: HumanVerificationProps) => {
> >
{/* Track label */} {/* Track label */}
<div className="absolute inset-0 flex items-center justify-center pointer-events-none"> <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
<span className={`font-mono text-xs ${ <span className={`font-mono text-xs tracking-wide ${
isVerified ? 'text-green-500' : 'text-primary/40' isVerified ? 'text-green-500' : 'text-primary/30'
}`}> }`}>
{isVerified ? '[ ACCESS GRANTED ]' : '>>> SLIDE TO VERIFY >>>'} {isVerified ? '[ VERIFIED ]' : 'DRAG TO COMPLETE PUZZLE'}
</span> </span>
</div> </div>
@ -290,28 +439,22 @@ const HumanVerification = ({ onVerified }: HumanVerificationProps) => {
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
onTouchStart={handleTouchStart} onTouchStart={handleTouchStart}
animate={error ? { x: [0, -4, 4, -4, 4, 0] } : {}} animate={error ? { x: [0, -4, 4, -4, 4, 0] } : {}}
transition={{ duration: 0.25 }} transition={{ duration: 0.2 }}
> >
<ChevronRight className="w-5 h-5 text-background" /> <ChevronRight className="w-5 h-5 text-background" />
</motion.div> </motion.div>
</div> </div>
{/* Refresh */} {/* Controls */}
<div className="flex justify-between items-center text-xs font-mono text-muted-foreground"> <div className="flex justify-end">
<span>Protocol: SLIDER-VERIFY-3.0</span>
<button <button
onClick={generatePuzzle} onClick={generatePuzzle}
className="flex items-center gap-1 text-primary hover:text-primary/80 transition-colors" className="flex items-center gap-1.5 text-xs font-mono text-primary/60 hover:text-primary transition-colors"
> >
<RefreshCw className="w-3 h-3" /> <RefreshCw className="w-3 h-3" />
Refresh Refresh
</button> </button>
</div> </div>
{/* Footer */}
<div className="text-center text-muted-foreground/50 font-mono text-xs">
// This verification helps protect against automated access
</div>
</motion.div> </motion.div>
</div> </div>
); );

View File

@ -77,7 +77,7 @@ const Sidebar = () => {
</button> </button>
{/* Desktop Navigation - Always visible */} {/* Desktop Navigation - Always visible */}
<nav ref={navRef} className="hidden md:block flex-grow overflow-y-auto p-4 md:p-5"> <nav ref={navRef} className="hidden md:flex flex-col flex-grow p-4 md:p-5">
{navItems.map((item, index) => { {navItems.map((item, index) => {
const isActive = location.pathname === item.path; const isActive = location.pathname === item.path;
@ -108,9 +108,9 @@ const Sidebar = () => {
); );
})} })}
{/* Inline footer when sidebar is too short */} {/* Inline footer when sidebar is too short - folded into nav */}
{!showFooter && ( {!showFooter && (
<div className="mt-4 pt-4 border-t border-primary/30"> <div className="mt-auto pt-4 border-t border-primary/30">
<p className="font-pixel text-sm text-primary text-glow text-center"> <p className="font-pixel text-sm text-primary text-glow text-center">
Access Granted Access Granted
</p> </p>

View File

@ -1,318 +0,0 @@
import { useState, useRef, useEffect, useCallback } from 'react';
import { motion } from 'framer-motion';
import { RefreshCw, ChevronRight } from 'lucide-react';
interface SliderVerificationProps {
onVerified: () => void;
}
interface PuzzlePiece {
targetX: number;
currentX: number;
}
export const SliderVerification = ({ onVerified }: SliderVerificationProps) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [puzzle, setPuzzle] = useState<PuzzlePiece | null>(null);
const [isDragging, setIsDragging] = useState(false);
const [sliderX, setSliderX] = useState(0);
const [isVerified, setIsVerified] = useState(false);
const [error, setError] = useState(false);
const sliderRef = useRef<HTMLDivElement>(null);
const trackRef = useRef<HTMLDivElement>(null);
const CANVAS_WIDTH = 320;
const CANVAS_HEIGHT = 180;
const PIECE_SIZE = 50;
const TOLERANCE = 8;
const generatePuzzle = useCallback(() => {
const targetX = Math.floor(Math.random() * (CANVAS_WIDTH - PIECE_SIZE - 80)) + 60;
setPuzzle({ targetX, currentX: 0 });
setSliderX(0);
setError(false);
setIsVerified(false);
}, []);
useEffect(() => {
generatePuzzle();
}, [generatePuzzle]);
useEffect(() => {
if (!puzzle || !canvasRef.current) return;
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
if (!ctx) return;
// Clear canvas
ctx.fillStyle = 'hsl(var(--background))';
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// Draw matrix-style grid pattern
ctx.strokeStyle = 'hsl(var(--primary) / 0.1)';
ctx.lineWidth = 1;
for (let x = 0; x < CANVAS_WIDTH; x += 20) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, CANVAS_HEIGHT);
ctx.stroke();
}
for (let y = 0; y < CANVAS_HEIGHT; y += 20) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(CANVAS_WIDTH, y);
ctx.stroke();
}
// Draw random "code" elements
ctx.fillStyle = 'hsl(var(--primary) / 0.15)';
ctx.font = '10px monospace';
for (let i = 0; i < 15; i++) {
const x = Math.random() * CANVAS_WIDTH;
const y = Math.random() * CANVAS_HEIGHT;
const chars = ['0', '1', 'A', 'F', 'X', '#', '@'];
ctx.fillText(chars[Math.floor(Math.random() * chars.length)], x, y);
}
// Draw target slot (where puzzle piece should go)
const slotY = (CANVAS_HEIGHT - PIECE_SIZE) / 2;
ctx.fillStyle = 'hsl(var(--primary) / 0.2)';
ctx.strokeStyle = 'hsl(var(--primary) / 0.5)';
ctx.lineWidth = 2;
// Draw puzzle slot with notch
ctx.beginPath();
ctx.moveTo(puzzle.targetX, slotY);
ctx.lineTo(puzzle.targetX + PIECE_SIZE, slotY);
ctx.lineTo(puzzle.targetX + PIECE_SIZE, slotY + PIECE_SIZE);
ctx.lineTo(puzzle.targetX, slotY + PIECE_SIZE);
ctx.closePath();
ctx.fill();
ctx.stroke();
// Draw inner pattern for slot
ctx.strokeStyle = 'hsl(var(--primary) / 0.3)';
ctx.lineWidth = 1;
for (let i = 0; i < 3; i++) {
const offset = i * 12 + 8;
ctx.strokeRect(
puzzle.targetX + offset / 2,
slotY + offset / 2,
PIECE_SIZE - offset,
PIECE_SIZE - offset
);
}
// Draw the draggable puzzle piece at current position
const pieceX = sliderX * (CANVAS_WIDTH - PIECE_SIZE) / (trackRef.current?.clientWidth || 280);
const pieceY = slotY;
// Piece shadow/glow
ctx.shadowColor = 'hsl(var(--primary))';
ctx.shadowBlur = 10;
ctx.fillStyle = error
? 'hsl(var(--destructive) / 0.8)'
: isVerified
? 'hsl(142 76% 36% / 0.8)'
: 'hsl(var(--primary) / 0.8)';
ctx.strokeStyle = error
? 'hsl(var(--destructive))'
: isVerified
? 'hsl(142 76% 36%)'
: 'hsl(var(--primary))';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(pieceX, pieceY);
ctx.lineTo(pieceX + PIECE_SIZE, pieceY);
ctx.lineTo(pieceX + PIECE_SIZE, pieceY + PIECE_SIZE);
ctx.lineTo(pieceX, pieceY + PIECE_SIZE);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.shadowBlur = 0;
// Draw inner pattern for piece
ctx.strokeStyle = 'hsl(var(--background) / 0.5)';
ctx.lineWidth = 1;
for (let i = 0; i < 3; i++) {
const offset = i * 12 + 8;
ctx.strokeRect(
pieceX + offset / 2,
pieceY + offset / 2,
PIECE_SIZE - offset,
PIECE_SIZE - offset
);
}
// Draw arrow indicator on piece
ctx.fillStyle = 'hsl(var(--background))';
ctx.beginPath();
const arrowX = pieceX + PIECE_SIZE / 2;
const arrowY = pieceY + PIECE_SIZE / 2;
ctx.moveTo(arrowX - 8, arrowY - 5);
ctx.lineTo(arrowX + 5, arrowY);
ctx.lineTo(arrowX - 8, arrowY + 5);
ctx.closePath();
ctx.fill();
}, [puzzle, sliderX, error, isVerified]);
const handleMouseDown = (e: React.MouseEvent) => {
setIsDragging(true);
setError(false);
};
const handleMouseMove = (e: React.MouseEvent) => {
if (!isDragging || !trackRef.current) return;
const rect = trackRef.current.getBoundingClientRect();
const x = Math.max(0, Math.min(e.clientX - rect.left - 20, rect.width - 40));
setSliderX(x);
};
const handleMouseUp = () => {
if (!isDragging || !puzzle || !trackRef.current) return;
setIsDragging(false);
// Calculate if piece is in correct position
const pieceX = sliderX * (CANVAS_WIDTH - PIECE_SIZE) / (trackRef.current.clientWidth - 40);
const diff = Math.abs(pieceX - puzzle.targetX);
if (diff <= TOLERANCE) {
setIsVerified(true);
setTimeout(() => {
onVerified();
}, 800);
} else {
setError(true);
setTimeout(() => {
setSliderX(0);
setError(false);
}, 500);
}
};
const handleTouchStart = (e: React.TouchEvent) => {
setIsDragging(true);
setError(false);
};
const handleTouchMove = (e: React.TouchEvent) => {
if (!isDragging || !trackRef.current) return;
const touch = e.touches[0];
const rect = trackRef.current.getBoundingClientRect();
const x = Math.max(0, Math.min(touch.clientX - rect.left - 20, rect.width - 40));
setSliderX(x);
};
const handleTouchEnd = () => {
handleMouseUp();
};
return (
<div className="fixed inset-0 z-50 bg-background flex items-center justify-center p-4">
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
className="w-full max-w-md space-y-4"
>
{/* Header */}
<div className="text-center border border-primary/30 bg-primary/5 py-3 px-4">
<div className="text-primary font-mono text-sm tracking-wider">
SECURITY VERIFICATION v3.0
</div>
<div className="text-primary/60 font-mono text-xs">
ANTI-BOT PROTOCOL
</div>
</div>
{/* Instructions */}
<div className="text-primary font-mono text-sm">
{'>'} Slide the puzzle piece to complete verification
</div>
{/* Canvas puzzle area */}
<div className="border border-primary/40 bg-background p-1 relative overflow-hidden">
<canvas
ref={canvasRef}
width={CANVAS_WIDTH}
height={CANVAS_HEIGHT}
className="w-full"
style={{ imageRendering: 'pixelated' }}
/>
{/* Scanline effect */}
<div
className="absolute inset-0 pointer-events-none opacity-20"
style={{
background: 'repeating-linear-gradient(0deg, transparent, transparent 2px, hsl(var(--primary) / 0.03) 2px, hsl(var(--primary) / 0.03) 4px)'
}}
/>
</div>
{/* Slider track */}
<div
ref={trackRef}
className={`relative h-12 border-2 ${
error ? 'border-destructive bg-destructive/10' :
isVerified ? 'border-green-500 bg-green-500/10' :
'border-primary/40 bg-primary/5'
} transition-colors duration-200 cursor-pointer select-none`}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
>
{/* Track label */}
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
<span className={`font-mono text-sm ${
isVerified ? 'text-green-500' : 'text-primary/40'
}`}>
{isVerified ? '[ VERIFIED ]' : '>>> SLIDE TO VERIFY >>>'}
</span>
</div>
{/* Slider handle */}
<motion.div
ref={sliderRef}
className={`absolute top-0 h-full w-12 flex items-center justify-center cursor-grab active:cursor-grabbing ${
error ? 'bg-destructive' :
isVerified ? 'bg-green-500' :
'bg-primary'
} transition-colors duration-200`}
style={{ left: sliderX }}
onMouseDown={handleMouseDown}
onTouchStart={handleTouchStart}
animate={error ? { x: [0, -5, 5, -5, 5, 0] } : {}}
transition={{ duration: 0.3 }}
>
<ChevronRight className="w-6 h-6 text-background" />
</motion.div>
</div>
{/* Refresh button */}
<div className="flex justify-between items-center text-xs font-mono text-muted-foreground">
<span>Protocol: SLIDER-VERIFY-3.0</span>
<button
onClick={generatePuzzle}
className="flex items-center gap-1 text-primary hover:text-primary/80 transition-colors"
>
<RefreshCw className="w-3 h-3" />
Refresh
</button>
</div>
{/* Footer */}
<div className="text-center text-muted-foreground/50 font-mono text-xs">
// This verification helps protect against automated access
</div>
</motion.div>
</div>
);
};