Skip to content

Commit f4061f6

Browse files
committed
merge~2
1 parent aa1cf68 commit f4061f6

File tree

9 files changed

+340
-0
lines changed

9 files changed

+340
-0
lines changed

CyberQuestGame/public/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Cyber Quest</title>
7+
<meta name="description" content="Cyber Quest - Gamified Cybersecurity Awareness" />
8+
</head>
9+
<body>
10+
<div id="root"></div>
11+
<script type="module" src="/src/index.jsx"></script>
12+
</body>
13+
</html>

CyberQuestGame/src/App.jsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React, { useState } from "react";
2+
import LandingPage from "./components/LandingPage.jsx";
3+
import LevelSelect from "./components/LevelSelect.jsx";
4+
import GameScreen from "./components/GameScreen.jsx";
5+
import Leaderboard from "./components/Leaderboard.jsx";
6+
import "./styles.css";
7+
8+
function App() {
9+
const [screen, setScreen] = useState("landing");
10+
const [level, setLevel] = useState(null);
11+
const [score, setScore] = useState(0);
12+
13+
const startGame = () => setScreen("levelSelect");
14+
const selectLevel = (lvl) => {
15+
setLevel(lvl);
16+
setScreen("game");
17+
};
18+
const finishGame = (finalScore) => {
19+
setScore(finalScore);
20+
setScreen("leaderboard");
21+
};
22+
const goHome = () => {
23+
setScreen("landing");
24+
setLevel(null);
25+
setScore(0);
26+
};
27+
28+
return (
29+
<div className="app-container">
30+
{screen === "landing" && <LandingPage onStart={startGame} />}
31+
{screen === "levelSelect" && <LevelSelect onSelect={selectLevel} onBack={goHome} />}
32+
{screen === "game" && <GameScreen level={level} onFinish={finishGame} onBack={goHome} />}
33+
{screen === "leaderboard" && <Leaderboard score={score} onRestart={goHome} />}
34+
</div>
35+
);
36+
}
37+
38+
export default App;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React, { useState } from "react";
2+
import questionsData from "../data/questions";
3+
4+
function GameScreen({ level, onFinish, onBack }) {
5+
const questions = questionsData[level?.name] || [];
6+
const [current, setCurrent] = useState(0);
7+
const [score, setScore] = useState(0);
8+
const [showFeedback, setShowFeedback] = useState(false);
9+
const [feedback, setFeedback] = useState("");
10+
11+
if (!level) return null;
12+
13+
const handleAnswer = (isCorrect) => {
14+
setShowFeedback(true);
15+
setFeedback(isCorrect ? "✅ Correct!" : "❌ Incorrect!");
16+
if (isCorrect) setScore((s) => s + 1);
17+
setTimeout(() => {
18+
setShowFeedback(false);
19+
if (current + 1 < questions.length) {
20+
setCurrent((c) => c + 1);
21+
} else {
22+
onFinish(score + (isCorrect ? 1 : 0));
23+
}
24+
}, 1100);
25+
};
26+
27+
const q = questions[current];
28+
return (
29+
<div className="card" style={{ textAlign: 'center' }}>
30+
<h2 style={{ marginBottom: '1rem' }}>{level.name}</h2>
31+
<div style={{ marginBottom: '1rem' }}>
32+
<strong>Question {current + 1} of {questions.length}</strong>
33+
<p style={{ fontSize: '1.1rem' }}>{q.question}</p>
34+
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem', alignItems: 'center' }}>
35+
{q.options.map((opt, idx) => (
36+
<button
37+
key={idx}
38+
onClick={() => handleAnswer(opt.correct)}
39+
disabled={showFeedback}
40+
style={{ width: '100%', maxWidth: 320 }}
41+
>
42+
{opt.text}
43+
</button>
44+
))}
45+
</div>
46+
{showFeedback && (
47+
<div style={{ marginTop: 16, fontWeight: 600, color: feedback.includes('Correct') ? '#22c55e' : '#ef4444' }}>
48+
{feedback} <br />
49+
<span style={{ fontWeight: 400, fontSize: '0.95rem' }}>{q.explanation}</span>
50+
</div>
51+
)}
52+
</div>
53+
<div style={{ marginTop: 20, display: 'flex', justifyContent: 'space-between' }}>
54+
<button onClick={onBack} style={{ background: "#64748b" }}>Quit</button>
55+
<span style={{ alignSelf: 'center', marginLeft: 10, fontWeight: 600 }}>Score: {score}</span>
56+
</div>
57+
</div>
58+
);
59+
}
60+
61+
export default GameScreen;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React from "react";
2+
3+
function LandingPage({ onStart }) {
4+
return (
5+
<div className="card" style={{ textAlign: 'center' }}>
6+
<h1 style={{ fontSize: '2.5rem', marginBottom: '1rem' }}>Cyber Quest</h1>
7+
<p style={{ fontSize: '1.1rem', marginBottom: '2rem' }}>
8+
Welcome to <b>Cyber Quest</b>!<br />
9+
Test your cybersecurity knowledge and learn how to stay safe online.<br />
10+
<span role="img" aria-label="shield">🛡️</span>
11+
</p>
12+
<button onClick={onStart} style={{ fontSize: '1.3rem', padding: '1rem 2.5rem' }}>Start Game</button>
13+
</div>
14+
);
15+
}
16+
17+
export default LandingPage;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React, { useEffect, useState } from "react";
2+
3+
function Leaderboard({ score, onRestart }) {
4+
const [scores, setScores] = useState([]);
5+
6+
useEffect(() => {
7+
const prev = JSON.parse(localStorage.getItem("cq_scores") || "[]");
8+
const updated = [...prev, score].sort((a, b) => b - a).slice(0, 5);
9+
setScores(updated);
10+
localStorage.setItem("cq_scores", JSON.stringify(updated));
11+
}, [score]);
12+
13+
return (
14+
<div className="card" style={{ textAlign: 'center' }}>
15+
<h2 style={{ marginBottom: '1rem' }}>Leaderboard</h2>
16+
<ol style={{ textAlign: 'left', margin: '0 auto 1.5rem', maxWidth: 200 }}>
17+
{scores.map((s, i) => (
18+
<li key={i} style={{ fontWeight: i === 0 ? 700 : 400, fontSize: i === 0 ? '1.2rem' : '1rem', color: i === 0 ? '#2563eb' : '#f1f5f9' }}>{s}</li>
19+
))}
20+
</ol>
21+
<button onClick={onRestart} style={{ fontSize: '1.1rem', padding: '0.7rem 2rem' }}>Home</button>
22+
</div>
23+
);
24+
}
25+
26+
export default Leaderboard;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React from "react";
2+
3+
const levels = [
4+
{ id: 1, name: "Password Safety", emoji: "🔒" },
5+
{ id: 2, name: "Phishing Awareness", emoji: "🎣" },
6+
{ id: 3, name: "Malware Detection", emoji: "🦠" },
7+
{ id: 4, name: "Online Privacy", emoji: "👤" },
8+
];
9+
10+
function LevelSelect({ onSelect, onBack }) {
11+
return (
12+
<div className="card" style={{ textAlign: 'center' }}>
13+
<h2 style={{ marginBottom: '1.5rem' }}>Select a Level</h2>
14+
<ul style={{ listStyle: "none", padding: 0, marginBottom: '2rem' }}>
15+
{levels.map((level) => (
16+
<li key={level.id} style={{ margin: '1rem 0' }}>
17+
<button onClick={() => onSelect(level)} style={{ fontSize: '1.1rem', minWidth: 200 }}>
18+
<span style={{ fontSize: '1.5rem', marginRight: 8 }}>{level.emoji}</span>
19+
{level.name}
20+
</button>
21+
</li>
22+
))}
23+
</ul>
24+
<button onClick={onBack} style={{ background: "#64748b" }}>Back</button>
25+
</div>
26+
);
27+
}
28+
29+
export default LevelSelect;
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
const questions = {
2+
"Password Safety": [
3+
{
4+
question: "Which password is the strongest?",
5+
options: [
6+
{ text: "password123", correct: false },
7+
{ text: "P@ssw0rd!2025", correct: true },
8+
{ text: "qwerty", correct: false },
9+
{ text: "123456", correct: false }
10+
],
11+
explanation: "Strong passwords use a mix of letters, numbers, and symbols."
12+
},
13+
{
14+
question: "What should you avoid when creating a password?",
15+
options: [
16+
{ text: "Using your birthdate", correct: true },
17+
{ text: "Using a random phrase", correct: false },
18+
{ text: "Using symbols", correct: false },
19+
{ text: "Using both upper and lower case letters", correct: false }
20+
],
21+
explanation: "Personal information is easy to guess or find."
22+
}
23+
],
24+
"Phishing Awareness": [
25+
{
26+
question: "What is a common sign of a phishing email?",
27+
options: [
28+
{ text: "Spelling mistakes and urgent requests", correct: true },
29+
{ text: "A friendly greeting", correct: false },
30+
{ text: "Your name in the email", correct: false },
31+
{ text: "A known sender address", correct: false }
32+
],
33+
explanation: "Phishing emails often contain errors and pressure to act quickly."
34+
},
35+
{
36+
question: "What should you do if you suspect a phishing attempt?",
37+
options: [
38+
{ text: "Click the link to check", correct: false },
39+
{ text: "Delete or report the email", correct: true },
40+
{ text: "Reply asking for more info", correct: false },
41+
{ text: "Forward to friends", correct: false }
42+
],
43+
explanation: "Never interact with suspicious emails; report or delete them."
44+
}
45+
],
46+
"Malware Detection": [
47+
{
48+
question: "What is malware?",
49+
options: [
50+
{ text: "Malicious software", correct: true },
51+
{ text: "A type of hardware", correct: false },
52+
{ text: "A secure website", correct: false },
53+
{ text: "An antivirus program", correct: false }
54+
],
55+
explanation: "Malware is software designed to harm your device or data."
56+
},
57+
{
58+
question: "Which action can help avoid malware?",
59+
options: [
60+
{ text: "Opening email attachments from unknown senders", correct: false },
61+
{ text: "Clicking pop-up ads", correct: false },
62+
{ text: "Installing software from trusted sources", correct: true },
63+
{ text: "Ignoring software updates", correct: false }
64+
],
65+
explanation: "Install software only from reputable sources."
66+
}
67+
],
68+
"Online Privacy": [
69+
{
70+
question: "Which info should you avoid sharing online?",
71+
options: [
72+
{ text: "Your home address", correct: true },
73+
{ text: "A favorite color", correct: false },
74+
{ text: "A hobby", correct: false },
75+
{ text: "A nickname", correct: false }
76+
],
77+
explanation: "Personal details like your address should be kept private."
78+
},
79+
{
80+
question: "How can you improve your online privacy?",
81+
options: [
82+
{ text: "Use strong, unique passwords", correct: true },
83+
{ text: "Share passwords with friends", correct: false },
84+
{ text: "Post your schedule publicly", correct: false },
85+
{ text: "Accept all cookies on every website", correct: false }
86+
],
87+
explanation: "Unique passwords and careful sharing improve privacy."
88+
}
89+
]
90+
};
91+
92+
export default questions;

