${topic.content}
`; // Content is already HTML-safe
accordionItem.appendChild(button);
accordionItem.appendChild(panel);
accordionContainer.appendChild(accordionItem);
button.addEventListener('click', () => {
button.classList.toggle('spg-active');
panel.classList.toggle('spg-active');
if (panel.classList.contains('spg-active')) {
panel.style.maxHeight = panel.scrollHeight + "px";
} else {
panel.style.maxHeight = null;
}
});
});
}
// --- Generator Logic ---
function handleGeneration() {
const length = parseInt(lengthSlider.value, 10);
const useUpper = checkUpper.checked;
const useLower = checkLower.checked;
const useNumbers = checkNumbers.checked;
const useSymbols = checkSymbols.checked;
let charset = "";
let guaranteedChars = [];
let numSets = 0;
if (useUpper) {
charset += CHARSETS.UPPER;
guaranteedChars.push(
CHARSETS.UPPER[
Math.floor(Math.random() * CHARSETS.UPPER.length)
]
);
numSets++;
}
if (useLower) {
charset += CHARSETS.LOWER;
guaranteedChars.push(
CHARSETS.LOWER[
Math.floor(Math.random() * CHARSETS.LOWER.length)
]
);
numSets++;
}
if (useNumbers) {
charset += CHARSETS.NUMBERS;
guaranteedChars.push(
CHARSETS.NUMBERS[
Math.floor(Math.random() * CHARSETS.NUMBERS.length)
]
);
numSets++;
}
if (useSymbols) {
charset += CHARSETS.SYMBOLS;
guaranteedChars.push(
CHARSETS.SYMBOLS[
Math.floor(Math.random() * CHARSETS.SYMBOLS.length)
]
);
numSets++;
}
if (charset === "") {
passwordOutput.value = "Select at least one set!";
updateStrength(0, 0);
return;
}
let password = [...guaranteedChars];
const remainingLength = length - password.length;
for (let i = 0; i < remainingLength; i++) {
password.push(
charset[Math.floor(Math.random() * charset.length)]
);
}
// Shuffle the array to mix guaranteed chars
const finalPassword = shuffleArray(password).join("");
passwordOutput.value = finalPassword;
updateStrength(length, numSets);
}
function updateStrength(length, numSets) {
let score = 0;
if (length >= 12) score++;
if (length >= 16) score++;
if (numSets >= 3) score++;
if (numSets === 4) score++;
if (length >= 20 && numSets === 4) score++;
strengthIndicator.className = "spg-strength-indicator"; // Reset
if (score <= 1) {
strengthIndicator.classList.add("spg-weak");
strengthIndicator.style.width = "25%";
} else if (score <= 3) {
strengthIndicator.classList.add("spg-medium");
strengthIndicator.style.width = "60%";
} else {
strengthIndicator.classList.add("spg-strong");
strengthIndicator.style.width = "100%";
}
}
function updateLengthLabel() {
if(lengthValue) {
lengthValue.textContent = lengthSlider.value;
}
}
function copyPassword() {
if (!navigator.clipboard) {
// Fallback for insecure contexts
passwordOutput.select();
document.execCommand('copy');
copyBtn.textContent = "Copied!";
} else {
navigator.clipboard.writeText(passwordOutput.value).then(() => {
copyBtn.textContent = "Copied!";
}).catch(err => {
console.error("SPG: Could not copy text: ", err);
copyBtn.textContent = "Error";
});
}
setTimeout(() => {
copyBtn.textContent = "Copy";
}, 1500);
}
// --- PDF Download Function ---
if (pdfDownloadBtn) {
pdfDownloadBtn.addEventListener("click", () => {
if (
typeof window.jspdf === "undefined" ||
typeof window.jspdf.jsPDF === "undefined"
) {
alert(
"Error: PDF library (jsPDF) is not loaded. Please check your internet connection."
);
return;
}
if (typeof window.jspdf.plugin.autotable === "undefined") {
alert(
"Error: PDF library (jsPDF-AutoTable) is not loaded. Please check your internet connection."
);
return;
}
generatePDF();
});
}
function generatePDF() {
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
const primaryColor =
getComputedStyle(container).getPropertyValue(
"--spg-primary-color"
) || "#0d6e6e";
const headStyles = {
fillColor: primaryColor,
textColor: "#ffffff",
fontStyle: "bold",
};
// Title
doc.setFont("helvetica", "bold");
doc.setFontSize(18);
doc.setTextColor(primaryColor);
doc.text("Generated Password Report", 105, 20, {
align: "center",
});
// Security Warning
doc.setFont("helvetica", "bold");
doc.setFontSize(12);
doc.setTextColor(var(--spg-danger-color, "#d32f2f"));
doc.text("SECURITY WARNING:", 15, 35);
doc.setFont("helvetica", "normal");
doc.setFontSize(10);
doc.text("This document contains a password in plain text. Please destroy this file securely after use. Do not email or save it to a shared or public device.", 15, 42, { maxWidth: 180 });
// 1. Password Table
const passHead = [["Generated Password"]];
const passBody = [[passwordOutput.value]];
doc.autoTable({
startY: 55,
head: passHead,
body: passBody,
theme: "grid",
headStyles: headStyles,
bodyStyles: { font: "courier", fontSize: 12, fontStyle: 'bold' }
});
// 2. Settings Table
let lastY = doc.autoTable.previous.finalY;
const settingsHead = [["Setting", "Value"]];
const settingsBody = [
["Password Length", lengthSlider.value],
["Include Uppercase (A-Z)", checkUpper.checked ? "Yes" : "No"],
["Include Lowercase (a-z)", checkLower.checked ? "Yes" : "No"],
["Include Numbers (0-9)", checkNumbers.checked ? "Yes" : "No"],
["Include Symbols (!@#...)", checkSymbols.checked ? "Yes" : "No"],
];
doc.autoTable({
startY: lastY + 10,
head: settingsHead,
body: settingsBody,
theme: "striped",
headStyles: headStyles
});
doc.save("Password-Report.pdf");
}
// --- Utility Functions ---
function escapeHTML(str) {
if (!str) return "";
return str
.toString()
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"g, """)
.replace(/'g, "'");
}
function shuffleArray(array) {
let newArr = [...array];
for (let i = newArr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[newArr[i], newArr[j]] = [newArr[j], newArr[i]];
}
return newArr;
}
// --- Event Listeners ---
if (lengthSlider) {
lengthSlider.addEventListener("input", () => {
updateLengthLabel();
handleGeneration();
});
}
if (generateBtn) {
generateBtn.addEventListener("click", handleGeneration);
}
if (copyBtn) {
copyBtn.addEventListener("click", copyPassword);
}
[checkUpper, checkLower, checkNumbers, checkSymbols].forEach(el => {
if(el) {
el.addEventListener("change", handleGeneration);
}
});
// --- Initial Load ---
populateGuide();
handleGeneration(); // Generate a password on load
updateNavButtons(); // Set initial state
} // --- End of initializePasswordGenerator() function ---
/*
* This new logic checks if the page is already loaded (common in Elementor).
* If it is, it runs the setup function immediately.
* If it's still loading (rare), it waits for the DOM event.
*/
if (
document.readyState === "complete" ||
document.readyState === "interactive"
) {
// DOM is already ready, run the function now.
initializePasswordGenerator();
} else {
// Still loading, wait for the event
document.addEventListener(
"DOMContentLoaded",
initializePasswordGenerator
);
}