mirror of
https://github.com/JorySeverijnse/ui-fixer-supreme.git
synced 2026-01-29 17:58:38 +00:00
264 lines
8.8 KiB
TypeScript
264 lines
8.8 KiB
TypeScript
import { useState, useEffect, useRef } from 'react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
|
|
const BYPASS_KEY = 'bypass-human-check';
|
|
const VERIFIED_KEY = 'human-verified';
|
|
|
|
interface HumanVerificationProps {
|
|
onVerified: () => void;
|
|
}
|
|
|
|
// Generate a simple math equation
|
|
const generateEquation = () => {
|
|
const operators = ['+', '-', '*'] as const;
|
|
const operator = operators[Math.floor(Math.random() * operators.length)];
|
|
|
|
let a: number, b: number, answer: number;
|
|
|
|
switch (operator) {
|
|
case '+':
|
|
a = Math.floor(Math.random() * 50) + 1;
|
|
b = Math.floor(Math.random() * 50) + 1;
|
|
answer = a + b;
|
|
break;
|
|
case '-':
|
|
a = Math.floor(Math.random() * 50) + 20;
|
|
b = Math.floor(Math.random() * (a - 1)) + 1;
|
|
answer = a - b;
|
|
break;
|
|
case '*':
|
|
a = Math.floor(Math.random() * 12) + 2;
|
|
b = Math.floor(Math.random() * 12) + 2;
|
|
answer = a * b;
|
|
break;
|
|
}
|
|
|
|
return { equation: `${a} ${operator} ${b}`, answer };
|
|
};
|
|
|
|
const HumanVerification = ({ onVerified }: HumanVerificationProps) => {
|
|
const [{ equation, answer }, setEquation] = useState(generateEquation);
|
|
const [userAnswer, setUserAnswer] = useState('');
|
|
const [error, setError] = useState(false);
|
|
const [attempts, setAttempts] = useState(0);
|
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
|
|
// Check for bypass in URL
|
|
useEffect(() => {
|
|
const params = new URLSearchParams(window.location.search);
|
|
if (params.has(BYPASS_KEY) || window.location.pathname.includes(BYPASS_KEY)) {
|
|
localStorage.setItem(VERIFIED_KEY, 'true');
|
|
onVerified();
|
|
}
|
|
}, [onVerified]);
|
|
|
|
// Draw equation on canvas for added security
|
|
useEffect(() => {
|
|
const canvas = canvasRef.current;
|
|
if (!canvas) return;
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
if (!ctx) return;
|
|
|
|
// Get computed styles for theming
|
|
const computedStyle = getComputedStyle(document.documentElement);
|
|
const primaryColor = computedStyle.getPropertyValue('--primary').trim();
|
|
const hslMatch = primaryColor.match(/[\d.]+/g);
|
|
const primaryRGB = hslMatch
|
|
? `hsl(${hslMatch[0]}, ${hslMatch[1]}%, ${hslMatch[2]}%)`
|
|
: '#00ff00';
|
|
|
|
// Clear canvas
|
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
|
|
// Draw grid lines for terminal feel
|
|
ctx.strokeStyle = 'rgba(0, 255, 0, 0.1)';
|
|
ctx.lineWidth = 1;
|
|
for (let i = 0; i < canvas.width; i += 20) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(i, 0);
|
|
ctx.lineTo(i, canvas.height);
|
|
ctx.stroke();
|
|
}
|
|
for (let i = 0; i < canvas.height; i += 20) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, i);
|
|
ctx.lineTo(canvas.width, i);
|
|
ctx.stroke();
|
|
}
|
|
|
|
// Draw border
|
|
ctx.strokeStyle = primaryRGB;
|
|
ctx.lineWidth = 2;
|
|
ctx.strokeRect(2, 2, canvas.width - 4, canvas.height - 4);
|
|
|
|
// Draw equation with slight random positioning for anti-bot
|
|
const offsetX = Math.random() * 10 - 5;
|
|
const offsetY = Math.random() * 6 - 3;
|
|
|
|
ctx.font = 'bold 48px monospace';
|
|
ctx.fillStyle = primaryRGB;
|
|
ctx.textAlign = 'center';
|
|
ctx.textBaseline = 'middle';
|
|
|
|
// Add subtle noise/distortion
|
|
const chars = equation.split('');
|
|
let xPos = canvas.width / 2 - (chars.length * 15) + offsetX;
|
|
|
|
chars.forEach((char) => {
|
|
const yOffset = Math.random() * 4 - 2;
|
|
ctx.fillText(char, xPos, canvas.height / 2 + offsetY + yOffset);
|
|
xPos += 30;
|
|
});
|
|
|
|
// Draw "= ?" at the end
|
|
ctx.fillText('= ?', xPos + 20, canvas.height / 2 + offsetY);
|
|
|
|
// Add scanline effect
|
|
for (let y = 0; y < canvas.height; y += 3) {
|
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.15)';
|
|
ctx.fillRect(0, y, canvas.width, 1);
|
|
}
|
|
}, [equation]);
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
const parsed = parseInt(userAnswer, 10);
|
|
|
|
if (!isNaN(parsed) && parsed === answer) {
|
|
localStorage.setItem(VERIFIED_KEY, 'true');
|
|
onVerified();
|
|
} else {
|
|
setError(true);
|
|
setAttempts(prev => prev + 1);
|
|
setTimeout(() => {
|
|
setError(false);
|
|
// Generate new equation after 3 failed attempts
|
|
if (attempts >= 2) {
|
|
setEquation(generateEquation());
|
|
setAttempts(0);
|
|
}
|
|
}, 600);
|
|
setUserAnswer('');
|
|
}
|
|
};
|
|
|
|
const handleNewEquation = () => {
|
|
setEquation(generateEquation());
|
|
setUserAnswer('');
|
|
setError(false);
|
|
};
|
|
|
|
return (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
className="fixed inset-0 z-[100] bg-background flex items-center justify-center p-4"
|
|
>
|
|
<div className="max-w-lg w-full">
|
|
<motion.div
|
|
initial={{ scale: 0.9, opacity: 0 }}
|
|
animate={{ scale: 1, opacity: 1 }}
|
|
transition={{ delay: 0.2 }}
|
|
className="border-2 border-primary box-glow p-6 bg-background"
|
|
>
|
|
{/* Header */}
|
|
<div className="text-center mb-6">
|
|
<pre className="font-mono text-[10px] sm:text-xs text-primary leading-tight mb-4">
|
|
{`┌─────────────────────────────────────┐
|
|
│ SECURITY VERIFICATION v2.0 │
|
|
│ ANTI-BOT PROTOCOL │
|
|
└─────────────────────────────────────┘`}
|
|
</pre>
|
|
<p className="font-mono text-sm text-foreground/70">
|
|
{'>'} Solve the equation to verify humanity
|
|
</p>
|
|
</div>
|
|
|
|
{/* Canvas with equation */}
|
|
<div className="mb-6 flex justify-center">
|
|
<canvas
|
|
ref={canvasRef}
|
|
width={320}
|
|
height={100}
|
|
className="border border-primary/30 rounded"
|
|
/>
|
|
</div>
|
|
|
|
{/* Input form */}
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<div className="relative">
|
|
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-primary font-mono">
|
|
{'>'}_
|
|
</span>
|
|
<input
|
|
type="text"
|
|
inputMode="numeric"
|
|
pattern="-?[0-9]*"
|
|
value={userAnswer}
|
|
onChange={(e) => setUserAnswer(e.target.value.replace(/[^0-9-]/g, ''))}
|
|
placeholder="Enter answer"
|
|
autoFocus
|
|
className={`w-full bg-background border-2 ${
|
|
error ? 'border-destructive animate-pulse' : 'border-primary/50'
|
|
} p-3 pl-12 font-mono text-lg text-foreground placeholder:text-foreground/30 focus:outline-none focus:border-primary transition-colors`}
|
|
/>
|
|
</div>
|
|
|
|
<AnimatePresence>
|
|
{error && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: -10 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0 }}
|
|
className="font-mono text-sm text-destructive flex items-center gap-2"
|
|
>
|
|
<span>✗</span>
|
|
<span>INCORRECT - {3 - attempts} attempts remaining</span>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
|
|
<div className="flex gap-3">
|
|
<button
|
|
type="submit"
|
|
disabled={!userAnswer}
|
|
className="flex-1 font-minecraft text-sm py-3 border-2 border-primary bg-primary/20 text-primary hover:bg-primary/40 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-300 box-glow"
|
|
>
|
|
[VERIFY]
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={handleNewEquation}
|
|
className="px-4 py-3 border-2 border-primary/50 text-primary/70 hover:border-primary hover:text-primary transition-all font-mono text-sm"
|
|
>
|
|
↻
|
|
</button>
|
|
</div>
|
|
</form>
|
|
|
|
{/* Footer */}
|
|
<div className="mt-6 pt-4 border-t border-primary/20 space-y-2">
|
|
<div className="flex justify-between text-[10px] font-mono text-foreground/40">
|
|
<span>Protocol: MATH-VERIFY-2.0</span>
|
|
<span>Encryption: ACTIVE</span>
|
|
</div>
|
|
<p className="font-mono text-[10px] text-foreground/30 text-center">
|
|
// This verification helps protect against automated access
|
|
</p>
|
|
</div>
|
|
</motion.div>
|
|
|
|
{/* Terminal decoration */}
|
|
<div className="mt-4 font-mono text-[10px] text-primary/50 space-y-1">
|
|
<p>{'>'} Awaiting verification input...</p>
|
|
<p className="animate-pulse">{'>'} _</p>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
};
|
|
|
|
export default HumanVerification;
|
|
export { VERIFIED_KEY, BYPASS_KEY }; |