CyberQuestGame/src/index.jsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import React from "react";
2+
import { createRoot } from "react-dom/client";
3+
import App from "./App.jsx";
4+
import "./styles.css";
5+
6+
const container = document.getElementById("root");
7+
const root = createRoot(container);
8+
root.render(<App />);

CyberQuestGame/src/styles.css

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
body {
2+
margin: 0;
3+
font-family: 'Segoe UI', Arial, sans-serif;
4+
background: #0f172a;
5+
color: #f1f5f9;
6+
}
7+
.app-container {
8+
min-height: 100vh;
9+
display: flex;
10+
flex-direction: column;
11+
align-items: center;
12+
justify-content: center;
13+
padding: 2rem;
14+
}
15+
button {
16+
background: #2563eb;
17+
color: #fff;
18+
border: none;
19+
padding: 0.75rem 2rem;
20+
margin: 1rem 0;
21+
border-radius: 8px;
22+
font-size: 1.1rem;
23+
cursor: pointer;
24+
transition: background 0.2s;
25+
outline: none;
26+
}
27+
button:focus {
28+
outline: 2px solid #38bdf8;
29+
outline-offset: 2px;
30+
}
31+
button:hover {
32+
background: #1e40af;
33+
}
34+
.card {
35+
background: #1e293b;
36+
padding: 2rem;
37+
border-radius: 12px;
38+
box-shadow: 0 2px 16px rgba(0,0,0,0.18);
39+
margin-bottom: 2rem;
40+
max-width: 440px;
41+
width: 100%;
42+
margin-left: auto;
43+
margin-right: auto;
44+
}
45+
h1, h2 {
46+
color: #38bdf8;
47+
margin-top: 0;
48+
}
49+
::-webkit-scrollbar {
50+
width: 8px;
51+
background: #1e293b;
52+
}
53+
::-webkit-scrollbar-thumb {
54+
background: #334155;
55+
border-radius: 6px;
56+
}

0 commit comments

Comments
 (0)