// evaluation-engine.jsx — Verigo Global: ISO 27001 readiness evaluation engine // Questions, scoring, quiz + results components, and a results-PDF generator. // Exposes window.EVAL const { V: GV, MAXW: GMW, FONT: GFT } = window; /* ── QUESTION BANK — 4 themes × 4 statements ─────────────── */ const QUESTIONS = [ { theme: 'A.5 Organizational', icon: 'briefcase', controls: 37, docs: ['Information Security Policy', 'Risk Management Policy', 'Supplier & Third-Party Security Policy'], q: [ 'An approved, top-level Information Security Policy is in place and reviewed at least annually.', 'Information security roles and responsibilities are formally defined and assigned.', 'A documented risk assessment and treatment process is operated and kept current.', 'Security requirements for suppliers and cloud services are defined and monitored.', ] }, { theme: 'A.6 People', icon: 'users', controls: 8, docs: ['Human Resources Security Policy', 'Security Awareness & Training Procedure'], q: [ 'Personnel are screened before employment and bound by confidentiality terms.', 'All staff complete security awareness training that is tracked and refreshed.', 'A disciplinary process and post-employment responsibilities are defined.', 'Staff know how and where to report information security events.', ] }, { theme: 'A.7 Physical', icon: 'building', controls: 14, docs: ['Physical & Environmental Security Policy', 'Clear Desk & Clear Screen Policy'], q: [ 'Secure areas and physical entry controls protect facilities and equipment.', 'Clear desk and clear screen practices are enforced.', 'Storage media and equipment are securely handled, maintained, and disposed of.', 'Physical and environmental threats are monitored and mitigated.', ] }, { theme: 'A.8 Technological', icon: 'cpu', controls: 34, docs: ['Access Control Policy', 'Vulnerability & Patch Management Policy', 'Backup Policy', 'Logging & Monitoring Policy'], q: [ 'Multi-factor authentication and least-privilege access are enforced.', 'Technical vulnerabilities are identified and remediated on a defined cadence.', 'Logging and monitoring capture and review security-relevant events.', 'Backups are performed and restoration is regularly tested.', ] }, ]; const OPTIONS = [ ['Not started', 0], ['Partial', 1], ['Largely', 2], ['Fully', 3], ]; const BANDS = [ [0, 'Initial', GV.orange, 'Foundations are largely missing — start with the core policy and risk set.'], [40, 'Developing', GV.orange, 'Key pieces exist, but real gaps remain before certification is viable.'], [60, 'Established', GV.purple, 'A working ISMS is in place — focus on closing the weaker themes.'], [80, 'Optimized', GV.purple, 'Strong posture — fine-tune evidence and keep it continuous.'], ]; const bandFor = (pct) => { let b = BANDS[0]; BANDS.forEach((x) => { if (pct >= x[0]) b = x; }); return b; }; function computeResults(answers) { const themes = QUESTIONS.map((t, ti) => { const max = t.q.length * 3; let sum = 0; t.q.forEach((_, qi) => { sum += (answers[ti + '-' + qi] || 0); }); const pct = Math.round((sum / max) * 100); return { theme: t.theme, icon: t.icon, docs: t.docs, controls: t.controls, pct, gaps: Math.round((1 - pct / 100) * t.controls) }; }); const totSum = themes.reduce((a, t) => a + (t.pct), 0); const overall = Math.round(totSum / themes.length); const gaps = themes.reduce((a, t) => a + t.gaps, 0); return { themes, overall, gaps, band: bandFor(overall) }; } /* ── SEGMENTED MATURITY CONTROL ──────────────────────────── */ const Segmented = ({ value, onChange }) => (
{OPTIONS.map(([label, val]) => { const on = value === val; return ( ); })}
); /* ── QUIZ ────────────────────────────────────────────────── */ const EvalQuiz = ({ onComplete, onExit }) => { const [step, setStep] = React.useState(0); const [answers, setAnswers] = React.useState({}); const total = QUESTIONS.length; const cur = QUESTIONS[step]; const set = (qi, v) => setAnswers((a) => ({ ...a, [step + '-' + qi]: v })); const stepDone = cur.q.every((_, qi) => answers[step + '-' + qi] !== undefined); const answeredCount = Object.keys(answers).length; const totalQ = QUESTIONS.reduce((a, t) => a + t.q.length, 0); const progress = Math.round((answeredCount / totalQ) * 100); const next = () => { if (step < total - 1) { setStep(step + 1); window.scrollTo({ top: 0 }); } else { onComplete(computeResults(answers)); } }; const back = () => { if (step > 0) { setStep(step - 1); window.scrollTo({ top: 0 }); } else { onExit(); } }; return (
{/* progress */}
Section {step + 1} of {total} {progress}% complete
Annex A theme

{cur.theme}

For each statement, choose how completely it reflects your organization today.

{cur.q.map((text, qi) => (
{String(qi + 1).padStart(2, '0')}

{text}

set(qi, v)} />
))}
{step === 0 ? 'Exit' : 'Back'} {step === total - 1 ? 'See my results' : 'Next section'}
{!stepDone &&

Answer all four statements to continue.

}
); }; /* ── RESULTS ─────────────────────────────────────────────── */ const Donut = ({ pct, color }) => (
{pct} / 100
); const EvalResults = ({ results, user, onNav, onRetake }) => { const { themes, overall, gaps, band } = results; const [, label, bandColor, bandDesc] = band; const sorted = [...themes].sort((a, b) => a.pct - b.pct); const focus = sorted.filter((t) => t.pct < 80); const MODULES = window.TK_ISO.QUOTE_MODULES; // Recommend option modules from the weak themes + overall score. const reco = new Set(['tailor']); if (overall < 80) reco.add('guidance'); if (overall < 60) { reco.add('impl'); reco.add('preaudit'); } themes.forEach((t) => { if (t.pct < 60) { if (t.theme.startsWith('A.5')) reco.add('risk'); if (t.theme.startsWith('A.8')) reco.add('preaudit'); } }); const recoIds = [...reco]; const recoModules = MODULES.filter((m) => reco.has(m.id)); const goBuild = () => { try { localStorage.setItem('verigo_reco', JSON.stringify(recoIds)); } catch (e) {} onNav('checkout'); }; return (
{/* SCORE HEADER */}
Your readiness report
Overall maturity · {label}

{overall >= 80 ? 'You\u2019re close to audit-ready.' : overall >= 60 ? 'A solid base, with clear gaps to close.' : 'There\u2019s real groundwork to do first.'}

{bandDesc} We estimate ~{gaps} of 93 controls need attention before a Stage 2 audit. {user && user.company ? ` Prepared for ${user.company}.` : ''}

{/* THEME BREAKDOWN */}
{themes.map((t, i) => { const c = t.pct < 60 ? GV.orange : GV.purple; return (
{t.theme} {t.pct}%
~{t.gaps} of {t.controls} controls need attention
); })}
{/* ACTION PLAN */}
{focus.length === 0 && (
Every theme scored 80%+ — you\u2019re in strong shape. A practitioner can help validate evidence and close the final details.
)} {focus.map((t, i) => (
{i + 1}

{t.theme}

{t.pct}%
{t.docs.map((d, di) => ( {d} ))}
onNav('toolkit:iso27001')} style={{ whiteSpace: 'nowrap' }}>Open toolkit
))}
window.EVAL.generateEvalPDF(results, user)}> Download my results (PDF) Retake the evaluation
{/* CUSTOM PACKAGE CTA — recommended OPTIONS, not basic bundles */}
{recoModules.map((m, i) => (
{m.name}
{m.desc}
))}
Build my custom package Pick your options, see the price, then download a custom quote.
); }; /* ── RESULTS PDF ─────────────────────────────────────────── */ function generateEvalPDF(results, user) { const lib = window.jspdf; if (!lib || !lib.jsPDF) { alert('PDF engine is still loading — please try again in a moment.'); return; } const { themes, overall, gaps, band } = results; const doc = new lib.jsPDF({ unit: 'pt', format: 'a4' }); const W = doc.internal.pageSize.getWidth(); const H = doc.internal.pageSize.getHeight(); const M = 50; const PURPLE = [91, 46, 145], ORANGE = [232, 98, 42], INK = [30, 30, 46], GREY = [120, 120, 134]; let y = 100; doc.setFillColor(...PURPLE); doc.rect(0, 0, W, 70, 'F'); doc.setFillColor(...ORANGE); doc.rect(W - M - 11, 28, 11, 11, 'F'); doc.setTextColor(255, 255, 255); doc.setFont('helvetica', 'bold'); doc.setFontSize(15); doc.text('VERIGO GLOBAL', M, 33); doc.setFont('helvetica', 'normal'); doc.setFontSize(8.5); doc.setTextColor(214, 204, 232); doc.text('Compliance by Design', M, 49); doc.setTextColor(255, 255, 255); doc.setFontSize(8.5); doc.text('ISO/IEC 27001:2022', W - M - 20, 35, { align: 'right' }); doc.setTextColor(...INK); doc.setFont('helvetica', 'bold'); doc.setFontSize(21); doc.text('ISO 27001 Readiness Report', M, y); y += 12; doc.setDrawColor(...ORANGE); doc.setLineWidth(2.5); doc.line(M, y, M + 54, y); y += 24; // Score block doc.setFont('helvetica', 'bold'); doc.setFontSize(40); doc.setTextColor(...PURPLE); doc.text(String(overall), M, y + 14); doc.setFontSize(11); doc.setTextColor(...GREY); doc.setFont('helvetica', 'normal'); doc.text('/ 100', M + 58, y + 14); doc.setFont('helvetica', 'bold'); doc.setFontSize(13); doc.setTextColor(...INK); doc.text('Overall maturity: ' + band[1], M + 130, y - 2); doc.setFont('helvetica', 'normal'); doc.setFontSize(10); doc.setTextColor(...GREY); doc.splitTextToSize('~' + gaps + ' of 93 controls need attention before a Stage 2 audit. ' + band[3], W - M - (M + 130)).forEach((ln, i) => doc.text(ln, M + 130, y + 14 + i * 13)); y += 56; if (user && (user.company || user.name)) { doc.setDrawColor(224, 224, 232); doc.setLineWidth(0.5); doc.line(M, y, W - M, y); y += 16; doc.setFontSize(9); doc.setTextColor(...PURPLE); doc.setFont('helvetica', 'bold'); doc.text('Prepared for ' + (user.company || user.name), M, y); doc.setFont('helvetica', 'normal'); doc.setTextColor(...GREY); doc.text(new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }), W - M, y, { align: 'right' }); y += 22; } // Theme breakdown doc.setFont('helvetica', 'bold'); doc.setFontSize(12.5); doc.setTextColor(...PURPLE); doc.text('Maturity by control theme', M, y); y += 6; doc.setDrawColor(...PURPLE); doc.setLineWidth(0.8); doc.line(M, y, W - M, y); y += 20; const barX = M + 200, barW = W - M - barX; themes.forEach((t) => { doc.setFont('helvetica', 'bold'); doc.setFontSize(10.5); doc.setTextColor(...INK); doc.text(t.theme, M, y + 3); doc.setFillColor(237, 237, 245); doc.rect(barX, y - 7, barW, 10, 'F'); const col = t.pct < 60 ? ORANGE : PURPLE; doc.setFillColor(...col); doc.rect(barX, y - 7, barW * (t.pct / 100), 10, 'F'); doc.setFont('helvetica', 'bold'); doc.setFontSize(10); doc.setTextColor(...col); doc.text(t.pct + '%', W - M, y + 3, { align: 'right' }); y += 26; }); y += 6; // Recommendations const focus = [...themes].sort((a, b) => a.pct - b.pct).filter((t) => t.pct < 80); if (focus.length) { doc.setFont('helvetica', 'bold'); doc.setFontSize(12.5); doc.setTextColor(...PURPLE); doc.text('Recommended next steps', M, y); y += 6; doc.setDrawColor(...PURPLE); doc.setLineWidth(0.8); doc.line(M, y, W - M, y); y += 18; focus.forEach((t, i) => { if (y > H - 90) { doc.addPage(); y = 60; } doc.setFont('helvetica', 'bold'); doc.setFontSize(10.5); doc.setTextColor(...INK); doc.text((i + 1) + '. ' + t.theme + ' (' + t.pct + '%)', M, y); y += 14; doc.setFont('helvetica', 'normal'); doc.setFontSize(9.5); doc.setTextColor(...GREY); doc.splitTextToSize('Toolkit documents: ' + t.docs.join(', '), W - 2 * M - 14).forEach((ln) => { doc.text(ln, M + 14, y); y += 12; }); y += 8; }); } const total = doc.internal.getNumberOfPages(); for (let i = 1; i <= total; i++) { doc.setPage(i); doc.setDrawColor(224, 224, 232); doc.setLineWidth(0.5); doc.line(M, H - 38, W - M, H - 38); doc.setFont('helvetica', 'normal'); doc.setFontSize(8); doc.setTextColor(150, 150, 160); doc.text('© 2026 Verigo Global · Readiness self-assessment — indicative, not a formal audit', M, H - 24); doc.text(i + ' / ' + total, W - M, H - 24, { align: 'right' }); } doc.save('Verigo-ISO27001-Readiness-Report.pdf'); } window.EVAL = { QUESTIONS, OPTIONS, BANDS, computeResults, EvalQuiz, EvalResults, generateEvalPDF }; Object.assign(window, { EvalQuiz, EvalResults });