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