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(``)); + 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(``)); + 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 = false; } out.push('
    '); inOl = true; } + out.push(`
  1. ${inline(olMatch[1])}
  2. `); + 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 +
    +
    +
    + ); +}
${inline(c)}
${inline(c)}