/** Client-safe password strength estimate (no deps). Returns 0–4 and a reason. */ const COMMON = new Set(['password', 'password1', 'qwerty', '123456', '12345678', 'iloveyou', 'letmein', 'welcome', 'admin', 'rareindex', 'collectibles', 'charizard', 'pokemon']); export interface Strength { score: 0 | 1 | 2 | 3 | 4; label: 'Too weak' | 'Weak' | 'Fair' | 'Strong' | 'Excellent'; hint: string | null; } export function passwordStrength(pw: string, email?: string): Strength { const s = pw ?? ''; if (s.length < 10) return { score: 0, label: 'Too weak', hint: 'Use at least 10 characters.' }; const lower = s.toLowerCase(); const stem = lower.replace(/[\d!@#$%^&*._-]+$/, ''); if (COMMON.has(lower) || COMMON.has(stem) || /^(.)\1+$/.test(s) || /^(?:0123456789|1234567890|abcdefghij)/.test(lower)) return { score: 0, label: 'Too weak', hint: 'That password is too common.' }; if (email && lower.includes(email.split('@')[0]!.toLowerCase()) && email.split('@')[0]!.length >= 4) return { score: 1, label: 'Weak', hint: 'Avoid using your e-mail in the password.' }; let pool = 0; if (/[a-z]/.test(s)) pool += 26; if (/[A-Z]/.test(s)) pool += 26; if (/\d/.test(s)) pool += 10; if (/[^A-Za-z0-9]/.test(s)) pool += 33; const uniq = new Set(s).size; const entropy = Math.log2(pool || 1) * s.length * Math.min(1, uniq / 6); if (entropy < 45) return { score: 1, label: 'Weak', hint: 'Add more variety or length.' }; if (entropy < 60) return { score: 2, label: 'Fair', hint: 'A passphrase of 4+ words is stronger.' }; if (entropy < 80) return { score: 3, label: 'Strong', hint: null }; return { score: 4, label: 'Excellent', hint: null }; }