1
0
Fork 0
security-lab/src/cases/CybersecMenu.tsx
2026-04-05 05:32:27 +03:00

362 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useEffect, useRef, useState, useCallback } from 'react';
import './CybersecMenu.css';
interface Notification {
id: number;
title: string;
message: string;
removing: boolean;
}
const CybersecMenu: React.FC = () => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const animFrameRef = useRef<number>(0);
const smokeOffsetRef = useRef(0);
const titleRef = useRef<HTMLHeadingElement>(null);
const [notification, setNotification] = useState<Notification | null>(null);
const notifIdRef = useRef(0);
const notifTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const showNotification = useCallback((title: string, message: string) => {
if (notifTimerRef.current) clearTimeout(notifTimerRef.current);
const id = ++notifIdRef.current;
setNotification({ id, title, message, removing: false });
notifTimerRef.current = setTimeout(() => {
setNotification(prev => prev?.id === id ? { ...prev, removing: true } : prev);
setTimeout(() => setNotification(prev => prev?.id === id ? null : prev), 300);
}, 3000);
}, []);
const selectLevel = useCallback((level: number | string) => {
const levelNames: Record<string | number, string> = {
1: 'Phishing Fake emails & links',
2: 'Skimming ATM/card fraud',
3: 'Password cracking Brute force & dictionary',
4: 'Social engineering Manipulation',
'deepfake': 'Voice deepfake AI-generated speech',
};
showNotification(`>> LEVEL ${level} SELECTED`, levelNames[level]);
}, [showNotification]);
const openWiki = useCallback(() => {
showNotification('>> ACCESSING WIKI', 'Security knowledge base loading...');
}, [showNotification]);
const openQuiz = useCallback(() => {
showNotification('>> INITIATING QUIZ', 'Test your skills module loading...');
}, [showNotification]);
// Keyboard navigation
useEffect(() => {
const handleKey = (e: KeyboardEvent) => {
const map: Record<string, () => void> = {
'1': () => selectLevel(1),
'2': () => selectLevel(2),
'3': () => selectLevel(3),
'4': () => selectLevel(4),
'5': () => selectLevel('deepfake'),
'w': openWiki, 'W': openWiki,
'q': openQuiz, 'Q': openQuiz,
};
map[e.key]?.();
};
window.addEventListener('keydown', handleKey);
return () => window.removeEventListener('keydown', handleKey);
}, [selectLevel, openWiki, openQuiz]);
// Glitch effect on title
useEffect(() => {
const interval = setInterval(() => {
if (Math.random() > 0.9 && titleRef.current) {
const rx = (Math.random() * 10 - 5).toFixed(1);
const rx2 = (Math.random() * 10 - 5).toFixed(1);
titleRef.current.style.textShadow = `${rx}px 0 #FF0040, ${rx2}px 0 #00FFFF`;
setTimeout(() => {
if (titleRef.current) titleRef.current.style.textShadow = '0 0 30px rgba(0,255,255,0.5)';
}, 100);
}
}, 2000);
return () => clearInterval(interval);
}, []);
// Particle canvas
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const resize = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
resize();
const COLORS = ['#00FF41', '#FF00FF', '#00FFFF', '#FF6600'];
class Particle {
x = 0; y = 0; size = 0; speedX = 0; speedY = 0;
opacity = 0; color = ''; life = 0; maxLife = 0;
constructor() { this.reset(); }
reset() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.size = Math.random() * 3 + 1;
this.speedX = (Math.random() - 0.5) * 0.5;
this.speedY = (Math.random() - 0.5) * 0.5;
this.opacity = Math.random() * 0.5 + 0.1;
this.color = COLORS[Math.floor(Math.random() * COLORS.length)];
this.life = 0;
this.maxLife = Math.random() * 200 + 100;
}
update() {
this.x += this.speedX;
this.y += this.speedY;
this.life++;
if (this.x < 0) this.x = canvas.width;
if (this.x > canvas.width) this.x = 0;
if (this.y < 0) this.y = canvas.height;
if (this.y > canvas.height) this.y = 0;
if (this.life > this.maxLife) this.reset();
}
draw() {
ctx.save();
ctx.globalAlpha = this.opacity * (1 - this.life / this.maxLife);
ctx.fillStyle = this.color;
ctx.shadowBlur = 10;
ctx.shadowColor = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
}
const count = Math.min(80, Math.floor((canvas.width * canvas.height) / 15000));
let particles = Array.from({ length: count }, () => new Particle());
const onVis = () => {
if (document.hidden) { cancelAnimationFrame(animFrameRef.current); }
else { animate(); }
};
document.addEventListener('visibilitychange', onVis);
const onResize = () => { resize(); particles = Array.from({ length: Math.min(80, Math.floor((canvas.width * canvas.height) / 15000)) }, () => new Particle()); };
window.addEventListener('resize', onResize);
function drawSmoke() {
smokeOffsetRef.current += 0.2;
const so = smokeOffsetRef.current;
const layerColors = ['#00FF41', '#FF00FF', '#00FFFF'];
for (let layer = 0; layer < 3; layer++) {
ctx.save();
ctx.globalAlpha = 0.03 - layer * 0.008;
const grad = ctx.createRadialGradient(
canvas.width * 0.3 + Math.sin(so * 0.01 + layer) * 200,
canvas.height * 0.5 + Math.cos(so * 0.015 + layer) * 100,
0,
canvas.width * 0.5, canvas.height * 0.5,
canvas.width * 0.8
);
grad.addColorStop(0, layerColors[layer]);
grad.addColorStop(1, '#0a0a0a');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.restore();
}
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawSmoke();
particles.forEach(p => { p.update(); p.draw(); });
ctx.save();
ctx.strokeStyle = 'rgba(0,255,255,0.05)';
ctx.lineWidth = 0.5;
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
const dx = particles[i].x - particles[j].x;
const dy = particles[i].y - particles[j].y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 100) {
ctx.globalAlpha = (1 - dist / 100) * 0.2;
ctx.beginPath();
ctx.moveTo(particles[i].x, particles[i].y);
ctx.lineTo(particles[j].x, particles[j].y);
ctx.stroke();
}
}
}
ctx.restore();
animFrameRef.current = requestAnimationFrame(animate);
}
animate();
return () => {
cancelAnimationFrame(animFrameRef.current);
window.removeEventListener('resize', onResize);
document.removeEventListener('visibilitychange', onVis);
};
}, []);
const menuItems = [
{ level: 1 as number | string, cls: 'csm-level-1', icon: '🔒', title: 'Level 1', desc: 'Phishing Fake emails & links' },
{ level: 2 as number | string, cls: 'csm-level-2', icon: '⌨️', title: 'Level 2', desc: 'Skimming ATM/card fraud' },
{ level: 3 as number | string, cls: 'csm-level-3', icon: '🖥️', title: 'Level 3', desc: 'Password cracking Brute force & dictionary' },
{ level: 4 as number | string, cls: 'csm-level-4', icon: '🛡️', title: 'Level 4', desc: 'Social engineering Manipulation' },
{ level: 'deepfake', cls: 'csm-deepfake', icon: '🎭', title: 'Deepfake', desc: 'Voice deepfake AI-generated speech' },
];
return (
<div className="csm-root">
<canvas ref={canvasRef} id="csm-canvas" />
<div className="csm-grid-overlay" />
<div className="csm-scanlines" />
<div className="csm-noise" />
<div className="csm-corner csm-tl" />
<div className="csm-corner csm-tr" />
<div className="csm-corner csm-bl" />
<div className="csm-corner csm-br" />
<div className="csm-main-container">
{/* Header */}
<div className="csm-header">
<h1 ref={titleRef}>CYBERSEC TRAINING</h1>
<div className="csm-subtitle">[ TACTICAL SECURITY SIMULATION ]</div>
</div>
{/* Character */}
<div className="csm-character-container">
<svg className="csm-hacker-svg" viewBox="0 0 400 500" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="csm-hoodGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style={{stopColor:'#1a1a1a',stopOpacity:1}}/>
<stop offset="100%" style={{stopColor:'#0d0d0d',stopOpacity:1}}/>
</linearGradient>
<linearGradient id="csm-maskGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style={{stopColor:'#2a2a2a',stopOpacity:1}}/>
<stop offset="50%" style={{stopColor:'#1a1a1a',stopOpacity:1}}/>
<stop offset="100%" style={{stopColor:'#0a0a0a',stopOpacity:1}}/>
</linearGradient>
<filter id="csm-glow">
<feGaussianBlur stdDeviation="3" result="coloredBlur"/>
<feMerge><feMergeNode in="coloredBlur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="csm-strongGlow">
<feGaussianBlur stdDeviation="5" result="coloredBlur"/>
<feMerge><feMergeNode in="coloredBlur"/><feMergeNode in="coloredBlur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
{/* Hood/Head Silhouette */}
<path d="M200 80 C 150 80, 100 120, 90 180 C 85 210, 85 250, 95 290 C 100 320, 110 350, 130 370 L 130 420 C 130 440, 150 450, 200 450 C 250 450, 270 440, 270 420 L 270 370 C 290 350, 300 320, 305 290 C 315 250, 315 210, 310 180 C 300 120, 250 80, 200 80 Z"
fill="url(#csm-hoodGrad)" stroke="#00FF41" strokeWidth="2" filter="url(#csm-glow)"/>
{/* Hood Inner Shadow */}
<path d="M200 100 C 165 100, 130 130, 125 170 C 122 190, 122 220, 128 250 C 132 270, 140 290, 155 305 L 155 350 C 155 365, 170 375, 200 375 C 230 375, 245 365, 245 350 L 245 305 C 260 290, 268 270, 272 250 C 278 220, 278 190, 275 170 C 270 130, 235 100, 200 100 Z"
fill="#0a0a0a"/>
{/* Goggles/Mask */}
<rect x="125" y="200" width="150" height="70" rx="15"
fill="url(#csm-maskGrad)" stroke="#00FFFF" strokeWidth="2" filter="url(#csm-glow)"/>
{/* Goggle Lenses */}
<ellipse cx="165" cy="235" rx="28" ry="22" fill="#000" stroke="#00FFFF" strokeWidth="1.5"/>
<ellipse cx="235" cy="235" rx="28" ry="22" fill="#000" stroke="#00FFFF" strokeWidth="1.5"/>
{/* Glowing Eyes */}
<circle cx="165" cy="235" r="8" fill="#00FF41" className="csm-eye-glow" filter="url(#csm-strongGlow)"/>
<circle cx="235" cy="235" r="8" fill="#00FF41" className="csm-eye-glow" filter="url(#csm-strongGlow)"/>
{/* Eye Details */}
<circle cx="167" cy="233" r="3" fill="#fff" opacity="0.8"/>
<circle cx="237" cy="233" r="3" fill="#fff" opacity="0.8"/>
{/* Goggle Frame Details */}
<line x1="193" y1="220" x2="207" y2="220" stroke="#00FFFF" strokeWidth="2"/>
<line x1="193" y1="235" x2="207" y2="235" stroke="#00FFFF" strokeWidth="2"/>
<line x1="193" y1="250" x2="207" y2="250" stroke="#00FFFF" strokeWidth="2"/>
{/* Goggle Side Lights */}
<rect x="115" y="225" width="8" height="20" rx="2" fill="#FF00FF" filter="url(#csm-glow)" opacity="0.8"/>
<rect x="277" y="225" width="8" height="20" rx="2" fill="#FF00FF" filter="url(#csm-glow)" opacity="0.8"/>
{/* Shoulders/Torso */}
<path d="M80 380 C 60 390, 40 420, 30 480 L 30 500 L 370 500 L 370 480 C 360 420, 340 390, 320 380 C 280 360, 240 355, 200 355 C 160 355, 120 360, 80 380 Z"
fill="#0d0d0d" stroke="#00FF41" strokeWidth="1.5" opacity="0.6"/>
{/* Jacket Details */}
<path d="M200 355 L 200 500" stroke="#00FF41" strokeWidth="1" opacity="0.3"/>
<path d="M120 380 L 150 500" stroke="#00FF41" strokeWidth="1" opacity="0.2"/>
<path d="M280 380 L 250 500" stroke="#00FF41" strokeWidth="1" opacity="0.2"/>
{/* Collar */}
<path d="M150 340 C 150 340, 200 360, 250 340 L 260 370 C 260 370, 200 395, 140 370 Z"
fill="#1a1a1a" stroke="#00FFFF" strokeWidth="1"/>
{/* Holographic Tablet */}
<g transform="translate(220, 400) rotate(-10)">
<rect x="0" y="0" width="100" height="70" rx="5"
fill="rgba(0,20,40,0.9)" stroke="#00FF41" strokeWidth="2" className="csm-tablet-glow"/>
<rect x="5" y="5" width="90" height="60" rx="3" fill="rgba(0,10,20,0.95)"/>
<line x1="10" y1="12" x2="50" y2="12" stroke="#00FF41" strokeWidth="1.5" opacity="0.9"/>
<line x1="10" y1="20" x2="70" y2="20" stroke="#00FF41" strokeWidth="1.5" opacity="0.7"/>
<line x1="10" y1="28" x2="45" y2="28" stroke="#00FF41" strokeWidth="1.5" opacity="0.8"/>
<line x1="10" y1="36" x2="60" y2="36" stroke="#00FF41" strokeWidth="1.5" opacity="0.6"/>
<line x1="10" y1="44" x2="40" y2="44" stroke="#00FF41" strokeWidth="1.5" opacity="0.9"/>
<line x1="10" y1="52" x2="55" y2="52" stroke="#00FF41" strokeWidth="1.5" opacity="0.7"/>
<text x="55" y="58" className="csm-code-text" fontSize="6">01001</text>
<ellipse cx="50" cy="75" rx="45" ry="8" fill="url(#csm-hoodGrad)" opacity="0.3" filter="url(#csm-glow)"/>
</g>
{/* Hand */}
<ellipse cx="230" cy="445" rx="15" ry="12" fill="#1a1a1a" stroke="#00FF41" strokeWidth="1" opacity="0.8"/>
{/* Circuit Patterns */}
<path d="M120 150 L 140 150 L 145 160" stroke="#00FFFF" strokeWidth="0.5" fill="none" opacity="0.4"/>
<path d="M280 150 L 260 150 L 255 160" stroke="#00FFFF" strokeWidth="0.5" fill="none" opacity="0.4"/>
<circle cx="145" cy="160" r="2" fill="#00FFFF" opacity="0.5"/>
<circle cx="255" cy="160" r="2" fill="#00FFFF" opacity="0.5"/>
{/* Chin/Mouth */}
<path d="M175 290 C 175 290, 200 300, 225 290 L 225 305 C 225 305, 200 315, 175 305 Z"
fill="#0a0a0a" stroke="#00FF41" strokeWidth="0.5" opacity="0.5"/>
</svg>
</div>
{/* Right Side Menu */}
<div className="csm-right-menu">
{menuItems.map(item => (
<div key={String(item.level)} className={`csm-menu-item ${item.cls}`} onClick={() => selectLevel(item.level)}>
<div className="csm-hexagon">
<svg className="csm-hexagon-bg" viewBox="0 0 70 80">
<polygon points="35,2 66,20 66,58 35,76 4,58 4,20"/>
</svg>
<span className="csm-hexagon-icon">{item.icon}</span>
</div>
<div className="csm-tooltip">
<div className="csm-tooltip-title">{item.title}</div>
<div className="csm-tooltip-desc">{item.desc}</div>
</div>
</div>
))}
</div>
{/* Bottom Container */}
<div className="csm-bottom-container">
<div className="csm-bottom-block" onClick={openWiki}>
<div className="csm-block-shape">
<span className="csm-block-icon">📖</span>
<span className="csm-block-text">WIKI</span>
</div>
<div className="csm-block-tooltip">Security knowledge base</div>
</div>
<div className="csm-bottom-block" onClick={openQuiz}>
<div className="csm-block-shape">
<span className="csm-block-icon"></span>
<span className="csm-block-text">QUIZ</span>
</div>
<div className="csm-block-tooltip">Test your skills</div>
</div>
</div>
</div>
{/* Notification */}
{notification && (
<div className={`csm-notification ${notification.removing ? 'csm-notif-out' : ''}`}>
<div className="csm-notif-title">{notification.title}</div>
<div className="csm-notif-message">{notification.message}</div>
</div>
)}
</div>
);
};
export default CybersecMenu;