diff --git a/client/src/components/ReadmeModal.jsx b/client/src/components/ReadmeModal.jsx
new file mode 100644
index 0000000..e2c4588
--- /dev/null
+++ b/client/src/components/ReadmeModal.jsx
@@ -0,0 +1,451 @@
+import React, { useEffect, useRef } from 'react';
+
+// ─── Minimal Markdown → HTML renderer ────────────────────────────────────────
+// Handles: headings, bold, inline-code, fenced code blocks, tables, hr,
+// unordered lists, ordered lists, and paragraphs.
+function mdToHtml(md) {
+ const lines = md.split('\n');
+ const out = [];
+ let i = 0;
+ let inUl = false;
+ let inOl = false;
+ let inTable = false;
+ let tableHead = false;
+
+ const closeOpenLists = () => {
+ if (inUl) { out.push(''); inUl = false; }
+ if (inOl) { out.push(''); inOl = false; }
+ if (inTable) { out.push(''); inTable = false; tableHead = false; }
+ };
+
+ const inline = (s) =>
+ s
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/\*\*(.+?)\*\*/g, '$1')
+ .replace(/`([^`]+)`/g, '$1');
+
+ while (i < lines.length) {
+ const line = lines[i];
+
+ // Fenced code block
+ if (line.startsWith('```')) {
+ closeOpenLists();
+ const lang = line.slice(3).trim();
+ const codeLines = [];
+ i++;
+ while (i < lines.length && !lines[i].startsWith('```')) {
+ codeLines.push(lines[i].replace(/&/g,'&').replace(//g,'>'));
+ i++;
+ }
+ out.push(`
${codeLines.join('\n')}
`);
+ i++;
+ continue;
+ }
+
+ // HR
+ if (/^---+$/.test(line.trim())) {
+ closeOpenLists();
+ out.push('
');
+ i++;
+ continue;
+ }
+
+ // Headings
+ const hMatch = line.match(/^(#{1,4})\s+(.+)/);
+ if (hMatch) {
+ closeOpenLists();
+ const level = hMatch[1].length;
+ const id = hMatch[2].toLowerCase().replace(/[^a-z0-9]+/g, '-');
+ out.push(`${inline(hMatch[2])}`);
+ i++;
+ continue;
+ }
+
+ // Table row
+ if (line.trim().startsWith('|')) {
+ const cells = line.trim().replace(/^\||\|$/g, '').split('|').map(c => c.trim());
+ if (!inTable) {
+ closeOpenLists();
+ inTable = true;
+ tableHead = true;
+ out.push('');
+ cells.forEach(c => out.push(`| ${inline(c)} | `));
+ out.push('
');
+ i++;
+ // skip separator row
+ if (i < lines.length && lines[i].trim().startsWith('|') && /^[\|\s\-:]+$/.test(lines[i])) i++;
+ continue;
+ } else {
+ out.push('');
+ cells.forEach(c => out.push(`| ${inline(c)} | `));
+ out.push('
');
+ i++;
+ continue;
+ }
+ }
+
+ // Unordered list
+ const ulMatch = line.match(/^[-*]\s+(.*)/);
+ if (ulMatch) {
+ if (inTable) closeOpenLists();
+ if (!inUl) { if (inOl) { out.push(''); inOl = false; } out.push(''); inUl = true; }
+ out.push(`- ${inline(ulMatch[1])}
`);
+ i++;
+ continue;
+ }
+
+ // Ordered list
+ const olMatch = line.match(/^\d+\.\s+(.*)/);
+ if (olMatch) {
+ if (inTable) closeOpenLists();
+ if (!inOl) { if (inUl) { out.push('
'); inUl = false; } out.push(''); inOl = true; }
+ out.push(`- ${inline(olMatch[1])}
`);
+ i++;
+ continue;
+ }
+
+ // Blank line
+ if (line.trim() === '') {
+ closeOpenLists();
+ i++;
+ continue;
+ }
+
+ // Paragraph
+ closeOpenLists();
+ out.push(`${inline(line)}
`);
+ i++;
+ }
+
+ closeOpenLists();
+ return out.join('\n');
+}
+
+// ─── Styles ───────────────────────────────────────────────────────────────────
+const overlay = {
+ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.75)',
+ zIndex: 2000, display: 'flex', alignItems: 'flex-start', justifyContent: 'flex-end',
+};
+
+const panel = {
+ background: '#111217', color: '#f8f9fa', width: '760px', maxWidth: '95vw',
+ height: '100vh', overflowY: 'auto', boxShadow: '-4px 0 32px rgba(0,0,0,0.8)',
+ display: 'flex', flexDirection: 'column',
+};
+
+const header = {
+ background: 'linear-gradient(135deg, #000000, #151622)', color: 'white',
+ padding: '22px 28px', position: 'sticky', top: 0, zIndex: 10,
+ borderBottom: '1px solid #222', display: 'flex', alignItems: 'center',
+ justifyContent: 'space-between',
+};
+
+const closeBtn = {
+ background: 'none', border: 'none', color: 'white',
+ fontSize: '22px', cursor: 'pointer', lineHeight: 1,
+};
+
+const body = {
+ padding: '28px 32px', flex: 1, fontSize: '13px', lineHeight: '1.7',
+};
+
+// Injected
+
+ e.stopPropagation()}>
+
+ {/* Header */}
+
+
+
+ 📋 CPAS Tracker — Documentation
+
+
+ Admin reference · use Esc or click outside to close
+
+
+
+
+
+ {/* TOC strip */}
+
+ {toc.filter(h => h.level <= 2).map((h) => (
+
+ ))}
+
+
+ {/* Body */}
+
+
+ {/* Footer */}
+
+ CPAS Violation Tracker · internal admin use only
+
+
+
+ );
+}