| 366 | // ---- assessment helpers -------------------------------------------------- |
| 367 | |
| 368 | function journeyHtml(s) { |
| 369 | const labels = ["Assess", "Remediate", "Validate", "Ship"]; |
| 370 | const hasPlan = !!((s.report && s.report.findings) || (s.plan && s.plan.exists) || (s.progress && s.progress.exists)); |
| 371 | const gates = s.gates || {}; |
| 372 | const anyGate = Object.keys(gates).some((k) => gates[k] && gates[k] !== "not_run"); |
| 373 | const done = s.status === "completed"; |
| 374 | let cur; |
| 375 | if (done) cur = 4; |
| 376 | else if (anyGate) cur = 2; |
| 377 | else if (hasPlan) cur = 1; |
| 378 | else cur = 0; |
| 379 | let h = '<div class="stepper">'; |
| 380 | labels.forEach((label, i) => { |
| 381 | if (i > 0) h += '<span class="bar' + (i <= cur ? " done" : "") + '"></span>'; |
| 382 | const cls = i < cur ? "done" : i === cur ? "current" : ""; |
| 383 | const mark = i < cur ? "✓" : String(i + 1); |
| 384 | h += '<div class="step ' + cls + '"><span class="ix">' + mark + '</span><span class="lbl">' + esc(label) + "</span></div>"; |
| 385 | }); |
| 386 | return h + "</div>"; |
| 387 | } |
| 388 | |
| 389 | function heroNext(s) { |
| 390 | const rep = s.report; |