{"ast":null,"code":"var _jsxFileName = \"/Users/davidl/Nextcloud/Uni/Swinburne/ICT30016 - ICT Project/Assignment 3/incident-response-game/src/App.jsx\",\n _s = $RefreshSig$();\nimport React, { useState, useEffect } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { Shield, Skull, RefreshCw, HelpCircle, Info, Sparkles } from \"lucide-react\";\n\n/**\n * Cybersecurity Incident Response – Card Game (Web App Prototype)\n * Tailwind CSS v4: ensure src/index.css contains only: @import \"tailwindcss\";\n */\n\n// --- MITRE ATT&CK references ---\nimport { jsxDEV as _jsxDEV } from \"react/jsx-dev-runtime\";\nconst MITRE = {\n phishing: {\n name: \"Phishing\",\n tactic: \"Initial Access\",\n id: \"T1566\"\n },\n malware: {\n name: \"Malware\",\n tactic: \"Execution\",\n id: \"T1204\"\n },\n privEsc: {\n name: \"Privilege Escalation\",\n tactic: \"Privilege Escalation\",\n id: \"TA0004\"\n },\n lateral: {\n name: \"Lateral Movement\",\n tactic: \"Lateral Movement\",\n id: \"TA0008\"\n },\n exfil: {\n name: \"Data Exfiltration\",\n tactic: \"Exfiltration\",\n id: \"TA0010\"\n }\n};\n\n// --- Attack deck ---\nconst ATTACK_DECK = [{\n id: \"A1\",\n title: \"Phishing Email\",\n description: \"A convincing email attempts to trick a user into entering credentials.\",\n mitreTag: \"phishing\",\n difficulty: 1,\n counterTags: [\"report\", \"mfa\", \"blockSender\", \"awareness\"]\n}, {\n id: \"A2\",\n title: \"Malware Dropper\",\n description: \"A user executes a downloaded attachment that launches a dropper.\",\n mitreTag: \"malware\",\n difficulty: 2,\n counterTags: [\"edr\", \"isolateHost\", \"patch\"]\n}, {\n id: \"A3\",\n title: \"Privilege Escalation\",\n description: \"Attacker abuses a vulnerable service to gain admin rights.\",\n mitreTag: \"privEsc\",\n difficulty: 2,\n counterTags: [\"patch\", \"leastPrivilege\", \"audit\"]\n}, {\n id: \"A4\",\n title: \"Lateral Movement\",\n description: \"Compromised account used to pivot through the network.\",\n mitreTag: \"lateral\",\n difficulty: 3,\n counterTags: [\"networkSeg\", \"isolateHost\", \"resetCreds\", \"monitor\"]\n}, {\n id: \"A5\",\n title: \"Data Exfiltration\",\n description: \"Sensitive data is staged and sent out of the organisation.\",\n mitreTag: \"exfil\",\n difficulty: 3,\n counterTags: [\"dlp\", \"isolateHost\", \"blockEgress\", \"monitor\"]\n}];\n\n// --- Defence pool ---\nconst DEFENCE_POOL = [{\n id: \"D1\",\n title: \"Report to IT\",\n cost: 2,\n tags: [\"report\", \"awareness\"],\n description: \"Escalate suspicious activity to IT/security.\"\n}, {\n id: \"D2\",\n title: \"Enable MFA\",\n cost: 3,\n tags: [\"mfa\"],\n description: \"Require multi-factor authentication for access.\"\n}, {\n id: \"D3\",\n title: \"Patch Systems\",\n cost: 4,\n tags: [\"patch\"],\n description: \"Apply vendor patches to remediate vulnerabilities.\"\n}, {\n id: \"D4\",\n title: \"Endpoint Detection & Response\",\n cost: 5,\n tags: [\"edr\", \"monitor\"],\n description: \"Detect, contain, and remediate malicious processes.\"\n}, {\n id: \"D5\",\n title: \"Isolate Host\",\n cost: 6,\n tags: [\"isolateHost\"],\n description: \"Remove a device from the network to stop spread.\"\n}, {\n id: \"D6\",\n title: \"Network Segmentation\",\n cost: 5,\n tags: [\"networkSeg\"],\n description: \"Restrict lateral movement with segmented zones.\"\n}, {\n id: \"D7\",\n title: \"Reset Credentials\",\n cost: 3,\n tags: [\"resetCreds\"],\n description: \"Force password reset and revoke sessions.\"\n}, {\n id: \"D8\",\n title: \"DLP Policy\",\n cost: 5,\n tags: [\"dlp\"],\n description: \"Detect and prevent exfiltration of sensitive data.\"\n}, {\n id: \"D9\",\n title: \"Block Egress Rule\",\n cost: 4,\n tags: [\"blockEgress\"],\n description: \"Block suspicious outbound connections at firewall.\"\n}, {\n id: \"D10\",\n title: \"Block Sender/Domain\",\n cost: 2,\n tags: [\"blockSender\"],\n description: \"Block malicious sender to prevent recurrences.\"\n}, {\n id: \"D11\",\n title: \"Audit & Logging\",\n cost: 2,\n tags: [\"audit\", \"monitor\"],\n description: \"Increase logging and review to detect anomalies.\"\n}, {\n id: \"D12\",\n title: \"Reimage & Network Scan\",\n cost: 6,\n tags: [\"isolateHost\", \"monitor\"],\n description: \"Rebuild host and scan for indicators of compromise.\"\n}, {\n id: \"D13\",\n title: \"Least Privilege\",\n cost: 3,\n tags: [\"leastPrivilege\"],\n description: \"Reduce account/device permissions to minimum.\"\n}];\n\n// --- Helpers ---\nfunction pickN(arr, n) {\n const copy = [...arr];\n const out = [];\n while (out.length < n && copy.length) {\n const idx = Math.floor(Math.random() * copy.length);\n out.push(copy.splice(idx, 1)[0]);\n }\n return out;\n}\nfunction defenceEffect(attack, defence) {\n const overlap = defence.tags.some(t => attack.counterTags.includes(t));\n if (overlap) return {\n result: \"mitigate\",\n score: 2\n };\n if (defence.tags.includes(\"monitor\") || defence.tags.includes(\"audit\")) {\n return {\n result: \"reduce\",\n score: 1\n };\n }\n return {\n result: \"fail\",\n score: 0\n };\n}\nfunction mitreBadge(tagKey) {\n const tag = MITRE[tagKey];\n if (!tag) return null;\n return /*#__PURE__*/_jsxDEV(\"span\", {\n className: \"text-xs px-2 py-1 rounded-full bg-slate-100 border font-medium\",\n children: [tag.name, \" \\u2022 \", tag.tactic, \" (\", tag.id, \")\"]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 104,\n columnNumber: 5\n }, this);\n}\nfunction Card({\n title,\n children,\n icon,\n accent = \"\",\n onClick,\n disabled\n}) {\n return /*#__PURE__*/_jsxDEV(\"button\", {\n onClick: onClick,\n disabled: disabled,\n className: `group relative text-left w-full ${onClick ? \"hover:-translate-y-0.5 active:translate-y-0\" : \"\"} transition disabled:opacity-50`,\n children: /*#__PURE__*/_jsxDEV(\"div\", {\n className: `rounded-2xl shadow p-4 border ${accent} bg-white`,\n children: [/*#__PURE__*/_jsxDEV(\"div\", {\n className: \"flex items-center gap-2 mb-2\",\n children: [icon, /*#__PURE__*/_jsxDEV(\"h3\", {\n className: \"font-semibold text-slate-800\",\n children: title\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 120,\n columnNumber: 11\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 118,\n columnNumber: 9\n }, this), /*#__PURE__*/_jsxDEV(\"div\", {\n className: \"text-sm text-slate-600\",\n children: children\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 122,\n columnNumber: 9\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 117,\n columnNumber: 7\n }, this)\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 112,\n columnNumber: 5\n }, this);\n}\n\n// --- Main App ---\n_c = Card;\nexport default function App() {\n _s();\n const [round, setRound] = useState(1);\n const [ap, setAp] = useState(10);\n const [score, setScore] = useState(0);\n const [attacksLeft, setAttacksLeft] = useState(5);\n const [currentAttack, setCurrentAttack] = useState(null);\n const [hand, setHand] = useState([]);\n const [log, setLog] = useState([]);\n const [showHelp, setShowHelp] = useState(false);\n const drawAttack = () => ATTACK_DECK[Math.floor(Math.random() * ATTACK_DECK.length)];\n function drawHandForAttack(attack, size = 5) {\n for (let i = 0; i < 25; i++) {\n const h = pickN(DEFENCE_POOL, size);\n const viable = h.some(d => d.tags.some(t => attack.counterTags.includes(t)));\n if (viable) return h;\n }\n const forced = pickN(DEFENCE_POOL, size - 1);\n const must = DEFENCE_POOL.find(d => d.tags.some(t => attack.counterTags.includes(t)));\n return [must, ...forced].slice(0, size);\n }\n useEffect(() => {\n if (!currentAttack) {\n const atk = drawAttack();\n setCurrentAttack(atk);\n setHand(drawHandForAttack(atk));\n }\n }, [currentAttack]);\n function playDefence(card) {\n if (card.cost > ap) return;\n setAp(v => v - card.cost);\n const outcome = defenceEffect(currentAttack, card);\n setScore(s => s + outcome.score);\n setLog(l => [{\n ts: Date.now(),\n text: `${card.title} (${card.cost} AP) → ${outcome.result.toUpperCase()} (+${outcome.score})`\n }, ...l]);\n const nextAttacks = attacksLeft - (outcome.result === \"mitigate\" ? 1 : 0);\n setAttacksLeft(nextAttacks);\n if (nextAttacks <= 0 || ap - card.cost <= 0) {\n return;\n }\n const atk = drawAttack();\n setCurrentAttack(atk);\n setHand(drawHandForAttack(atk));\n }\n function newRound() {\n setRound(r => r + 1);\n setAp(10);\n setAttacksLeft(5);\n setCurrentAttack(null);\n setHand([]);\n setLog([]);\n }\n const roundEnded = attacksLeft <= 0 || ap <= 0;\n return /*#__PURE__*/_jsxDEV(\"div\", {\n className: \"min-h-screen bg-slate-50 p-6\",\n children: [/*#__PURE__*/_jsxDEV(\"div\", {\n className: \"max-w-6xl mx-auto space-y-6\",\n children: [/*#__PURE__*/_jsxDEV(\"header\", {\n className: \"flex items-center justify-between\",\n children: [/*#__PURE__*/_jsxDEV(\"div\", {\n className: \"flex items-center gap-3\",\n children: [/*#__PURE__*/_jsxDEV(Shield, {\n className: \"w-6 h-6\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 199,\n columnNumber: 13\n }, this), /*#__PURE__*/_jsxDEV(\"h1\", {\n className: \"text-2xl font-bold\",\n children: \"Incident Response \\u2013 Card Game\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 200,\n columnNumber: 13\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 198,\n columnNumber: 11\n }, this), /*#__PURE__*/_jsxDEV(\"div\", {\n className: \"flex items-center gap-2\",\n children: [/*#__PURE__*/_jsxDEV(\"button\", {\n onClick: () => setShowHelp(true),\n className: \"inline-flex items-center gap-1 text-sm px-3 py-2 rounded-xl bg-white border shadow-sm hover:bg-slate-50\",\n children: [/*#__PURE__*/_jsxDEV(HelpCircle, {\n className: \"w-4 h-4\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 204,\n columnNumber: 15\n }, this), \" How to Play\"]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 203,\n columnNumber: 13\n }, this), /*#__PURE__*/_jsxDEV(\"button\", {\n onClick: newRound,\n className: \"inline-flex items-center gap-1 text-sm px-3 py-2 rounded-xl bg-white border shadow-sm hover:bg-slate-50\",\n children: [/*#__PURE__*/_jsxDEV(RefreshCw, {\n className: \"w-4 h-4\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 207,\n columnNumber: 15\n }, this), \" New Round\"]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 206,\n columnNumber: 13\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 202,\n columnNumber: 11\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 197,\n columnNumber: 9\n }, this), /*#__PURE__*/_jsxDEV(\"section\", {\n className: \"grid grid-cols-1 md:grid-cols-3 gap-4\",\n children: [/*#__PURE__*/_jsxDEV(\"div\", {\n className: \"md:col-span-2 space-y-4\",\n children: [/*#__PURE__*/_jsxDEV(\"div\", {\n className: \"grid grid-cols-3 gap-3\",\n children: [/*#__PURE__*/_jsxDEV(\"div\", {\n className: \"rounded-2xl bg-white border p-4 shadow-sm\",\n children: [/*#__PURE__*/_jsxDEV(\"div\", {\n className: \"text-xs text-slate-500\",\n children: \"Round\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 217,\n columnNumber: 17\n }, this), /*#__PURE__*/_jsxDEV(\"div\", {\n className: \"text-2xl font-semibold\",\n children: round\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 218,\n columnNumber: 17\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 216,\n columnNumber: 15\n }, this), /*#__PURE__*/_jsxDEV(\"div\", {\n className: \"rounded-2xl bg-white border p-4 shadow-sm\",\n children: [/*#__PURE__*/_jsxDEV(\"div\", {\n className: \"text-xs text-slate-500\",\n children: \"Action Points\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 221,\n columnNumber: 17\n }, this), /*#__PURE__*/_jsxDEV(\"div\", {\n className: \"text-2xl font-semibold\",\n children: ap\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 222,\n columnNumber: 17\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 220,\n columnNumber: 15\n }, this), /*#__PURE__*/_jsxDEV(\"div\", {\n className: \"rounded-2xl bg-white border p-4 shadow-sm\",\n children: [/*#__PURE__*/_jsxDEV(\"div\", {\n className: \"text-xs text-slate-500\",\n children: \"Score\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 225,\n columnNumber: 17\n }, this), /*#__PURE__*/_jsxDEV(\"div\", {\n className: \"text-2xl font-semibold\",\n children: score\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 226,\n columnNumber: 17\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 224,\n columnNumber: 15\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 215,\n columnNumber: 13\n }, this), /*#__PURE__*/_jsxDEV(\"div\", {\n className: \"rounded-2xl bg-white border p-5 shadow-sm\",\n children: [/*#__PURE__*/_jsxDEV(\"div\", {\n className: \"flex items-center justify-between mb-3\",\n children: [/*#__PURE__*/_jsxDEV(\"div\", {\n className: \"flex items-center gap-2\",\n children: [/*#__PURE__*/_jsxDEV(Skull, {\n className: \"w-5 h-5\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 234,\n columnNumber: 19\n }, this), /*#__PURE__*/_jsxDEV(\"h2\", {\n className: \"font-semibold\",\n children: \"Current Attack\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 235,\n columnNumber: 19\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 233,\n columnNumber: 17\n }, this), /*#__PURE__*/_jsxDEV(\"span\", {\n className: \"text-xs text-slate-500\",\n children: [\"Attacks remaining this round: \", attacksLeft]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 237,\n columnNumber: 17\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 232,\n columnNumber: 15\n }, this), currentAttack && /*#__PURE__*/_jsxDEV(\"div\", {\n className: \"space-y-3\",\n children: [/*#__PURE__*/_jsxDEV(\"div\", {\n className: \"flex flex-wrap items-center gap-2\",\n children: [mitreBadge(currentAttack.mitreTag), /*#__PURE__*/_jsxDEV(\"span\", {\n className: \"text-xs px-2 py-1 rounded-full bg-amber-100 text-amber-800\",\n children: [\"Difficulty \", currentAttack.difficulty]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 244,\n columnNumber: 21\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 242,\n columnNumber: 19\n }, this), /*#__PURE__*/_jsxDEV(\"h3\", {\n className: \"text-lg font-semibold\",\n children: currentAttack.title\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 246,\n columnNumber: 19\n }, this), /*#__PURE__*/_jsxDEV(\"p\", {\n className: \"text-sm text-slate-600\",\n children: currentAttack.description\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 247,\n columnNumber: 19\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 241,\n columnNumber: 17\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 231,\n columnNumber: 13\n }, this), /*#__PURE__*/_jsxDEV(\"div\", {\n className: \"space-y-2\",\n children: [/*#__PURE__*/_jsxDEV(\"div\", {\n className: \"flex items-center gap-2\",\n children: [/*#__PURE__*/_jsxDEV(Shield, {\n className: \"w-4 h-4\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 255,\n columnNumber: 17\n }, this), /*#__PURE__*/_jsxDEV(\"h2\", {\n className: \"font-semibold\",\n children: \"Your Defence Hand\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 256,\n columnNumber: 17\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 254,\n columnNumber: 15\n }, this), /*#__PURE__*/_jsxDEV(\"div\", {\n className: \"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\",\n children: hand.map(card => /*#__PURE__*/_jsxDEV(Card, {\n title: `${card.title} • ${card.cost} AP`,\n icon: /*#__PURE__*/_jsxDEV(Shield, {\n className: \"w-4 h-4\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 263,\n columnNumber: 27\n }, this),\n onClick: !roundEnded ? () => playDefence(card) : undefined,\n disabled: roundEnded || card.cost > ap,\n accent: card.cost > ap ? \"border-slate-200\" : \"border-slate-200 hover:border-emerald-300\",\n children: [/*#__PURE__*/_jsxDEV(\"div\", {\n className: \"text-xs mb-2\",\n children: card.description\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 268,\n columnNumber: 21\n }, this), /*#__PURE__*/_jsxDEV(\"div\", {\n className: \"flex flex-wrap gap-1\",\n children: card.tags.map(t => /*#__PURE__*/_jsxDEV(\"span\", {\n className: \"text-[10px] px-2 py-0.5 rounded-full bg-slate-100 border\",\n children: t\n }, t, false, {\n fileName: _jsxFileName,\n lineNumber: 271,\n columnNumber: 25\n }, this))\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 269,\n columnNumber: 21\n }, this)]\n }, card.id, true, {\n fileName: _jsxFileName,\n lineNumber: 260,\n columnNumber: 19\n }, this))\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 258,\n columnNumber: 15\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 253,\n columnNumber: 13\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 213,\n columnNumber: 11\n }, this), /*#__PURE__*/_jsxDEV(\"div\", {\n className: \"space-y-3\",\n children: [/*#__PURE__*/_jsxDEV(\"div\", {\n className: \"rounded-2xl bg-white border p-4 shadow-sm\",\n children: [/*#__PURE__*/_jsxDEV(\"div\", {\n className: \"flex items-center gap-2 mb-2\",\n children: [/*#__PURE__*/_jsxDEV(Info, {\n className: \"w-4 h-4\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 286,\n columnNumber: 17\n }, this), /*#__PURE__*/_jsxDEV(\"h3\", {\n className: \"font-semibold\",\n children: \"Event Log\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 287,\n columnNumber: 17\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 285,\n columnNumber: 15\n }, this), /*#__PURE__*/_jsxDEV(\"div\", {\n className: \"space-y-2 max-h-80 overflow-auto pr-2\",\n children: [log.length === 0 && /*#__PURE__*/_jsxDEV(\"p\", {\n className: \"text-sm text-slate-500\",\n children: \"No actions yet. Play a defence card to respond.\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 290,\n columnNumber: 38\n }, this), log.map(e => /*#__PURE__*/_jsxDEV(\"div\", {\n className: \"text-sm text-slate-700\",\n children: [new Date(e.ts).toLocaleTimeString(), \" \\u2014 \", e.text]\n }, e.ts, true, {\n fileName: _jsxFileName,\n lineNumber: 292,\n columnNumber: 19\n }, this))]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 289,\n columnNumber: 15\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 284,\n columnNumber: 13\n }, this), /*#__PURE__*/_jsxDEV(\"div\", {\n className: \"rounded-2xl bg-gradient-to-br from-emerald-50 to-slate-50 border p-4 shadow-sm text-center\",\n children: roundEnded ? /*#__PURE__*/_jsxDEV(\"div\", {\n className: \"space-y-2\",\n children: [/*#__PURE__*/_jsxDEV(\"div\", {\n className: \"inline-flex items-center gap-2 text-emerald-700\",\n children: [/*#__PURE__*/_jsxDEV(Sparkles, {\n className: \"w-4 h-4\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 304,\n columnNumber: 21\n }, this), \" \", /*#__PURE__*/_jsxDEV(\"b\", {\n children: \"Round Complete\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 304,\n columnNumber: 54\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 303,\n columnNumber: 19\n }, this), /*#__PURE__*/_jsxDEV(\"div\", {\n className: \"text-sm text-slate-600\",\n children: [\"Score this round: \", /*#__PURE__*/_jsxDEV(\"b\", {\n children: score\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 307,\n columnNumber: 39\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 306,\n columnNumber: 19\n }, this), /*#__PURE__*/_jsxDEV(\"button\", {\n onClick: newRound,\n className: \"mt-2 inline-flex items-center gap-1 text-sm px-3 py-2 rounded-xl bg-emerald-600 text-white hover:bg-emerald-700\",\n children: \"Start Next Round\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 309,\n columnNumber: 19\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 302,\n columnNumber: 17\n }, this) : /*#__PURE__*/_jsxDEV(\"div\", {\n className: \"text-sm text-slate-600\",\n children: \"Mitigate attacks efficiently and conserve AP for tougher threats.\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 317,\n columnNumber: 17\n }, this)\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 300,\n columnNumber: 13\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 283,\n columnNumber: 11\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 212,\n columnNumber: 9\n }, this), /*#__PURE__*/_jsxDEV(\"footer\", {\n className: \"text-xs text-slate-500 pt-4\",\n children: /*#__PURE__*/_jsxDEV(\"p\", {\n children: \"MITRE ATT&CK tags are indicative and simplified for learning. This prototype focuses on accessibility across skill levels and resource-based decision making.\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 324,\n columnNumber: 11\n }, this)\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 323,\n columnNumber: 9\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 196,\n columnNumber: 7\n }, this), /*#__PURE__*/_jsxDEV(AnimatePresence, {\n children: showHelp && /*#__PURE__*/_jsxDEV(motion.div, {\n initial: {\n opacity: 0\n },\n animate: {\n opacity: 1\n },\n exit: {\n opacity: 0\n },\n className: \"fixed inset-0 bg-black/30 backdrop-blur-sm flex items-center justify-center p-4 z-50\",\n children: /*#__PURE__*/_jsxDEV(motion.div, {\n initial: {\n scale: 0.95,\n opacity: 0\n },\n animate: {\n scale: 1,\n opacity: 1\n },\n exit: {\n scale: 0.95,\n opacity: 0\n },\n className: \"max-w-xl w-full bg-white rounded-2xl shadow-2xl border p-6 space-y-3\",\n children: [/*#__PURE__*/_jsxDEV(\"div\", {\n className: \"flex items-center justify-between\",\n children: [/*#__PURE__*/_jsxDEV(\"div\", {\n className: \"flex items-center gap-2\",\n children: [/*#__PURE__*/_jsxDEV(HelpCircle, {\n className: \"w-5 h-5\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 348,\n columnNumber: 19\n }, this), /*#__PURE__*/_jsxDEV(\"h3\", {\n className: \"font-semibold\",\n children: \"How to Play\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 349,\n columnNumber: 19\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 347,\n columnNumber: 17\n }, this), /*#__PURE__*/_jsxDEV(\"button\", {\n onClick: () => setShowHelp(false),\n className: \"text-slate-500 hover:text-slate-700\",\n children: \"Close\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 351,\n columnNumber: 17\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 346,\n columnNumber: 15\n }, this), /*#__PURE__*/_jsxDEV(\"ol\", {\n className: \"list-decimal pl-5 text-sm space-y-2 text-slate-700\",\n children: [/*#__PURE__*/_jsxDEV(\"li\", {\n children: [\"The computer plays an \", /*#__PURE__*/_jsxDEV(\"b\", {\n children: \"Attack\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 356,\n columnNumber: 43\n }, this), \". Review its MITRE tag and description.\"]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 356,\n columnNumber: 17\n }, this), /*#__PURE__*/_jsxDEV(\"li\", {\n children: [\"You have \", /*#__PURE__*/_jsxDEV(\"b\", {\n children: \"10 Action Points\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 357,\n columnNumber: 30\n }, this), \" (AP) to spend each round. Each defence costs AP.\"]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 357,\n columnNumber: 17\n }, this), /*#__PURE__*/_jsxDEV(\"li\", {\n children: [\"Play a \", /*#__PURE__*/_jsxDEV(\"b\", {\n children: \"Defence\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 359,\n columnNumber: 26\n }, this), \". If its tags match the attack\\u2019s counters, you \", /*#__PURE__*/_jsxDEV(\"b\", {\n children: \"mitigate\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 359,\n columnNumber: 87\n }, this), \" it (+2 points). Generic controls may \", /*#__PURE__*/_jsxDEV(\"b\", {\n children: \"reduce\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 359,\n columnNumber: 140\n }, this), \" impact (+1). Otherwise it \", /*#__PURE__*/_jsxDEV(\"b\", {\n children: \"fails\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 359,\n columnNumber: 180\n }, this), \" (0).\"]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 358,\n columnNumber: 17\n }, this), /*#__PURE__*/_jsxDEV(\"li\", {\n children: \"Mitigated attacks advance the round. When AP hits 0 or you clear all attacks, the round ends. Start a new round anytime.\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 361,\n columnNumber: 17\n }, this), /*#__PURE__*/_jsxDEV(\"li\", {\n children: \"Learn strategically: conserve AP for harder threats and combine monitoring with containment.\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 364,\n columnNumber: 17\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 355,\n columnNumber: 15\n }, this), /*#__PURE__*/_jsxDEV(\"div\", {\n className: \"text-xs text-slate-500\",\n children: \"Designed for classrooms/workshops; quick rounds target 15\\u201320 minutes with discussion.\"\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 368,\n columnNumber: 15\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 340,\n columnNumber: 13\n }, this)\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 334,\n columnNumber: 11\n }, this)\n }, void 0, false, {\n fileName: _jsxFileName,\n lineNumber: 332,\n columnNumber: 7\n }, this)]\n }, void 0, true, {\n fileName: _jsxFileName,\n lineNumber: 195,\n columnNumber: 5\n }, this);\n}\n_s(App, \"9/DowfyYaAy9fzb8ESi04nHjjD0=\");\n_c2 = App;\nvar _c, _c2;\n$RefreshReg$(_c, \"Card\");\n$RefreshReg$(_c2, \"App\");","map":{"version":3,"names":["React","useState","useEffect","motion","AnimatePresence","Shield","Skull","RefreshCw","HelpCircle","Info","Sparkles","jsxDEV","_jsxDEV","MITRE","phishing","name","tactic","id","malware","privEsc","lateral","exfil","ATTACK_DECK","title","description","mitreTag","difficulty","counterTags","DEFENCE_POOL","cost","tags","pickN","arr","n","copy","out","length","idx","Math","floor","random","push","splice","defenceEffect","attack","defence","overlap","some","t","includes","result","score","mitreBadge","tagKey","tag","className","children","fileName","_jsxFileName","lineNumber","columnNumber","Card","icon","accent","onClick","disabled","_c","App","_s","round","setRound","ap","setAp","setScore","attacksLeft","setAttacksLeft","currentAttack","setCurrentAttack","hand","setHand","log","setLog","showHelp","setShowHelp","drawAttack","drawHandForAttack","size","i","h","viable","d","forced","must","find","slice","atk","playDefence","card","v","outcome","s","l","ts","Date","now","text","toUpperCase","nextAttacks","newRound","r","roundEnded","map","undefined","e","toLocaleTimeString","div","initial","opacity","animate","exit","scale","_c2","$RefreshReg$"],"sources":["/Users/davidl/Nextcloud/Uni/Swinburne/ICT30016 - ICT Project/Assignment 3/incident-response-game/src/App.jsx"],"sourcesContent":["import React, { useState, useEffect } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { Shield, Skull, RefreshCw, HelpCircle, Info, Sparkles } from \"lucide-react\";\n\n/**\n * Cybersecurity Incident Response – Card Game (Web App Prototype)\n * Tailwind CSS v4: ensure src/index.css contains only: @import \"tailwindcss\";\n */\n\n// --- MITRE ATT&CK references ---\nconst MITRE = {\n phishing: { name: \"Phishing\", tactic: \"Initial Access\", id: \"T1566\" },\n malware: { name: \"Malware\", tactic: \"Execution\", id: \"T1204\" },\n privEsc: { name: \"Privilege Escalation\", tactic: \"Privilege Escalation\", id: \"TA0004\" },\n lateral: { name: \"Lateral Movement\", tactic: \"Lateral Movement\", id: \"TA0008\" },\n exfil: { name: \"Data Exfiltration\", tactic: \"Exfiltration\", id: \"TA0010\" },\n};\n\n// --- Attack deck ---\nconst ATTACK_DECK = [\n {\n id: \"A1\",\n title: \"Phishing Email\",\n description: \"A convincing email attempts to trick a user into entering credentials.\",\n mitreTag: \"phishing\",\n difficulty: 1,\n counterTags: [\"report\", \"mfa\", \"blockSender\", \"awareness\"],\n },\n {\n id: \"A2\",\n title: \"Malware Dropper\",\n description: \"A user executes a downloaded attachment that launches a dropper.\",\n mitreTag: \"malware\",\n difficulty: 2,\n counterTags: [\"edr\", \"isolateHost\", \"patch\"],\n },\n {\n id: \"A3\",\n title: \"Privilege Escalation\",\n description: \"Attacker abuses a vulnerable service to gain admin rights.\",\n mitreTag: \"privEsc\",\n difficulty: 2,\n counterTags: [\"patch\", \"leastPrivilege\", \"audit\"],\n },\n {\n id: \"A4\",\n title: \"Lateral Movement\",\n description: \"Compromised account used to pivot through the network.\",\n mitreTag: \"lateral\",\n difficulty: 3,\n counterTags: [\"networkSeg\", \"isolateHost\", \"resetCreds\", \"monitor\"],\n },\n {\n id: \"A5\",\n title: \"Data Exfiltration\",\n description: \"Sensitive data is staged and sent out of the organisation.\",\n mitreTag: \"exfil\",\n difficulty: 3,\n counterTags: [\"dlp\", \"isolateHost\", \"blockEgress\", \"monitor\"],\n },\n];\n\n// --- Defence pool ---\nconst DEFENCE_POOL = [\n { id: \"D1\", title: \"Report to IT\", cost: 2, tags: [\"report\", \"awareness\"], description: \"Escalate suspicious activity to IT/security.\" },\n { id: \"D2\", title: \"Enable MFA\", cost: 3, tags: [\"mfa\"], description: \"Require multi-factor authentication for access.\" },\n { id: \"D3\", title: \"Patch Systems\", cost: 4, tags: [\"patch\"], description: \"Apply vendor patches to remediate vulnerabilities.\" },\n { id: \"D4\", title: \"Endpoint Detection & Response\", cost: 5, tags: [\"edr\", \"monitor\"], description: \"Detect, contain, and remediate malicious processes.\" },\n { id: \"D5\", title: \"Isolate Host\", cost: 6, tags: [\"isolateHost\"], description: \"Remove a device from the network to stop spread.\" },\n { id: \"D6\", title: \"Network Segmentation\", cost: 5, tags: [\"networkSeg\"], description: \"Restrict lateral movement with segmented zones.\" },\n { id: \"D7\", title: \"Reset Credentials\", cost: 3, tags: [\"resetCreds\"], description: \"Force password reset and revoke sessions.\" },\n { id: \"D8\", title: \"DLP Policy\", cost: 5, tags: [\"dlp\"], description: \"Detect and prevent exfiltration of sensitive data.\" },\n { id: \"D9\", title: \"Block Egress Rule\", cost: 4, tags: [\"blockEgress\"], description: \"Block suspicious outbound connections at firewall.\" },\n { id: \"D10\", title: \"Block Sender/Domain\", cost: 2, tags: [\"blockSender\"], description: \"Block malicious sender to prevent recurrences.\" },\n { id: \"D11\", title: \"Audit & Logging\", cost: 2, tags: [\"audit\", \"monitor\"], description: \"Increase logging and review to detect anomalies.\" },\n { id: \"D12\", title: \"Reimage & Network Scan\", cost: 6, tags: [\"isolateHost\", \"monitor\"], description: \"Rebuild host and scan for indicators of compromise.\" },\n { id: \"D13\", title: \"Least Privilege\", cost: 3, tags: [\"leastPrivilege\"], description: \"Reduce account/device permissions to minimum.\" },\n];\n\n// --- Helpers ---\nfunction pickN(arr, n) {\n const copy = [...arr];\n const out = [];\n while (out.length < n && copy.length) {\n const idx = Math.floor(Math.random() * copy.length);\n out.push(copy.splice(idx, 1)[0]);\n }\n return out;\n}\n\nfunction defenceEffect(attack, defence) {\n const overlap = defence.tags.some((t) => attack.counterTags.includes(t));\n if (overlap) return { result: \"mitigate\", score: 2 };\n if (defence.tags.includes(\"monitor\") || defence.tags.includes(\"audit\")) {\n return { result: \"reduce\", score: 1 };\n }\n return { result: \"fail\", score: 0 };\n}\n\nfunction mitreBadge(tagKey) {\n const tag = MITRE[tagKey];\n if (!tag) return null;\n return (\n \n {tag.name} • {tag.tactic} ({tag.id})\n \n );\n}\n\nfunction Card({ title, children, icon, accent = \"\", onClick, disabled }) {\n return (\n \n );\n}\n\n// --- Main App ---\nexport default function App() {\n const [round, setRound] = useState(1);\n const [ap, setAp] = useState(10);\n const [score, setScore] = useState(0);\n const [attacksLeft, setAttacksLeft] = useState(5);\n const [currentAttack, setCurrentAttack] = useState(null);\n const [hand, setHand] = useState([]);\n const [log, setLog] = useState([]);\n const [showHelp, setShowHelp] = useState(false);\n\n const drawAttack = () => ATTACK_DECK[Math.floor(Math.random() * ATTACK_DECK.length)];\n\n function drawHandForAttack(attack, size = 5) {\n for (let i = 0; i < 25; i++) {\n const h = pickN(DEFENCE_POOL, size);\n const viable = h.some((d) => d.tags.some((t) => attack.counterTags.includes(t)));\n if (viable) return h;\n }\n const forced = pickN(DEFENCE_POOL, size - 1);\n const must = DEFENCE_POOL.find((d) => d.tags.some((t) => attack.counterTags.includes(t)));\n return [must, ...forced].slice(0, size);\n }\n\n useEffect(() => {\n if (!currentAttack) {\n const atk = drawAttack();\n setCurrentAttack(atk);\n setHand(drawHandForAttack(atk));\n }\n }, [currentAttack]);\n\n function playDefence(card) {\n if (card.cost > ap) return;\n setAp((v) => v - card.cost);\n\n const outcome = defenceEffect(currentAttack, card);\n setScore((s) => s + outcome.score);\n setLog((l) => [\n { ts: Date.now(), text: `${card.title} (${card.cost} AP) → ${outcome.result.toUpperCase()} (+${outcome.score})` },\n ...l,\n ]);\n\n const nextAttacks = attacksLeft - (outcome.result === \"mitigate\" ? 1 : 0);\n setAttacksLeft(nextAttacks);\n\n if (nextAttacks <= 0 || ap - card.cost <= 0) {\n return;\n }\n\n const atk = drawAttack();\n setCurrentAttack(atk);\n setHand(drawHandForAttack(atk));\n }\n\n function newRound() {\n setRound((r) => r + 1);\n setAp(10);\n setAttacksLeft(5);\n setCurrentAttack(null);\n setHand([]);\n setLog([]);\n }\n\n const roundEnded = attacksLeft <= 0 || ap <= 0;\n\n return (\n
{currentAttack.description}
\nNo actions yet. Play a defence card to respond.
}\n {log.map((e) => (\n