document.addEventListener("DOMContentLoaded", () => { // DOM Elements const passwordOutput = document.getElementById("password-output"); const btnRegenerate = document.getElementById("btn-regenerate"); const btnCopy = document.getElementById("btn-copy"); const copyTooltip = document.getElementById("copy-tooltip"); const lengthSlider = document.getElementById("length-slider"); const lengthVal = document.getElementById("length-val"); const chkUpper = document.getElementById("chk-upper"); const chkLower = document.getElementById("chk-lower"); const chkNumbers = document.getElementById("chk-numbers"); const chkSymbols = document.getElementById("chk-symbols"); const chkExcludeAmbiguous = document.getElementById("chk-exclude-ambiguous"); const entropyVal = document.getElementById("entropy-val"); const strengthLabel = document.getElementById("strength-label"); const strengthMeter = document.getElementById("strength-meter"); const crackTime = document.getElementById("crack-time"); // Cryptographically secure random integer generation [0, max - 1] function getRandomInt(max) { const array = new Uint32Array(1); const maxVal = 4294967296; // 2^32 const limit = maxVal - (maxVal % max); do { window.crypto.getRandomValues(array); } while (array[0] >= limit); return array[0] % max; } // Cryptographically secure Fisher-Yates shuffle function secureShuffle(array) { for (let i = array.length - 1; i > 0; i--) { const j = getRandomInt(i + 1); const temp = array[i]; array[i] = array[j]; array[j] = temp; } return array; } // Core Password Generation Logic function generatePassword(length, useUpper, useLower, useNumbers, useSymbols, excludeAmbiguous) { let upperPool = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; let lowerPool = "abcdefghijklmnopqrstuvwxyz"; let numberPool = "0123456789"; let symbolPool = "!@#$%^&*()_+-=[]{}|;':\",./<>?~`"; if (excludeAmbiguous) { upperPool = upperPool.replace(/[IO]/g, ""); lowerPool = lowerPool.replace(/[lo]/g, ""); numberPool = numberPool.replace(/[01]/g, ""); symbolPool = symbolPool.replace(/[|]/g, ""); // Exclude vertical bar as it looks like I/l/1 } const pools = []; if (useUpper) pools.push(upperPool); if (useLower) pools.push(lowerPool); if (useNumbers) pools.push(numberPool); if (useSymbols) pools.push(symbolPool); if (pools.length === 0) return { password: "", poolSize: 0 }; const combinedPool = pools.join(""); const passwordChars = []; // 1. Guarantee at least one character from each selected class to satisfy constraints pools.forEach(pool => { const randIdx = getRandomInt(pool.length); passwordChars.push(pool[randIdx]); }); // 2. Fill the remaining spots up to target length while (passwordChars.length < length) { const randIdx = getRandomInt(combinedPool.length); passwordChars.push(combinedPool[randIdx]); } // 3. Shuffle array elements securely to remove sequential pattern secureShuffle(passwordChars); return { password: passwordChars.join(""), poolSize: combinedPool.length }; } // Determine Strength and Crack Time Estimates function getStrengthMetrics(entropy) { if (entropy === 0) { return { rating: 0, label: "Rating: -", crackTime: "Select at least one character class", colorClass: "" }; } if (entropy < 28) { return { rating: 0, label: "Rating: 0/5 (Very Weak)", crackTime: "Can be broken in seconds/minutes", colorClass: "strength-label-0" }; } else if (entropy < 40) { return { rating: 1, label: "Rating: 1/5 (Weak)", crackTime: "Can be broken in hours", colorClass: "strength-label-1" }; } else if (entropy < 60) { return { rating: 2, label: "Rating: 2/5 (Fair)", crackTime: "Can be broken in days or months", colorClass: "strength-label-2" }; } else if (entropy < 80) { return { rating: 3, label: "Rating: 3/5 (Good)", crackTime: "Can be broken in years", colorClass: "strength-label-3" }; } else if (entropy < 100) { return { rating: 4, label: "Rating: 4/5 (Strong)", crackTime: "Takes decades to break", colorClass: "strength-label-4" }; } else { return { rating: 5, label: "Rating: 5/5 (Centurial)", crackTime: "Takes centuries or millennia to break", colorClass: "strength-label-5" }; } } // Update UI Elements function updateApp() { const length = parseInt(lengthSlider.value, 10); lengthVal.textContent = length; const useUpper = chkUpper.checked; const useLower = chkLower.checked; const useNumbers = chkNumbers.checked; const useSymbols = chkSymbols.checked; const excludeAmbiguous = chkExcludeAmbiguous.checked; const result = generatePassword(length, useUpper, useLower, useNumbers, useSymbols, excludeAmbiguous); passwordOutput.value = result.password; // Entropy: L * log2(R) let entropy = 0; if (result.poolSize > 0 && result.password.length > 0) { entropy = result.password.length * Math.log2(result.poolSize); } entropyVal.textContent = entropy.toFixed(1); // Strength metrics const metrics = getStrengthMetrics(entropy); strengthLabel.textContent = metrics.label; strengthLabel.className = "metric-label " + metrics.colorClass; crackTime.textContent = metrics.crackTime; // Visual Strength Meter Bars Update const segments = strengthMeter.querySelectorAll(".meter-segment"); segments.forEach(segment => { segment.className = "meter-segment"; }); if (result.poolSize > 0) { const activeCount = Math.max(1, metrics.rating); // light up at least 1 bar if pool is selected for (let i = 0; i < activeCount; i++) { segments[i].classList.add(`active-${metrics.rating}`); } } } // Clipboard Copy Action function copyToClipboard(text) { if (navigator.clipboard && navigator.clipboard.writeText) { return navigator.clipboard.writeText(text); } else { const textArea = document.createElement("textarea"); textArea.value = text; textArea.style.position = "fixed"; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { document.execCommand("copy"); document.body.removeChild(textArea); return Promise.resolve(); } catch (err) { document.body.removeChild(textArea); return Promise.reject(err); } } } // Event Handlers lengthSlider.addEventListener("input", updateApp); [chkUpper, chkLower, chkNumbers, chkSymbols, chkExcludeAmbiguous].forEach(el => { el.addEventListener("change", updateApp); }); btnRegenerate.addEventListener("click", () => { // Trigger subtle spin animation on click const icon = btnRegenerate.querySelector(".icon-refresh"); icon.classList.add("spinning"); setTimeout(() => { icon.classList.remove("spinning"); }, 500); updateApp(); }); btnCopy.addEventListener("click", () => { if (!passwordOutput.value) return; copyToClipboard(passwordOutput.value) .then(() => { btnCopy.classList.add("copied"); copyTooltip.textContent = "Copied!"; setTimeout(() => { btnCopy.classList.remove("copied"); copyTooltip.textContent = "Copy"; }, 2000); }) .catch(err => { console.error("Copy failed:", err); copyTooltip.textContent = "Failed!"; setTimeout(() => { copyTooltip.textContent = "Copy"; }, 2000); }); }); // Initialize state updateApp(); });