added cyber menu
This commit is contained in:
parent
87f07afc27
commit
0f8a089188
2 changed files with 845 additions and 0 deletions
483
src/cases/CybersecMenu.css
Normal file
483
src/cases/CybersecMenu.css
Normal file
|
|
@ -0,0 +1,483 @@
|
|||
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;700;900&family=Share+Tech+Mono&display=swap');
|
||||
|
||||
.csm-root {
|
||||
--csm-green: #00FF41;
|
||||
--csm-magenta: #FF00FF;
|
||||
--csm-cyan: #00FFFF;
|
||||
--csm-orange: #FF6600;
|
||||
--csm-bg: #0a0a0a;
|
||||
|
||||
font-family: 'Orbitron', monospace;
|
||||
background: var(--csm-bg);
|
||||
color: white;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#csm-canvas {
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.csm-grid-overlay {
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
background-image:
|
||||
linear-gradient(rgba(0,255,255,0.03) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(0,255,255,0.03) 1px, transparent 1px);
|
||||
background-size: 50px 50px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.csm-scanlines {
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
background: repeating-linear-gradient(
|
||||
0deg,
|
||||
rgba(0,0,0,0.15),
|
||||
rgba(0,0,0,0.15) 1px,
|
||||
transparent 1px,
|
||||
transparent 2px
|
||||
);
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
animation: csm-scanlineFlicker 0.1s infinite;
|
||||
}
|
||||
|
||||
@keyframes csm-scanlineFlicker {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.98; }
|
||||
}
|
||||
|
||||
.csm-noise {
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 999;
|
||||
opacity: 0.03;
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* Corner decorations */
|
||||
.csm-corner {
|
||||
position: absolute;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: 1px solid rgba(0,255,255,0.2);
|
||||
z-index: 5;
|
||||
}
|
||||
.csm-tl { top: 20px; left: 20px; border-right: none; border-bottom: none; }
|
||||
.csm-tr { top: 20px; right: 20px; border-left: none; border-bottom: none; }
|
||||
.csm-bl { bottom: 20px; left: 20px; border-right: none; border-top: none; }
|
||||
.csm-br { bottom: 20px; right: 20px; border-left: none; border-top: none; }
|
||||
|
||||
/* Main container */
|
||||
.csm-main-container {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.csm-header {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
text-align: center;
|
||||
z-index: 20;
|
||||
}
|
||||
.csm-header h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 900;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 8px;
|
||||
background: linear-gradient(90deg, var(--csm-cyan), var(--csm-magenta), var(--csm-green));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-shadow: 0 0 30px rgba(0,255,255,0.5);
|
||||
animation: csm-titlePulse 3s ease-in-out infinite;
|
||||
transition: text-shadow 0.1s;
|
||||
}
|
||||
.csm-subtitle {
|
||||
font-family: 'Share Tech Mono', monospace;
|
||||
font-size: 0.9rem;
|
||||
color: var(--csm-green);
|
||||
letter-spacing: 4px;
|
||||
margin-top: 10px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
@keyframes csm-titlePulse {
|
||||
0%, 100% { filter: brightness(1); }
|
||||
50% { filter: brightness(1.3); }
|
||||
}
|
||||
|
||||
/* Character */
|
||||
.csm-character-container {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 15;
|
||||
}
|
||||
.csm-hacker-svg {
|
||||
width: 400px;
|
||||
height: 500px;
|
||||
filter: drop-shadow(0 0 20px rgba(0,255,65,0.3));
|
||||
animation: csm-characterFloat 4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes csm-characterFloat {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-15px); }
|
||||
}
|
||||
|
||||
.csm-tablet-glow { animation: csm-tabletPulse 2s ease-in-out infinite; }
|
||||
@keyframes csm-tabletPulse {
|
||||
0%, 100% { opacity: 0.6; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
.csm-eye-glow { animation: csm-eyePulse 3s ease-in-out infinite; }
|
||||
@keyframes csm-eyePulse {
|
||||
0%, 100% { opacity: 0.7; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
.csm-code-text {
|
||||
font-family: 'Share Tech Mono', monospace;
|
||||
font-size: 8px;
|
||||
fill: var(--csm-green);
|
||||
animation: csm-codeFlicker 0.1s infinite;
|
||||
}
|
||||
@keyframes csm-codeFlicker {
|
||||
0%, 100% { opacity: 0.8; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
/* Right Side Menu */
|
||||
.csm-right-menu {
|
||||
position: absolute;
|
||||
right: 40px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 25px;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.csm-menu-item {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.csm-hexagon {
|
||||
width: 70px;
|
||||
height: 80px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.csm-hexagon-bg {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: none;
|
||||
stroke: var(--csm-cyan);
|
||||
stroke-width: 2;
|
||||
filter: drop-shadow(0 0 10px var(--csm-cyan));
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.csm-hexagon:hover .csm-hexagon-bg {
|
||||
stroke: var(--csm-magenta);
|
||||
filter: drop-shadow(0 0 20px var(--csm-magenta));
|
||||
transform: scale(1.15) translateX(-8px);
|
||||
}
|
||||
|
||||
.csm-hexagon-icon {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
font-size: 28px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.csm-hexagon:hover .csm-hexagon-icon {
|
||||
transform: scale(1.15) translateX(-8px);
|
||||
}
|
||||
|
||||
.csm-menu-item:hover {
|
||||
animation: csm-glitch 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes csm-glitch {
|
||||
0% { transform: translate(0); }
|
||||
20% { transform: translate(-2px, 2px); }
|
||||
40% { transform: translate(-2px, -2px); }
|
||||
60% { transform: translate(2px, 2px); }
|
||||
80% { transform: translate(2px, -2px); }
|
||||
100% { transform: translate(0); }
|
||||
}
|
||||
|
||||
/* Tooltip */
|
||||
.csm-tooltip {
|
||||
position: absolute;
|
||||
right: 90px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: rgba(10,10,10,0.95);
|
||||
border: 1px solid var(--csm-cyan);
|
||||
padding: 12px 18px;
|
||||
border-radius: 4px;
|
||||
min-width: 220px;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.2s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
.csm-tooltip::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
border-width: 8px 0 8px 8px;
|
||||
border-style: solid;
|
||||
border-color: transparent transparent transparent var(--csm-cyan);
|
||||
}
|
||||
.csm-menu-item:hover .csm-tooltip {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
right: 100px;
|
||||
}
|
||||
.csm-tooltip-title {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
color: var(--csm-cyan);
|
||||
margin-bottom: 5px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
.csm-tooltip-desc {
|
||||
font-family: 'Share Tech Mono', monospace;
|
||||
font-size: 0.75rem;
|
||||
color: #aaa;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Level colors */
|
||||
.csm-level-1:hover .csm-hexagon-bg { stroke: var(--csm-green); filter: drop-shadow(0 0 20px var(--csm-green)); }
|
||||
.csm-level-1:hover .csm-hexagon-icon { color: var(--csm-green); }
|
||||
.csm-level-1 .csm-tooltip { border-color: var(--csm-green); }
|
||||
.csm-level-1 .csm-tooltip::after { border-left-color: var(--csm-green); }
|
||||
.csm-level-1 .csm-tooltip-title { color: var(--csm-green); }
|
||||
|
||||
.csm-level-2:hover .csm-hexagon-bg { stroke: var(--csm-orange); filter: drop-shadow(0 0 20px var(--csm-orange)); }
|
||||
.csm-level-2:hover .csm-hexagon-icon { color: var(--csm-orange); }
|
||||
.csm-level-2 .csm-tooltip { border-color: var(--csm-orange); }
|
||||
.csm-level-2 .csm-tooltip::after { border-left-color: var(--csm-orange); }
|
||||
.csm-level-2 .csm-tooltip-title { color: var(--csm-orange); }
|
||||
|
||||
.csm-level-3:hover .csm-hexagon-bg { stroke: var(--csm-magenta); filter: drop-shadow(0 0 20px var(--csm-magenta)); }
|
||||
.csm-level-3:hover .csm-hexagon-icon { color: var(--csm-magenta); }
|
||||
.csm-level-3 .csm-tooltip { border-color: var(--csm-magenta); }
|
||||
.csm-level-3 .csm-tooltip::after { border-left-color: var(--csm-magenta); }
|
||||
.csm-level-3 .csm-tooltip-title { color: var(--csm-magenta); }
|
||||
|
||||
.csm-level-4:hover .csm-hexagon-bg { stroke: var(--csm-cyan); filter: drop-shadow(0 0 20px var(--csm-cyan)); }
|
||||
.csm-level-4:hover .csm-hexagon-icon { color: var(--csm-cyan); }
|
||||
.csm-level-4 .csm-tooltip { border-color: var(--csm-cyan); }
|
||||
.csm-level-4 .csm-tooltip::after { border-left-color: var(--csm-cyan); }
|
||||
.csm-level-4 .csm-tooltip-title { color: var(--csm-cyan); }
|
||||
|
||||
.csm-deepfake:hover .csm-hexagon-bg { stroke: #FF0040; filter: drop-shadow(0 0 20px #FF0040); }
|
||||
.csm-deepfake:hover .csm-hexagon-icon { color: #FF0040; }
|
||||
.csm-deepfake .csm-tooltip { border-color: #FF0040; }
|
||||
.csm-deepfake .csm-tooltip::after { border-left-color: #FF0040; }
|
||||
.csm-deepfake .csm-tooltip-title { color: #FF0040; }
|
||||
|
||||
/* Bottom Container */
|
||||
.csm-bottom-container {
|
||||
position: absolute;
|
||||
bottom: 50px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 80px;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.csm-bottom-block {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.csm-block-shape {
|
||||
width: 120px;
|
||||
height: 60px;
|
||||
background: rgba(10,10,10,0.9);
|
||||
border: 2px solid var(--csm-cyan);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
position: relative;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.csm-block-shape::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -4px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--csm-cyan);
|
||||
opacity: 0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.csm-bottom-block:hover .csm-block-shape {
|
||||
border-color: var(--csm-magenta);
|
||||
box-shadow: 0 0 30px rgba(255,0,255,0.5);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
.csm-bottom-block:hover .csm-block-shape::before {
|
||||
opacity: 1;
|
||||
border-color: var(--csm-magenta);
|
||||
animation: csm-pulseRing 1.5s ease-out infinite;
|
||||
}
|
||||
@keyframes csm-pulseRing {
|
||||
0% { transform: scale(1); opacity: 0.8; }
|
||||
100% { transform: scale(1.3); opacity: 0; }
|
||||
}
|
||||
.csm-block-icon {
|
||||
font-size: 24px;
|
||||
color: var(--csm-cyan);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.csm-bottom-block:hover .csm-block-icon { color: var(--csm-magenta); }
|
||||
.csm-block-text {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 3px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.csm-block-tooltip {
|
||||
position: absolute;
|
||||
bottom: 75px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: rgba(10,10,10,0.95);
|
||||
border: 1px solid var(--csm-cyan);
|
||||
padding: 10px 15px;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.2s ease;
|
||||
font-family: 'Share Tech Mono', monospace;
|
||||
font-size: 0.8rem;
|
||||
color: #aaa;
|
||||
}
|
||||
.csm-block-tooltip::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -8px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-width: 8px 8px 0;
|
||||
border-style: solid;
|
||||
border-color: var(--csm-cyan) transparent transparent;
|
||||
}
|
||||
.csm-bottom-block:hover .csm-block-tooltip {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
bottom: 85px;
|
||||
border-color: var(--csm-magenta);
|
||||
}
|
||||
.csm-bottom-block:hover .csm-block-tooltip::after {
|
||||
border-top-color: var(--csm-magenta);
|
||||
}
|
||||
|
||||
/* Notification */
|
||||
.csm-notification {
|
||||
position: fixed;
|
||||
top: 100px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: rgba(10,10,10,0.95);
|
||||
border: 2px solid var(--csm-green);
|
||||
padding: 20px 40px;
|
||||
border-radius: 4px;
|
||||
z-index: 10000;
|
||||
text-align: center;
|
||||
box-shadow: 0 0 30px rgba(0,255,65,0.5);
|
||||
animation: csm-notifIn 0.3s ease;
|
||||
}
|
||||
.csm-notification.csm-notif-out {
|
||||
animation: csm-notifOut 0.3s ease forwards;
|
||||
}
|
||||
.csm-notif-title {
|
||||
font-family: 'Orbitron', monospace;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
color: var(--csm-green);
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
.csm-notif-message {
|
||||
font-family: 'Share Tech Mono', monospace;
|
||||
font-size: 0.9rem;
|
||||
color: #aaa;
|
||||
}
|
||||
@keyframes csm-notifIn {
|
||||
from { opacity: 0; transform: translateX(-50%) translateY(-20px); }
|
||||
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
||||
}
|
||||
@keyframes csm-notifOut {
|
||||
from { opacity: 1; transform: translateX(-50%) translateY(0); }
|
||||
to { opacity: 0; transform: translateX(-50%) translateY(-20px); }
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 1024px) {
|
||||
.csm-hacker-svg { width: 300px; height: 375px; }
|
||||
.csm-right-menu { right: 20px; gap: 15px; }
|
||||
.csm-hexagon { width: 55px; height: 63px; }
|
||||
.csm-hexagon-icon { font-size: 22px; }
|
||||
.csm-tooltip { min-width: 180px; padding: 10px 14px; }
|
||||
.csm-header h1 { font-size: 1.8rem; }
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.csm-header h1 { font-size: 1.4rem; letter-spacing: 4px; }
|
||||
.csm-hacker-svg { width: 220px; height: 275px; }
|
||||
.csm-right-menu { right: 10px; gap: 10px; }
|
||||
.csm-hexagon { width: 45px; height: 52px; }
|
||||
.csm-hexagon-icon { font-size: 18px; }
|
||||
.csm-tooltip { display: none; }
|
||||
.csm-bottom-container { padding: 0 30px; bottom: 30px; }
|
||||
.csm-block-shape { width: 90px; height: 50px; }
|
||||
.csm-block-text { font-size: 0.7rem; }
|
||||
}
|
||||
362
src/cases/CybersecMenu.tsx
Normal file
362
src/cases/CybersecMenu.tsx
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
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;
|
||||
Loading…
Add table
Reference in a new issue