diff --git a/client/src/components/ReadmeModal.jsx b/client/src/components/ReadmeModal.jsx
index e2c4588..edadd0a 100644
--- a/client/src/components/ReadmeModal.jsx
+++ b/client/src/components/ReadmeModal.jsx
@@ -1,286 +1,145 @@
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 lines = md.split('\n');
+ const out = [];
+ let i = 0, inUl = false, inOl = false, inTable = false;
- const closeOpenLists = () => {
- if (inUl) { out.push(''); inUl = false; }
- if (inOl) { out.push(''); inOl = false; }
- if (inTable) { out.push(''); inTable = false; tableHead = false; }
+ const close = () => {
+ if (inUl) { out.push(''); inUl = false; }
+ if (inOl) { out.push(''); inOl = false; }
+ if (inTable) { out.push(''); inTable = false; }
};
- const inline = (s) =>
- s
- .replace(/&/g, '&')
- .replace(//g, '>')
- .replace(/\*\*(.+?)\*\*/g, '$1')
- .replace(/`([^`]+)`/g, '$1');
+ 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 = [];
+ close();
i++;
- while (i < lines.length && !lines[i].startsWith('```')) {
- codeLines.push(lines[i].replace(/&/g,'&').replace(//g,'>'));
- i++;
- }
- out.push(`
${codeLines.join('\n')}
`);
- i++;
- continue;
+ while (i < lines.length && !lines[i].startsWith('```')) i++;
+ i++; continue;
+ }
+ if (/^---+$/.test(line.trim())) { close(); out.push('
'); i++; continue; }
+
+ const hm = line.match(/^(#{1,4})\s+(.+)/);
+ if (hm) {
+ close();
+ const lvl = hm[1].length;
+ const id = hm[2].toLowerCase().replace(/[^a-z0-9]+/g,'-');
+ out.push(`${inline(hm[2])}`);
+ 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());
+ const cells = line.trim().replace(/^\||\|$/g,'').split('|').map(c=>c.trim());
if (!inTable) {
- closeOpenLists();
- inTable = true;
- tableHead = true;
+ close(); inTable = 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++;
+ if (i < lines.length && /^[\|\s\-:]+$/.test(lines[i])) i++;
continue;
} else {
out.push('');
cells.forEach(c => out.push(`| ${inline(c)} | `));
out.push('
');
- i++;
- continue;
+ 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;
+ const ul = line.match(/^[-*]\s+(.*)/);
+ if (ul) {
+ if (inTable) close();
+ if (!inUl) { if (inOl) { out.push(''); inOl=false; } out.push(''); inUl=true; }
+ out.push(`- ${inline(ul[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;
+ const ol = line.match(/^\d+\.\s+(.*)/);
+ if (ol) {
+ if (inTable) close();
+ if (!inOl) { if (inUl) { out.push('
'); inUl=false; } out.push(''); inOl=true; }
+ out.push(`- ${inline(ol[1])}
`);
+ i++; continue;
}
- // Blank line
- if (line.trim() === '') {
- closeOpenLists();
- i++;
- continue;
- }
+ if (line.trim() === '') { close(); i++; continue; }
- // Paragraph
- closeOpenLists();
+ close();
out.push(`${inline(line)}
`);
i++;
}
-
- closeOpenLists();
+ close();
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 S = {
+ overlay: {
+ position:'fixed', inset:0, background:'rgba(0,0,0,0.75)',
+ zIndex:2000, display:'flex', alignItems:'flex-start', justifyContent:'flex-end',
+ },
+ panel: {
+ background:'#111217', color:'#f8f9fa', width:'780px', maxWidth:'95vw',
+ height:'100vh', overflowY:'auto', boxShadow:'-4px 0 32px rgba(0,0,0,0.85)',
+ display:'flex', flexDirection:'column',
+ },
+ 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',
+ },
+ closeBtn: { background:'none', border:'none', color:'white', fontSize:'22px', cursor:'pointer', lineHeight:1 },
+ toc: {
+ background:'#0d1117', borderBottom:'1px solid #1e1f2e',
+ padding:'10px 32px', display:'flex', flexWrap:'wrap', gap:'4px 18px', fontSize:'11px',
+ },
+ body: { padding:'28px 32px', flex:1, fontSize:'13px', lineHeight:'1.75' },
+ footer: { padding:'14px 32px', borderTop:'1px solid #1e1f2e', fontSize:'11px', color:'#555770', textAlign:'center' },
};
-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()}>
+
e.stopPropagation()}>
{/* Header */}
-
+
-
- 📋 CPAS Tracker — Documentation
+
+ 📋 CPAS Tracker — Admin Guide
-
- Admin reference · use Esc or click outside to close
+
+ Feature map · workflow reference · roadmap · Esc or click outside to close
-
+
{/* TOC strip */}
-
- {toc.filter(h => h.level <= 2).map((h) => (
+
+ {toc.map(h => (