This commit is contained in:
gpt-engineer-app[bot] 2025-12-08 12:19:53 +00:00
parent 4a2490a443
commit 6c6a9cf4ad
5 changed files with 324 additions and 86 deletions

View File

@ -1,25 +1,5 @@
import { useState, useEffect } from 'react'; import { useState, useEffect, useRef } from 'react';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import GlitchText from './GlitchText';
// Hacker-themed questions that any human would know
const QUESTIONS = [
{ q: "What key do you press to open a terminal? (Hint: between D and G)", answers: ["f", "f key"] },
{ q: "Complete: The Matrix has you, ___", answers: ["neo"] },
{ q: "What does 'www' stand for in a URL?", answers: ["world wide web", "worldwideweb"] },
{ q: "What color is the Matrix rain?", answers: ["green"] },
{ q: "What does 'ctrl+c' do?", answers: ["copy", "cancel", "stop", "interrupt"] },
{ q: "What number comes after 0 in binary?", answers: ["1", "one"] },
{ q: "What does 'IP' stand for in networking?", answers: ["internet protocol"] },
{ q: "What key exits most programs? (3 letters)", answers: ["esc", "escape"] },
{ q: "Red pill or blue pill - which reveals the truth?", answers: ["red", "red pill"] },
{ q: "What symbol starts most terminal commands?", answers: ["$", "/", ">", "dollar", "slash"] },
{ q: "What does 'USB' stand for?", answers: ["universal serial bus"] },
{ q: "What is localhost's IP address?", answers: ["127.0.0.1", "localhost"] },
{ q: "Complete: Hello, _____ (classic first program output)", answers: ["world"] },
{ q: "What animal is Linux's mascot?", answers: ["penguin", "tux"] },
{ q: "What does 'CPU' stand for?", answers: ["central processing unit"] },
];
const BYPASS_KEY = 'bypass-human-check'; const BYPASS_KEY = 'bypass-human-check';
const VERIFIED_KEY = 'human-verified'; const VERIFIED_KEY = 'human-verified';
@ -28,11 +8,40 @@ interface HumanVerificationProps {
onVerified: () => void; 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 HumanVerification = ({ onVerified }: HumanVerificationProps) => {
const [question] = useState(() => QUESTIONS[Math.floor(Math.random() * QUESTIONS.length)]); const [{ equation, answer }, setEquation] = useState(generateEquation);
const [answer, setAnswer] = useState(''); const [userAnswer, setUserAnswer] = useState('');
const [error, setError] = useState(false); const [error, setError] = useState(false);
const [showHint, setShowHint] = useState(false); const [attempts, setAttempts] = useState(0);
const canvasRef = useRef<HTMLCanvasElement>(null);
// Check for bypass in URL // Check for bypass in URL
useEffect(() => { useEffect(() => {
@ -43,18 +52,102 @@ const HumanVerification = ({ onVerified }: HumanVerificationProps) => {
} }
}, [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) => { const handleSubmit = (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
const normalizedAnswer = answer.toLowerCase().trim(); const parsed = parseInt(userAnswer, 10);
if (question.answers.some(a => normalizedAnswer === a.toLowerCase())) { if (!isNaN(parsed) && parsed === answer) {
localStorage.setItem(VERIFIED_KEY, 'true'); localStorage.setItem(VERIFIED_KEY, 'true');
onVerified(); onVerified();
} else { } else {
setError(true); setError(true);
setShowHint(true); setAttempts(prev => prev + 1);
setTimeout(() => setError(false), 500); 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 ( return (
@ -63,81 +156,104 @@ const HumanVerification = ({ onVerified }: HumanVerificationProps) => {
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
className="fixed inset-0 z-[100] bg-background flex items-center justify-center p-4" className="fixed inset-0 z-[100] bg-background flex items-center justify-center p-4"
> >
<div className="max-w-md w-full"> <div className="max-w-lg w-full">
<motion.div <motion.div
initial={{ scale: 0.9, opacity: 0 }} initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }} animate={{ scale: 1, opacity: 1 }}
transition={{ delay: 0.2 }} transition={{ delay: 0.2 }}
className="border-2 border-primary box-glow p-6 bg-background" className="border-2 border-primary box-glow p-6 bg-background"
> >
{/* ASCII Art Header */} {/* Header */}
<pre className="font-mono text-[8px] sm:text-[10px] text-primary text-center mb-4 leading-tight">
{`╔═══════════════════════════════════════╗
HUMAN VERIFICATION REQUIRED
`}
</pre>
<div className="text-center mb-6"> <div className="text-center mb-6">
<GlitchText <pre className="font-mono text-[10px] sm:text-xs text-primary leading-tight mb-4">
text="ACCESS CONTROL" {`┌─────────────────────────────────────┐
className="font-minecraft text-xl text-primary text-glow-strong" SECURITY VERIFICATION v2.0
/> ANTI-BOT PROTOCOL
<p className="font-pixel text-xs text-foreground/60 mt-2"> `}
Prove you are human to continue </pre>
<p className="font-mono text-sm text-foreground/70">
{'>'} Solve the equation to verify humanity
</p> </p>
</div> </div>
<form onSubmit={handleSubmit} className="space-y-4"> {/* Canvas with equation */}
<div className="border border-primary/30 p-4 bg-background/50"> <div className="mb-6 flex justify-center">
<p className="font-pixel text-[10px] text-foreground/60 mb-2">&gt; QUERY:</p> <canvas
<p className="font-minecraft text-sm text-primary">{question.q}</p> ref={canvasRef}
width={320}
height={100}
className="border border-primary/30 rounded"
/>
</div> </div>
<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 <input
type="text" type="text"
value={answer} inputMode="numeric"
onChange={(e) => setAnswer(e.target.value)} pattern="-?[0-9]*"
placeholder="&gt; Enter response..." value={userAnswer}
onChange={(e) => setUserAnswer(e.target.value.replace(/[^0-9-]/g, ''))}
placeholder="Enter answer"
autoFocus autoFocus
className={`w-full bg-background border-2 ${ className={`w-full bg-background border-2 ${
error ? 'border-destructive animate-pulse' : 'border-primary/50' error ? 'border-destructive animate-pulse' : 'border-primary/50'
} p-3 font-pixel text-sm text-foreground placeholder:text-foreground/30 focus:outline-none focus:border-primary transition-colors`} } p-3 pl-12 font-mono text-lg text-foreground placeholder:text-foreground/30 focus:outline-none focus:border-primary transition-colors`}
/> />
</div> </div>
<AnimatePresence> <AnimatePresence>
{showHint && ( {error && (
<motion.p <motion.div
initial={{ opacity: 0, y: -10 }} initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
className="font-pixel text-[10px] text-destructive" className="font-mono text-sm text-destructive flex items-center gap-2"
> >
ERROR: Invalid response. Retry required. <span></span>
</motion.p> <span>INCORRECT - {3 - attempts} attempts remaining</span>
</motion.div>
)} )}
</AnimatePresence> </AnimatePresence>
<div className="flex gap-3">
<button <button
type="submit" type="submit"
className="w-full font-minecraft text-sm py-3 border-2 border-primary bg-primary/20 text-primary hover:bg-primary/40 transition-all duration-300 box-glow" 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"
> >
EXECUTE [VERIFY]
</button> </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> </form>
<div className="mt-6 pt-4 border-t border-primary/20"> {/* Footer */}
<p className="font-pixel text-[8px] text-foreground/30 text-center"> <div className="mt-6 pt-4 border-t border-primary/20 space-y-2">
// Anti-bot verification protocol v1.0 <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> </p>
</div> </div>
</motion.div> </motion.div>
{/* Decorative terminal lines */} {/* Terminal decoration */}
<div className="mt-4 font-mono text-[10px] text-primary/50"> <div className="mt-4 font-mono text-[10px] text-primary/50 space-y-1">
<p>&gt; Awaiting human verification...</p> <p>{'>'} Awaiting verification input...</p>
<p className="animate-pulse">&gt; _</p> <p className="animate-pulse">{'>'} _</p>
</div> </div>
</div> </div>
</motion.div> </motion.div>

View File

@ -11,12 +11,27 @@ export interface Achievement {
secret?: boolean; secret?: boolean;
} }
// Track AI message count in localStorage
const AI_MESSAGE_COUNT_KEY = 'ai-message-count';
export const incrementAiMessageCount = (): number => {
const current = parseInt(localStorage.getItem(AI_MESSAGE_COUNT_KEY) || '0', 10);
const newCount = current + 1;
localStorage.setItem(AI_MESSAGE_COUNT_KEY, newCount.toString());
return newCount;
};
export const getAiMessageCount = (): number => {
return parseInt(localStorage.getItem(AI_MESSAGE_COUNT_KEY) || '0', 10);
};
interface AchievementsContextType { interface AchievementsContextType {
achievements: Achievement[]; achievements: Achievement[];
unlockAchievement: (id: string) => void; unlockAchievement: (id: string) => void;
getUnlockedCount: () => number; getUnlockedCount: () => number;
getTotalCount: () => number; getTotalCount: () => number;
timeOnSite: number; timeOnSite: number;
checkAiAchievements: () => void;
} }
const defaultAchievements: Achievement[] = [ const defaultAchievements: Achievement[] = [
@ -214,6 +229,13 @@ export const AchievementsProvider = ({ children }: { children: ReactNode }) => {
return achievements.length; return achievements.length;
}, [achievements]); }, [achievements]);
// Check AI message achievements
const checkAiAchievements = useCallback(() => {
const count = getAiMessageCount();
if (count >= 5) unlockAchievement('ai_conversation');
if (count >= 20) unlockAchievement('ai_long_chat');
}, [unlockAchievement]);
// Save achievements // Save achievements
useEffect(() => { useEffect(() => {
localStorage.setItem('achievements', JSON.stringify(achievements)); localStorage.setItem('achievements', JSON.stringify(achievements));
@ -227,6 +249,7 @@ export const AchievementsProvider = ({ children }: { children: ReactNode }) => {
getUnlockedCount, getUnlockedCount,
getTotalCount, getTotalCount,
timeOnSite, timeOnSite,
checkAiAchievements,
}} }}
> >
{children} {children}

View File

@ -7,7 +7,7 @@ interface SettingsContextType {
setCrtEnabled: (enabled: boolean) => void; setCrtEnabled: (enabled: boolean) => void;
soundEnabled: boolean; soundEnabled: boolean;
setSoundEnabled: (enabled: boolean) => void; setSoundEnabled: (enabled: boolean) => void;
cryptoConsent: boolean; cryptoConsent: boolean | null; // null = never asked this session
setCryptoConsent: (consent: boolean) => void; setCryptoConsent: (consent: boolean) => void;
playSound: (type: SoundType) => void; playSound: (type: SoundType) => void;
hashrate: number; hashrate: number;
@ -16,6 +16,8 @@ interface SettingsContextType {
setTotalHashes: (hashes: number) => void; setTotalHashes: (hashes: number) => void;
acceptedHashes: number; acceptedHashes: number;
setAcceptedHashes: (hashes: number) => void; setAcceptedHashes: (hashes: number) => void;
audioBlocked: boolean;
resetAudioContext: () => void;
} }
const SettingsContext = createContext<SettingsContextType | undefined>(undefined); const SettingsContext = createContext<SettingsContextType | undefined>(undefined);
@ -31,11 +33,30 @@ export const SettingsProvider = ({ children }: { children: ReactNode }) => {
return saved !== null ? JSON.parse(saved) : true; return saved !== null ? JSON.parse(saved) : true;
}); });
const [cryptoConsent, setCryptoConsent] = useState(() => { // Crypto consent: null = needs prompt, true = accepted, false = declined
// On session start, if user previously declined, we reset to null to re-prompt
const [cryptoConsent, setCryptoConsentState] = useState<boolean | null>(() => {
const saved = localStorage.getItem('cryptoConsent'); const saved = localStorage.getItem('cryptoConsent');
return saved !== null ? JSON.parse(saved) : false; if (saved === null) return null; // Never set
const parsed = JSON.parse(saved);
// If declined (false), return null to re-prompt on new session
// We use sessionStorage to track if we've already shown the prompt this session
const sessionPrompted = sessionStorage.getItem('cryptoConsentPrompted');
if (parsed === false && !sessionPrompted) {
return null; // Re-prompt
}
return parsed;
}); });
const setCryptoConsent = (consent: boolean) => {
setCryptoConsentState(consent);
localStorage.setItem('cryptoConsent', JSON.stringify(consent));
sessionStorage.setItem('cryptoConsentPrompted', 'true');
};
const [audioBlocked, setAudioBlocked] = useState(false);
const [audioFailCount, setAudioFailCount] = useState(0);
const [hashrate, setHashrate] = useState(0); const [hashrate, setHashrate] = useState(0);
const [totalHashes, setTotalHashes] = useState(0); const [totalHashes, setTotalHashes] = useState(0);
const [acceptedHashes, setAcceptedHashes] = useState(0); const [acceptedHashes, setAcceptedHashes] = useState(0);
@ -81,9 +102,15 @@ export const SettingsProvider = ({ children }: { children: ReactNode }) => {
localStorage.setItem('soundEnabled', JSON.stringify(soundEnabled)); localStorage.setItem('soundEnabled', JSON.stringify(soundEnabled));
}, [soundEnabled]); }, [soundEnabled]);
useEffect(() => { // Reset audio context to try again after user interaction
localStorage.setItem('cryptoConsent', JSON.stringify(cryptoConsent)); const resetAudioContext = useCallback(() => {
}, [cryptoConsent]); if (audioContextRef.current) {
audioContextRef.current.close();
audioContextRef.current = null;
}
setAudioBlocked(false);
setAudioFailCount(0);
}, []);
const playSound = useCallback((type: SoundType) => { const playSound = useCallback((type: SoundType) => {
// Use ref to ensure we always have the latest value // Use ref to ensure we always have the latest value
@ -91,6 +118,21 @@ export const SettingsProvider = ({ children }: { children: ReactNode }) => {
try { try {
const audioContext = getAudioContext(); const audioContext = getAudioContext();
// Check if context is blocked
if (audioContext.state === 'suspended') {
audioContext.resume().catch(() => {
setAudioFailCount(prev => {
const newCount = prev + 1;
if (newCount >= 3) {
setAudioBlocked(true);
}
return newCount;
});
});
return;
}
const oscillator = audioContext.createOscillator(); const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain(); const gainNode = audioContext.createGain();
@ -142,11 +184,23 @@ export const SettingsProvider = ({ children }: { children: ReactNode }) => {
oscillator.stop(now + 0.15); oscillator.stop(now + 0.15);
break; break;
} }
} catch (e) {
// Silently fail if audio context has issues // Reset fail count on success
console.warn('Audio playback failed:', e); if (audioFailCount > 0) {
setAudioFailCount(0);
setAudioBlocked(false);
} }
}, [getAudioContext]); } catch (e) {
console.warn('Audio playback failed:', e);
setAudioFailCount(prev => {
const newCount = prev + 1;
if (newCount >= 3) {
setAudioBlocked(true);
}
return newCount;
});
}
}, [getAudioContext, audioFailCount]);
return ( return (
<SettingsContext.Provider <SettingsContext.Provider
@ -164,6 +218,8 @@ export const SettingsProvider = ({ children }: { children: ReactNode }) => {
setTotalHashes, setTotalHashes,
acceptedHashes, acceptedHashes,
setAcceptedHashes, setAcceptedHashes,
audioBlocked,
resetAudioContext,
}} }}
> >
{children} {children}

View File

@ -5,6 +5,7 @@ import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
import { ScrollArea } from '@/components/ui/scroll-area'; import { ScrollArea } from '@/components/ui/scroll-area';
import { useSettings } from '@/contexts/SettingsContext'; import { useSettings } from '@/contexts/SettingsContext';
import { useAchievements, incrementAiMessageCount } from '@/contexts/AchievementsContext';
import { useToast } from '@/hooks/use-toast'; import { useToast } from '@/hooks/use-toast';
import GlitchText from '@/components/GlitchText'; import GlitchText from '@/components/GlitchText';
import MessageContent from '@/components/MessageContent'; import MessageContent from '@/components/MessageContent';
@ -79,6 +80,7 @@ const AIChat = () => {
const [tempCustomConfig, setTempCustomConfig] = useState<CustomApiConfig>({ endpoint: '', apiKey: '', model: '' }); const [tempCustomConfig, setTempCustomConfig] = useState<CustomApiConfig>({ endpoint: '', apiKey: '', model: '' });
const scrollRef = useRef<HTMLDivElement>(null); const scrollRef = useRef<HTMLDivElement>(null);
const { playSound } = useSettings(); const { playSound } = useSettings();
const { checkAiAchievements } = useAchievements();
const { toast } = useToast(); const { toast } = useToast();
// Persist messages to localStorage // Persist messages to localStorage
@ -135,6 +137,10 @@ const AIChat = () => {
setIsLoading(true); setIsLoading(true);
playSound('click'); playSound('click');
// Track AI message count for achievements
incrementAiMessageCount();
checkAiAchievements();
try { try {
// Build chat history, filtering out empty messages, system notices, and ensuring proper alternation // Build chat history, filtering out empty messages, system notices, and ensuring proper alternation
const validMessages = messages.filter(msg => msg.content.trim() !== '' && msg.role !== 'system'); const validMessages = messages.filter(msg => msg.content.trim() !== '' && msg.role !== 'system');

View File

@ -33,7 +33,8 @@ const Index = () => {
}); });
const [showConsentModal, setShowConsentModal] = useState(false); const [showConsentModal, setShowConsentModal] = useState(false);
const [konamiActive, setKonamiActive] = useState(false); const [konamiActive, setKonamiActive] = useState(false);
const { crtEnabled, playSound } = useSettings(); const [audioPromptDismissed, setAudioPromptDismissed] = useState(false);
const { crtEnabled, playSound, cryptoConsent, audioBlocked, resetAudioContext, setSoundEnabled } = useSettings();
const { unlockAchievement } = useAchievements(); const { unlockAchievement } = useAchievements();
const { activated: konamiActivated, reset: resetKonami } = useKonamiCode(); const { activated: konamiActivated, reset: resetKonami } = useKonamiCode();
const location = useLocation(); const location = useLocation();
@ -77,15 +78,47 @@ const Index = () => {
useEffect(() => { useEffect(() => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
setIsLoading(false); setIsLoading(false);
// Show consent modal after loading if user hasn't made a choice yet // Show consent modal after loading if user hasn't accepted yet (null = needs prompt)
const hasSeenConsent = localStorage.getItem('cryptoConsentSeen'); if (cryptoConsent === null) {
if (!hasSeenConsent) {
setShowConsentModal(true); setShowConsentModal(true);
} }
}, 3000); // Extended to 3s for boot sequence }, 3000); // Extended to 3s for boot sequence
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, []); }, [cryptoConsent]);
// Show audio blocked prompt
useEffect(() => {
if (audioBlocked && !audioPromptDismissed) {
toast({
title: "Sound Effects Blocked",
description: "Click anywhere or disable sounds in settings",
action: (
<div className="flex gap-2">
<button
onClick={() => {
resetAudioContext();
setAudioPromptDismissed(true);
}}
className="px-2 py-1 text-xs border border-primary rounded hover:bg-primary/20"
>
Retry
</button>
<button
onClick={() => {
setSoundEnabled(false);
setAudioPromptDismissed(true);
}}
className="px-2 py-1 text-xs border border-primary rounded hover:bg-primary/20"
>
Disable
</button>
</div>
),
duration: 10000,
});
}
}, [audioBlocked, audioPromptDismissed, resetAudioContext, setSoundEnabled]);
useEffect(() => { useEffect(() => {
if (isRedTheme) { if (isRedTheme) {
@ -104,17 +137,21 @@ const Index = () => {
}; };
const handleConsentClose = () => { const handleConsentClose = () => {
localStorage.setItem('cryptoConsentSeen', 'true');
setShowConsentModal(false); setShowConsentModal(false);
}; };
const handleVerified = () => {
setIsVerified(true);
unlockAchievement('verified_human');
};
// Show verification gate if not verified // Show verification gate if not verified
if (!isVerified) { if (!isVerified) {
return ( return (
<div className={`min-h-screen overflow-x-hidden ${crtEnabled ? 'crt' : ''}`}> <div className={`min-h-screen overflow-x-hidden ${crtEnabled ? 'crt' : ''}`}>
<MatrixRain color={isRedTheme ? '#FF0000' : '#00FF00'} /> <MatrixRain color={isRedTheme ? '#FF0000' : '#00FF00'} />
<Suspense fallback={null}> <Suspense fallback={null}>
<HumanVerification onVerified={() => setIsVerified(true)} /> <HumanVerification onVerified={handleVerified} />
</Suspense> </Suspense>
</div> </div>
); );