<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cyber Safety Training</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=IBM+Plex+Sans:wght@400;500&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #0d0f14;
--surface: #13161e;
--surface2: #1a1e28;
--border: #252b3a;
--border2: #2e3649;
--text: #e8eaf0;
--muted: #6b7491;
--accent: #4fc3f7;
--accent-dim: #1a3a4a;
--green: #4caf7d;
--green-dim: #142b1f;
--red: #e05c5c;
--red-dim: #2b1414;
--amber: #f0a843;
--amber-dim: #2b2008;
--mono: 'IBM Plex Mono', monospace;
--sans: 'IBM Plex Sans', sans-serif;
}
body {
background: var(--bg);
color: var(--text);
font-family: var(--sans);
font-size: 15px;
line-height: 1.6;
min-height: 100vh;
}
.shell {
max-width: 760px;
margin: 0 auto;
padding: 2rem 1.5rem 4rem;
}
/* ── Header ── */
.header {
border-bottom: 1px solid var(--border);
padding-bottom: 1.5rem;
margin-bottom: 2rem;
}
.header-top {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 6px;
}
.tag {
font-family: var(--mono);
font-size: 11px;
color: var(--accent);
background: var(--accent-dim);
padding: 3px 8px;
border: 1px solid #1e4a60;
letter-spacing: 0.06em;
}
h1 {
font-family: var(--mono);
font-size: 22px;
font-weight: 500;
letter-spacing: -0.02em;
color: var(--text);
margin-bottom: 6px;
}
.subtitle {
font-size: 13px;
color: var(--muted);
font-family: var(--mono);
}
/* ── Progress bar ── */
.progress-wrap {
margin-bottom: 2rem;
}
.progress-meta {
display: flex;
justify-content: space-between;
font-family: var(--mono);
font-size: 12px;
color: var(--muted);
margin-bottom: 8px;
}
.progress-track {
height: 3px;
background: var(--border);
position: relative;
}
.progress-fill {
height: 100%;
background: var(--accent);
transition: width 0.4s ease;
}
.module-dots {
display: flex;
gap: 6px;
margin-top: 10px;
}
.dot {
width: 8px; height: 8px;
border: 1px solid var(--border2);
background: transparent;
transition: all 0.2s;
}
.dot.done { background: var(--green); border-color: var(--green); }
.dot.active { background: var(--accent); border-color: var(--accent); }
.dot.wrong { background: var(--red); border-color: var(--red); }
/* ── Scenario card ── */
.scenario-label {
font-family: var(--mono);
font-size: 11px;
color: var(--muted);
letter-spacing: 0.08em;
margin-bottom: 12px;
}
.scenario-title {
font-family: var(--mono);
font-size: 17px;
font-weight: 500;
color: var(--text);
margin-bottom: 16px;
line-height: 1.4;
}
/* Simulated artifacts */
.artifact {
background: var(--surface);
border: 1px solid var(--border);
margin-bottom: 20px;
overflow: hidden;
}
.artifact-bar {
background: var(--surface2);
padding: 8px 12px;
display: flex;
align-items: center;
gap: 8px;
border-bottom: 1px solid var(--border);
}
.artifact-dots { display: flex; gap: 5px; }
.artifact-dot { width: 9px; height: 9px; border-radius: 50%; }
.artifact-dot:nth-child(1) { background: #e05c5c44; border: 1px solid #e05c5c66; }
.artifact-dot:nth-child(2) { background: #f0a84344; border: 1px solid #f0a84366; }
.artifact-dot:nth-child(3) { background: #4caf7d44; border: 1px solid #4caf7d66; }
.artifact-title { font-family: var(--mono); font-size: 11px; color: var(--muted); flex: 1; text-align: center; }
.artifact-body { padding: 1rem 1.25rem; font-size: 13px; line-height: 1.7; }
.artifact-body .field { display: flex; gap: 8px; margin-bottom: 4px; }
.artifact-body .field-label { color: var(--muted); min-width: 60px; font-family: var(--mono); font-size: 12px; }
.artifact-body .field-val { color: var(--text); }
.field-val.sus { color: var(--amber); }
.artifact-body .divider { border: none; border-top: 1px solid var(--border); margin: 10px 0; }
.artifact-body .body-text { color: #a0a8c0; }
.artifact-body .link { color: var(--accent); font-family: var(--mono); font-size: 12px; }
.artifact-body .link.sus { color: var(--amber); }
/* URL bar */
.url-bar {
display: flex;
align-items: center;
gap: 8px;
background: var(--surface2);
border: 1px solid var(--border2);
padding: 6px 12px;
margin-bottom: 14px;
font-family: var(--mono);
font-size: 12px;
}
.url-bar .lock { color: var(--muted); font-size: 13px; }
.url-text { color: var(--muted); }
.url-text .url-domain { color: var(--text); }
.url-text .url-sus { color: var(--amber); }
/* Terminal block */
.terminal {
background: #090b0f;
border: 1px solid var(--border);
padding: 1rem 1.25rem;
font-family: var(--mono);
font-size: 12px;
line-height: 1.8;
margin-bottom: 14px;
}
.terminal .prompt { color: var(--green); }
.terminal .cmd { color: var(--text); }
.terminal .out { color: var(--muted); }
.terminal .warn { color: var(--amber); }
/* Social post */
.social-post {
background: var(--surface);
border: 1px solid var(--border);
padding: 1rem 1.25rem;
margin-bottom: 14px;
}
.social-header { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.avatar { width: 36px; height: 36px; border-radius: 50%; background: var(--surface2); border: 1px solid var(--border2); display: flex; align-items: center; justify-content: center; font-family: var(--mono); font-size: 12px; color: var(--muted); }
.social-name { font-weight: 500; font-size: 13px; }
.social-handle { font-family: var(--mono); font-size: 11px; color: var(--muted); }
.social-text { font-size: 13px; color: var(--text); line-height: 1.6; }
.social-text .highlight { color: var(--amber); }
/* Call transcript */
.call-transcript {
background: var(--surface);
border: 1px solid var(--border);
padding: 1rem 1.25rem;
margin-bottom: 14px;
}
.call-header { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; font-family: var(--mono); font-size: 12px; color: var(--muted); }
.call-line { display: flex; gap: 10px; margin-bottom: 8px; font-size: 13px; line-height: 1.6; }
.call-who { font-family: var(--mono); font-size: 11px; min-width: 60px; padding-top: 2px; }
.call-who.caller { color: var(--red); }
.call-who.you { color: var(--accent); }
.call-text { color: var(--text); }
.call-text .sus { color: var(--amber); }
/* Download block */
.download-block {
background: var(--surface);
border: 1px solid var(--border);
padding: 1rem 1.25rem;
margin-bottom: 14px;
display: flex;
align-items: center;
gap: 14px;
}
.dl-icon { font-size: 28px; color: var(--muted); }
.dl-info { flex: 1; }
.dl-name { font-family: var(--mono); font-size: 13px; color: var(--text); margin-bottom: 3px; }
.dl-meta { font-family: var(--mono); font-size: 11px; color: var(--muted); }
.dl-meta .sus { color: var(--amber); }
.dl-btn { background: transparent; border: 1px solid var(--border2); color: var(--muted); padding: 6px 14px; font-family: var(--mono); font-size: 12px; cursor: default; }
/* Question */
.question-text {
font-size: 14px;
color: var(--text);
margin-bottom: 14px;
padding: 12px;
background: var(--surface2);
border-left: 3px solid var(--accent);
border-right: none; border-top: none; border-bottom: none;
}
/* Options */
.options { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
.option {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 12px 14px;
background: var(--surface);
border: 1px solid var(--border);
cursor: pointer;
transition: border-color 0.15s, background 0.15s;
font-size: 13px;
line-height: 1.5;
color: var(--text);
}
.option:hover { border-color: var(--border2); background: var(--surface2); }
.option.selected { border-color: var(--accent); background: var(--accent-dim); }
.option.correct { border-color: var(--green); background: var(--green-dim); }
.option.incorrect { border-color: var(--red); background: var(--red-dim); }
.option.reveal-correct { border-color: var(--green); background: var(--green-dim); }
.option-key {
font-family: var(--mono);
font-size: 11px;
color: var(--muted);
min-width: 20px;
margin-top: 1px;
}
.option.selected .option-key { color: var(--accent); }
.option.correct .option-key { color: var(--green); }
.option.incorrect .option-key { color: var(--red); }
.option.reveal-correct .option-key { color: var(--green); }
/* Feedback */
.feedback {
display: none;
padding: 14px;
border: 1px solid var(--border);
margin-bottom: 16px;
font-size: 13px;
line-height: 1.7;
}
.feedback.show { display: block; }
.feedback.pass { border-color: var(--green); background: var(--green-dim); color: #a8d8bc; }
.feedback.fail { border-color: var(--red); background: var(--red-dim); color: #d8a0a0; }
.feedback-head {
font-family: var(--mono);
font-size: 12px;
font-weight: 500;
margin-bottom: 6px;
display: flex;
align-items: center;
gap: 6px;
}
.feedback.pass .feedback-head { color: var(--green); }
.feedback.fail .feedback-head { color: var(--red); }
.ioc-list { margin-top: 10px; padding-top: 10px; border-top: 1px solid #ffffff11; }
.ioc-list-title { font-family: var(--mono); font-size: 11px; color: var(--muted); margin-bottom: 6px; letter-spacing: 0.06em; }
.ioc-item { display: flex; gap: 8px; margin-bottom: 4px; font-size: 12px; font-family: var(--mono); }
.ioc-item .ioc-k { color: var(--amber); min-width: 120px; }
/* Nav buttons */
.nav { display: flex; gap: 10px; margin-top: 8px; }
.btn {
font-family: var(--mono);
font-size: 12px;
padding: 9px 18px;
border: 1px solid var(--border2);
background: transparent;
color: var(--muted);
cursor: pointer;
transition: all 0.15s;
letter-spacing: 0.04em;
}
.btn:hover { background: var(--surface2); color: var(--text); }
.btn.primary { border-color: var(--accent); color: var(--accent); }
.btn.primary:hover { background: var(--accent-dim); }
.btn:disabled { opacity: 0.3; cursor: not-allowed; }
/* Results screen */
.results { display: none; }
.results.show { display: block; }
.results-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 10px;
margin: 1.5rem 0;
}
.stat-card {
background: var(--surface);
border: 1px solid var(--border);
padding: 1rem;
text-align: center;
}
.stat-num { font-family: var(--mono); font-size: 28px; font-weight: 500; color: var(--text); }
.stat-label { font-family: var(--mono); font-size: 11px; color: var(--muted); margin-top: 4px; letter-spacing: 0.06em; }
.topic-row {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 0;
border-bottom: 1px solid var(--border);
font-size: 13px;
}
.topic-name { flex: 1; color: var(--text); font-family: var(--mono); font-size: 12px; }
.topic-result { font-family: var(--mono); font-size: 12px; }
.topic-result.pass { color: var(--green); }
.topic-result.fail { color: var(--red); }
.verdict {
font-family: var(--mono);
font-size: 14px;
padding: 14px;
border: 1px solid var(--border);
margin-bottom: 1.5rem;
text-align: center;
letter-spacing: 0.03em;
}
.verdict.high { border-color: var(--green); color: var(--green); background: var(--green-dim); }
.verdict.mid { border-color: var(--amber); color: var(--amber); background: var(--amber-dim); }
.verdict.low { border-color: var(--red); color: var(--red); background: var(--red-dim); }
#quiz-screen { display: block; }
</style>
</head>
<body>
<div class="shell">
<div class="header">
<div class="header-top">
<span class="tag">CYBER SAFETY</span>
<span class="tag" style="color:var(--muted);background:var(--surface2);border-color:var(--border);">v1.0</span>
</div>
<h1>threat_awareness_module</h1>
<div class="subtitle">5 scenarios · interactive quiz · IOC breakdown</div>
</div>
<div id="quiz-screen">
<div class="progress-wrap">
<div class="progress-meta">
<span id="prog-label">module 1 of 5</span>
<span id="score-label">score: 0</span>
</div>
<div class="progress-track"><div class="progress-fill" id="prog-fill" style="width:0%"></div></div>
<div class="module-dots" id="dots"></div>
</div>
<div id="scenario-area"></div>
</div>
<div class="results" id="results-screen">
<div class="progress-meta" style="margin-bottom:1.5rem;">
<span style="font-family:var(--mono);font-size:13px;color:var(--muted);">session complete</span>
</div>
<h1 style="margin-bottom:1rem;">results</h1>
<div class="verdict" id="verdict-msg"></div>
<div class="results-grid" id="stat-cards"></div>
<div style="margin:1.5rem 0 8px;font-family:var(--mono);font-size:11px;color:var(--muted);letter-spacing:0.06em;">PER TOPIC</div>
<div id="topic-rows"></div>
<div class="nav" style="margin-top:1.5rem;">
<button class="btn primary" onclick="restart()">restart module</button>
<button class="btn" onclick="sendToClipboard()">copy score to clipboard</button>
</div>
</div>
</div>
<script>
const scenarios = [
{
topic: "Phishing email",
icon: "✉",
title: "You receive this email — is it legitimate?",
artifact: `
<div class="artifact">
<div class="artifact-bar">
<div class="artifact-dots"><div class="artifact-dot"></div><div class="artifact-dot"></div><div class="artifact-dot"></div></div>
<div class="artifact-title">Mail — Inbox</div>
</div>
<div class="artifact-body">
<div class="field"><span class="field-label">From:</span><span class="field-val sus">support@paypa1-secure.com</span></div>
<div class="field"><span class="field-label">To:</span><span class="field-val">you@email.com</span></div>
<div class="field"><span class="field-label">Subject:</span><span class="field-val">Urgent: Your account has been limited</span></div>
<hr class="divider">
<div class="body-text">
Dear valued customer,<br><br>
We have detected <strong style="color:var(--text)">unusual activity</strong> on your account. To avoid permanent suspension, you must verify your identity within <strong style="color:var(--amber)">24 hours</strong>.<br><br>
Click below to restore access:<br><br>
<span class="link sus">http://paypa1-secure.com/verify?token=8x92kA</span>
</div>
</div>
</div>`,
question: "What should you do with this email?",
options: [
"Click the link quickly — account suspension sounds urgent",
"Reply to ask if it's real before clicking anything",
"Mark as phishing/spam, do not click the link, and check your account directly via the official site",
"Forward to friends to warn them about PayPal issues"
],
correct: 2,
feedback: {
pass: "Correct. This is a textbook phishing email. Never act through links in unsolicited emails — go directly to the official site in a new browser tab.",
fail: "This is a phishing email. Even if the urgency feels real, that feeling is manufactured. Clicking the link could install malware or steal your credentials."
},
iocs: [
["Sender domain", "paypa1-secure.com (digit substitution — 'l' → '1')"],
["Urgency tactic", "24-hour deadline to pressure fast action"],
["Link domain", "Matches sender, not paypal.com"],
["Generic greeting", "'Dear valued customer' — no personalisation"]
]
},
{
topic: "Fake login page",
icon: "🔒",
title: "You click a link from a search result and land here",
artifact: `
<div class="url-bar">
<span class="lock">🔒</span>
<div class="url-text"><span class="url-muted">https://</span><span class="url-sus">netfliix.com</span><span class="url-muted">/login</span></div>
</div>
<div class="artifact">
<div class="artifact-bar">
<div class="artifact-dots"><div class="artifact-dot"></div><div class="artifact-dot"></div><div class="artifact-dot"></div></div>
<div class="artifact-title">Netflix — Sign In</div>
</div>
<div class="artifact-body" style="text-align:center;padding:1.5rem;">
<div style="font-family:var(--mono);font-size:22px;color:#e50914;margin-bottom:1.5rem;letter-spacing:-0.02em;">NETFLIX</div>
<input type="text" placeholder="Email or phone number" style="width:100%;max-width:300px;display:block;margin:0 auto 10px;padding:10px;background:#333;border:1px solid #444;color:#fff;font-size:14px;" readonly>
<input type="password" placeholder="Password" style="width:100%;max-width:300px;display:block;margin:0 auto 16px;padding:10px;background:#333;border:1px solid #444;color:#fff;font-size:14px;" readonly>
<button style="width:100%;max-width:300px;background:#e50914;color:#fff;border:none;padding:12px;font-size:15px;cursor:default;">Sign In</button>
</div>
</div>`,
question: "The page looks exactly like Netflix. What's the problem and what do you do?",
options: [
"It has HTTPS so it's safe — go ahead and sign in",
"The URL is 'netfliix.com' (double i) — this is a clone site, close the tab immediately",
"The design looks right so it must be fine — Netflix just updated their domain",
"Try signing in once to see if it works"
],
correct: 1,
feedback: {
pass: "Exactly right. HTTPS only means the connection is encrypted — it says nothing about whether the site itself is legitimate. Always verify the domain carefully before entering credentials.",
fail: "HTTPS does not make a site trustworthy — it only encrypts traffic. The domain 'netfliix.com' is a typosquat. Any credentials entered here go straight to the attacker."
},
iocs: [
["Domain", "netfliix.com — extra 'i', not netflix.com"],
["HTTPS misconception", "Certificate ≠ legitimacy. Attackers get free certs too"],
["Visual clone", "Pixel-perfect copies are trivial to build"],
["Search ads", "Attackers buy ads to rank above real sites"]
]
},
{
topic: "Suspicious download",
icon: "⬇",
title: "You want free video editing software — you find this",
artifact: `
<div class="download-block">
<div class="dl-icon">💾</div>
<div class="dl-info">
<div class="dl-name">Adobe_Premiere_Pro_2024_FULL_CRACK.exe</div>
<div class="dl-meta">Size: <span class="sus">47 MB</span> · Source: <span class="sus">free-software-downloads.net</span> · Downloads: 48,291</div>
</div>
<div class="dl-btn">Download</div>
</div>
<div class="terminal">
<div><span class="prompt">$</span> <span class="cmd">file Adobe_Premiere_Pro_2024_FULL_CRACK.exe</span></div>
<div class="out">Adobe_Premiere_Pro_2024_FULL_CRACK.exe: PE32 executable (GUI) Intel 80386</div>
<div><span class="prompt">$</span> <span class="cmd">strings Adobe_Premiere_Pro_2024_FULL_CRACK.exe | grep -i "http"</span></div>
<div class="warn">http://185.220.101.47/payload.bin</div>
<div class="warn">http://185.220.101.47/keylogger_config.json</div>
</div>`,
question: "You really need video editing software. What's the right move?",
options: [
"Download it — 48,000 people already did so it's probably fine",
"Run it in a VM first to check if it does anything bad",
"Use a legitimate free alternative like DaVinci Resolve, or the official Adobe free trial",
"Scan it with Windows Defender before running — that's enough protection"
],
correct: 2,
feedback: {
pass: "Right call. Cracked software is the single most common malware delivery vector. DaVinci Resolve is professional-grade and genuinely free — no compromise needed.",
fail: "Download counts are faked. Antivirus misses novel malware routinely. Running in a VM doesn't protect your host if the VM has shared folders or clipboard access. Cracked software = guaranteed risk."
},
iocs: [
["Filename pattern", "'CRACK', 'FULL', 'KEYGEN' in filenames are red flags"],
["Source domain", "Unofficial third-party site, not adobe.com"],
["Hidden network calls", "Strings reveal C2 server and keylogger config URLs"],
["Size anomaly", "Real Premiere is 2+ GB — 47 MB is a loader/dropper"]
]
},
{
topic: "Social engineering call",
icon: "📞",
title: "You get an unexpected phone call",
artifact: `
<div class="call-transcript">
<div class="call-header">📞 Incoming call · +91-80-XXXX-XXXX · "Microsoft Support"</div>
<div class="call-line">
<div class="call-who caller">CALLER</div>
<div class="call-text">Hello, this is Kevin from Microsoft. Our systems have detected that your Windows computer has been <span class="sus">infected with a critical virus</span> and is sending data to hackers. We need remote access to fix it immediately.</div>
</div>
<div class="call-line">
<div class="call-who you">YOU</div>
<div class="call-text">Oh no, that sounds serious...</div>
</div>
<div class="call-line">
<div class="call-who caller">CALLER</div>
<div class="call-text">Yes, very serious. Please open your browser and go to <span class="sus">anydesk.com</span> and install the software. Give me the 9-digit code shown, and <span class="sus">do not tell anyone we called</span> — it's a security protocol.</div>
</div>
</div>`,
question: "What is happening here and what do you do?",
options: [
"Follow the instructions — Microsoft really does monitor your PC for viruses",
"Ask the caller for their employee ID to verify before proceeding",
"Hang up immediately — Microsoft never makes unsolicited support calls",
"Install AnyDesk but don't give them the code until they prove who they are"
],
correct: 2,
feedback: {
pass: "Correct. Microsoft, Apple, Google — none of them cold-call users about infections. This is a classic tech support scam. Hanging up is the only correct action. There's nothing to verify; the whole premise is fraudulent.",
fail: "This is a vishing (voice phishing) attack. Asking for an employee ID just gives them time to make up one. Giving AnyDesk access hands them full control of your machine, regardless of what code you share."
},
iocs: [
["Unsolicited contact", "No legitimate company calls you first about your PC"],
["Urgency + fear", "'Critical virus', 'sending data to hackers' — manufactured panic"],
["Remote access request", "AnyDesk/TeamViewer access = full machine control"],
["Secrecy instruction", "'Do not tell anyone' is a classic social engineering marker"]
]
},
{
topic: "Oversharing online",
icon: "📢",
title: "Your colleague posts this on a public LinkedIn profile",
artifact: `
<div class="social-post">
<div class="social-header">
<div class="avatar">RK</div>
<div>
<div class="social-name">Rahul K.</div>
<div class="social-handle">@rahulk · Software Engineer at <span style="color:var(--accent)">FinanceCorpIndia</span> · Public</div>
</div>
</div>
<div class="social-text">
Just started at FinanceCorpIndia! Using <span class="highlight">Salesforce CRM</span> + <span class="highlight">Oracle DB on AWS</span> for our client management system. Our team of 12 is working on a <span class="highlight">payment gateway migration</span> this quarter — exciting stuff! Our office is at <span class="highlight">Prestige Tech Park, Bengaluru, Tower B, Floor 7</span>. Come say hi!
</div>
</div>`,
question: "What information in this post is a security risk, and why?",
options: [
"Nothing — LinkedIn is a professional network, it's fine to share work details",
"The location is the only risk; the tech stack info is harmless",
"The tech stack, tools, infrastructure details, and project info give attackers a precise map for targeted attacks",
"Only the company name matters — attackers already know everything else"
],
correct: 2,
feedback: {
pass: "Exactly. Every detail here reduces an attacker's reconnaissance effort: Salesforce + Oracle on AWS narrows the exploit surface; 'payment gateway migration' reveals a high-value target window; the floor number helps physical intrusion. This is an OSINT gold mine.",
fail: "This post is an OSINT gift. Knowing the exact tech stack means attackers can target known CVEs specifically. The project timeline tells them when systems are most vulnerable. 'Professional network' doesn't mean 'safe to overshare'."
},
iocs: [
["Tech stack disclosure", "Salesforce + Oracle + AWS → targeted CVE research"],
["Project timeline", "Migration windows = elevated vulnerability periods"],
["Physical location", "Floor + tower enables tailgating / physical intrusion"],
["Team size", "Helps attackers estimate attack surface and target lists"]
]
}
];
let current = 0;
let score = 0;
let answered = [];
let dotStates = Array(scenarios.length).fill('pending');
function initDots() {
const d = document.getElementById('dots');
d.innerHTML = scenarios.map((_,i) => `<div class="dot" id="dot-${i}"></div>`).join('');
updateDot(0, 'active');
}
function updateDot(i, state) {
const d = document.getElementById(`dot-${i}`);
if (!d) return;
d.className = `dot ${state}`;
dotStates[i] = state;
}
function updateProgress() {
const pct = Math.round((current / scenarios.length) * 100);
document.getElementById('prog-fill').style.width = pct + '%';
document.getElementById('prog-label').textContent = `module ${Math.min(current+1, scenarios.length)} of ${scenarios.length}`;
document.getElementById('score-label').textContent = `score: ${score}`;
}
function renderScenario() {
const s = scenarios[current];
updateProgress();
const keys = ['A', 'B', 'C', 'D'];
const optionsHTML = s.options.map((o, i) => `
<div class="option" id="opt-${i}" onclick="selectOption(${i})">
<span class="option-key">${keys[i]}</span>
<span>${o}</span>
</div>`).join('');
document.getElementById('scenario-area').innerHTML = `
<div class="scenario-label">SCENARIO ${current + 1} / ${scenarios.length} · ${s.topic.toUpperCase()}</div>
<div class="scenario-title">${s.title}</div>
${s.artifact}
<div class="question-text">${s.question}</div>
<div class="options" id="options-wrap">${optionsHTML}</div>
<div class="feedback" id="feedback-box">
<div class="feedback-head" id="feedback-head"></div>
<div id="feedback-text"></div>
<div class="ioc-list" id="ioc-list"></div>
</div>
<div class="nav">
<button class="btn primary" id="next-btn" onclick="nextScenario()" disabled>
${current < scenarios.length - 1 ? 'next scenario →' : 'see results →'}
</button>
</div>`;
}
function selectOption(i) {
const s = scenarios[current];
if (answered[current] !== undefined) return;
answered[current] = i;
const opts = document.querySelectorAll('.option');
opts.forEach(o => o.onclick = null);
const correct = i === s.correct;
if (correct) {
score++;
opts[i].className = 'option correct';
updateDot(current, 'done');
} else {
opts[i].className = 'option incorrect';
opts[s.correct].className = 'option reveal-correct';
updateDot(current, 'wrong');
}
const fb = document.getElementById('feedback-box');
const head = document.getElementById('feedback-head');
const text = document.getElementById('feedback-text');
const iocList = document.getElementById('ioc-list');
fb.className = `feedback show ${correct ? 'pass' : 'fail'}`;
head.textContent = correct ? '✓ correct' : '✗ incorrect';
text.textContent = correct ? s.feedback.pass : s.feedback.fail;
iocList.innerHTML = `
<div class="ioc-list-title">INDICATORS OF COMPROMISE / RED FLAGS</div>
${s.iocs.map(([k,v]) => `<div class="ioc-item"><span class="ioc-k">${k}</span><span>${v}</span></div>`).join('')}`;
document.getElementById('score-label').textContent = `score: ${score}`;
document.getElementById('next-btn').disabled = false;
}
function nextScenario() {
if (current < scenarios.length - 1) {
if (current + 1 < scenarios.length) updateDot(current + 1, 'active');
current++;
renderScenario();
window.scrollTo(0, 0);
} else {
showResults();
}
}
function showResults() {
document.getElementById('quiz-screen').style.display = 'none';
const rs = document.getElementById('results-screen');
rs.className = 'results show';
const pct = Math.round((score / scenarios.length) * 100);
const verdict = document.getElementById('verdict-msg');
if (pct >= 80) {
verdict.className = 'verdict high';
verdict.textContent = `strong performance — ${pct}% · you identified most threats correctly`;
} else if (pct >= 60) {
verdict.className = 'verdict mid';
verdict.textContent = `moderate — ${pct}% · review the scenarios you missed`;
} else {
verdict.className = 'verdict low';
verdict.textContent = `needs work — ${pct}% · re-read the IOC breakdowns and retry`;
}
document.getElementById('stat-cards').innerHTML = `
<div class="stat-card"><div class="stat-num">${score}/${scenarios.length}</div><div class="stat-label">CORRECT</div></div>
<div class="stat-card"><div class="stat-num">${pct}%</div><div class="stat-label">SCORE</div></div>
<div class="stat-card"><div class="stat-num">${scenarios.length - score}</div><div class="stat-label">MISSED</div></div>`;
document.getElementById('topic-rows').innerHTML = scenarios.map((s, i) => {
const pass = answered[i] === s.correct;
return `<div class="topic-row">
<span class="topic-name">${s.topic}</span>
<span class="topic-result ${pass ? 'pass' : 'fail'}">${pass ? '✓ correct' : '✗ missed'}</span>
</div>`;
}).join('');
window.scrollTo(0, 0);
}
function restart() {
current = 0; score = 0; answered = [];
dotStates = Array(scenarios.length).fill('pending');
document.getElementById('quiz-screen').style.display = 'block';
document.getElementById('results-screen').className = 'results';
initDots();
renderScenario();
window.scrollTo(0, 0);
}
function sendToClipboard() {
const pct = Math.round((score / scenarios.length) * 100);
const lines = [`Cyber Safety Module — Score: ${score}/${scenarios.length} (${pct}%)`];
scenarios.forEach((s, i) => {
lines.push(` ${answered[i] === s.correct ? '✓' : '✗'} ${s.topic}`);
});
navigator.clipboard.writeText(lines.join('\n')).catch(() => {});
}
initDots();
renderScenario();
</script>
</body>
</html>
For further actions, you may consider blocking this person and/or reporting abuse
Top comments (0)