feat(health): show the per-category breakdown under the score
Each category is capped, so past the cap fixing one more password leaves the total unchanged and the work looks wasted. The breakdown lists count and points per category, marks a capped deduction with (max), and explains in a tooltip why the score is standing still.
This commit is contained in:
+33
-1
@@ -129,7 +129,39 @@ async function renderHealthDashboard(grid) {
|
||||
const scoreEl = el('div', { class: 'health-score ' + band.cls });
|
||||
scoreEl.appendChild(el('div', { class: 'health-score-num' }, String(h.score)));
|
||||
scoreEl.appendChild(el('div', { class: 'health-score-lbl' }, band.label));
|
||||
header.appendChild(scoreEl);
|
||||
// Per-category breakdown. Each category is capped, so once a cap is reached
|
||||
// fixing one more password leaves the total untouched — which reads as "my
|
||||
// work did nothing". Showing the raw deduction next to the capped one makes
|
||||
// the progress visible even when the score sits still.
|
||||
const parts = [
|
||||
{ lbl: 'Weak', n: h.weak.length, per: 5, cap: 40 },
|
||||
{ lbl: 'Reused', n: h.reused.length, per: 10, cap: 30 },
|
||||
{ lbl: 'Old', n: h.old.length, per: 2, cap: 20 },
|
||||
{ lbl: 'Pwned', n: h.pwned.length, per: 15, cap: 50 },
|
||||
];
|
||||
const breakdown = el('div', { class: 'health-score-breakdown' });
|
||||
parts.forEach(p => {
|
||||
const raw = p.n * p.per;
|
||||
const applied = Math.min(p.cap, raw);
|
||||
const row = el('div', {
|
||||
class: 'health-bd-row' + (p.n === 0 ? ' is-clear' : ''),
|
||||
title: raw > applied
|
||||
? p.lbl + ': ' + p.n + ' × ' + p.per + ' = −' + raw +
|
||||
', capped at −' + p.cap + '. Fixing one more won\'t move the ' +
|
||||
'score until you drop below the cap.'
|
||||
: p.lbl + ': ' + p.n + ' × ' + p.per + ' points',
|
||||
});
|
||||
row.appendChild(el('span', { class: 'health-bd-lbl' }, p.lbl));
|
||||
row.appendChild(el('span', { class: 'health-bd-n' }, String(p.n)));
|
||||
row.appendChild(el('span', { class: 'health-bd-pts' },
|
||||
applied === 0 ? '0' : '−' + applied + (raw > applied ? ' (max)' : '')));
|
||||
breakdown.appendChild(row);
|
||||
});
|
||||
// Under the dial, not inside it — the circle is a fixed 110px.
|
||||
const scoreCol = el('div', { class: 'health-score-col' });
|
||||
scoreCol.appendChild(scoreEl);
|
||||
scoreCol.appendChild(breakdown);
|
||||
header.appendChild(scoreCol);
|
||||
const intro = el('div', { class: 'health-intro' });
|
||||
intro.appendChild(el('h3', null, 'How healthy is your vault?'));
|
||||
intro.appendChild(el('p', null,
|
||||
|
||||
Reference in New Issue
Block a user