{"id":481,"date":"2026-03-06T12:17:25","date_gmt":"2026-03-06T12:17:25","guid":{"rendered":"https:\/\/primetoolhub.com\/?page_id=481"},"modified":"2026-07-12T15:42:36","modified_gmt":"2026-07-12T15:42:36","slug":"free-strong-password-generator","status":"publish","type":"page","link":"https:\/\/schoolict.net\/tools\/free-strong-password-generator\/","title":{"rendered":"Free Strong Password Generator |  Generate, Audit &amp; Build Secure Passwords Offline"},"content":{"rendered":"\n<div class=\"pth-hero-section\">\n    <div class=\"pth-hero-content\">\n        <h2>Password Studio <\/h2>\n        <p>Free offline password studio. Generate cryptographically strong passwords, audit your own for real entropy and weak patterns, and build PINs, recovery codes and passphrases. Nothing is uploaded.<\/p>\n\n        <div id=\"pth-toc-placeholder\"><\/div>\n    <\/div>\n\n<div class=\"pth-hero-image\">\n    <img data-no-lazy=\"1\"\n         src=\"https:\/\/schoolict.net\/tools\/wp-content\/uploads\/2026\/03\/Free-Strong-Password-Generato-800x447.jpeg\"\n         width=\"800\"\n         height=\"447\"\n         alt=\"strong password generator\"\n         fetchpriority=\"high\"\n         loading=\"eager\"\n         decoding=\"async\"\n         style=\"width:100%; height:auto; display:block;\">\n  <\/div>\n<\/div>\n\n\n\n\n<div class=\"wp-block-rank-math-toc-block\" id=\"rank-math-toc\"><h2>Table of Contents<\/h2><nav><ul><li><a href=\"#\ud83d\udd34-why-a-random-looking-password-is-not-the-same-as-a-random-one\">\ud83d\udd34\u00a0Why a random-looking password is not the same as a random one<\/a><\/li><li><a href=\"#\ud83d\udfe1-reading-the-strength-meter-and-trusting-the-auditor\">\ud83d\udfe1\u00a0Reading the strength meter, and trusting the auditor<\/a><ul><li><a href=\"#\ud83d\udfe2-the-patterns-the-auditor-hunts-for\">\ud83d\udfe2\u00a0The patterns the auditor hunts for<\/a><\/li><\/ul><\/li><li><a href=\"#\ud83d\udfe2-pi-ns-recovery-codes-and-keys-with-a-fixed-shape\">\ud83d\udfe2\u00a0PINs, recovery codes and keys with a fixed shape<\/a><\/li><li><a href=\"#\ud83d\udd34-what-this-tool-refuses-to-do-on-purpose\">\ud83d\udd34\u00a0What this tool refuses to do, on purpose<\/a><\/li><\/ul><\/nav><\/div>\n\n\n<!-- LSCACHE_DISABLE -->\r\n<div id=\"pth-pwgen-wrapper\">\r\n    <style>\r\n        #pth-pwgen-wrapper { box-sizing: border-box; font-family: 'Inter', system-ui, -apple-system, sans-serif; color: #1e293b; background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; overflow: hidden; margin: 20px 0; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05); }\r\n        #pth-pwgen-wrapper * { box-sizing: border-box; min-width: 0; }\r\n\r\n        #pth-pwgen-wrapper .pth-topbar { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; padding: 20px 24px 0; }\r\n        #pth-pwgen-wrapper .pth-title { margin: 0; font-size: 1.35rem; font-weight: 800; color: #0f172a; }\r\n        #pth-pwgen-wrapper .pth-toolbar { display: flex; gap: 8px; flex-wrap: wrap; }\r\n        #pth-pwgen-wrapper .pth-btn { border: none; padding: 8px 14px; font-size: 0.85rem; border-radius: 7px; cursor: pointer; font-weight: 700; font-family: inherit; }\r\n        #pth-pwgen-wrapper .pth-sample-btn { background: #eff6ff; color: #1d4ed8; }\r\n        #pth-pwgen-wrapper .pth-sample-btn:hover { background: #dbeafe; }\r\n        #pth-pwgen-wrapper .pth-fw-btn { background: #e2e8f0; color: #0f172a; }\r\n        #pth-pwgen-wrapper .pth-fw-btn:hover { background: #cbd5e1; }\r\n        #pth-pwgen-wrapper .pth-clear-btn { background: #fef2f2; color: #991b1b; }\r\n        #pth-pwgen-wrapper .pth-clear-btn:hover { background: #fee2e2; }\r\n\r\n        #pth-pwgen-wrapper .pth-meta-bar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; padding: 8px 24px 0; }\r\n        #pth-pwgen-wrapper .pth-version-badge { background: #eff6ff; color: #1d4ed8; font-size: 0.72rem; font-weight: 800; padding: 4px 10px; border-radius: 20px; letter-spacing: 0.4px; }\r\n        #pth-pwgen-wrapper .pth-status-offline { background: #ecfdf5; color: #047857; font-size: 0.72rem; font-weight: 800; padding: 4px 10px; border-radius: 20px; }\r\n\r\n        #pth-pwgen-wrapper .pth-tabbar { display: flex; gap: 4px; flex-wrap: wrap; padding: 16px 24px 0; border-bottom: 2px solid #e2e8f0; }\r\n        #pth-pwgen-wrapper .pth-tab-btn { background: transparent; border: none; border-bottom: 3px solid transparent; margin-bottom: -2px; padding: 10px 14px; font-size: 0.87rem; font-weight: 700; color: #334155; cursor: pointer; font-family: inherit; border-radius: 6px 6px 0 0; }\r\n        #pth-pwgen-wrapper .pth-tab-btn:hover { background: #f1f5f9; color: #0f172a; }\r\n        #pth-pwgen-wrapper .pth-tab-btn.active { color: #1d4ed8; border-bottom-color: #2563eb; background: #eff6ff; }\r\n\r\n        #pth-pwgen-wrapper .pth-grid { display: grid; grid-template-columns: 1.8fr 1fr; }\r\n        #pth-pwgen-wrapper .pth-main { padding: 24px; border-right: 1px solid #e2e8f0; }\r\n        #pth-pwgen-wrapper .pth-side { padding: 24px; background: #f8fafc; }\r\n        #pth-pwgen-wrapper .pth-grid.full-width { grid-template-columns: 1fr; }\r\n        #pth-pwgen-wrapper .pth-grid.full-width .pth-side { display: none; }\r\n        #pth-pwgen-wrapper .pth-grid.full-width .pth-main { border-right: none; }\r\n\r\n        #pth-pwgen-wrapper .pth-panel { display: none; }\r\n        #pth-pwgen-wrapper .pth-panel.active { display: block; }\r\n        #pth-pwgen-wrapper .pth-panel-intro { font-size: 0.9rem; color: #334155; line-height: 1.6; margin: 0 0 18px; font-weight: 500; }\r\n\r\n        \/* Output display *\/\r\n        #pth-pwgen-wrapper .pth-out-box { display: flex; align-items: center; gap: 10px; background: #f8fafc; border: 2px dashed #cbd5e1; border-radius: 10px; padding: 16px; margin-bottom: 16px; min-height: 68px; }\r\n        #pth-pwgen-wrapper .pth-out-text { flex: 1 1 auto; font-family: 'Courier New', monospace; font-size: 1.2rem; font-weight: 700; color: #1d4ed8; word-break: break-all; line-height: 1.45; }\r\n        #pth-pwgen-wrapper .pth-out-text.err { color: #991b1b; }\r\n        #pth-pwgen-wrapper .pth-icon-btn { flex: 0 0 auto; background: #ffffff; border: 1px solid #cbd5e1; color: #334155; width: 40px; height: 40px; border-radius: 8px; cursor: pointer; font-size: 1rem; }\r\n        #pth-pwgen-wrapper .pth-icon-btn:hover { background: #eff6ff; border-color: #93c5fd; }\r\n\r\n        \/* Strength *\/\r\n        #pth-pwgen-wrapper .pth-strength { margin-bottom: 18px; }\r\n        #pth-pwgen-wrapper .pth-strength-head { display: flex; justify-content: space-between; font-size: 0.82rem; font-weight: 700; color: #334155; margin-bottom: 7px; }\r\n        #pth-pwgen-wrapper .pth-bars { display: flex; gap: 5px; }\r\n        #pth-pwgen-wrapper .pth-bar { height: 7px; flex: 1; background: #e2e8f0; border-radius: 4px; }\r\n        #pth-pwgen-wrapper .s1 .pth-bar:nth-child(1) { background: #dc2626; }\r\n        #pth-pwgen-wrapper .s2 .pth-bar:nth-child(-n+2) { background: #d97706; }\r\n        #pth-pwgen-wrapper .s3 .pth-bar:nth-child(-n+3) { background: #2563eb; }\r\n        #pth-pwgen-wrapper .s4 .pth-bar:nth-child(-n+4) { background: #059669; }\r\n        #pth-pwgen-wrapper .s5 .pth-bar { background: #047857; }\r\n        #pth-pwgen-wrapper .v-weak { color: #991b1b; }\r\n        #pth-pwgen-wrapper .v-fair { color: #92400e; }\r\n        #pth-pwgen-wrapper .v-good { color: #1d4ed8; }\r\n        #pth-pwgen-wrapper .v-strong { color: #047857; }\r\n\r\n        \/* Controls *\/\r\n        #pth-pwgen-wrapper .pth-field { margin-bottom: 16px; }\r\n        #pth-pwgen-wrapper .pth-field > label { display: block; font-size: 0.88rem; font-weight: 700; margin-bottom: 8px; color: #334155; }\r\n        #pth-pwgen-wrapper .pth-hint { font-size: 0.79rem; color: #334155; font-style: italic; margin: 0 0 6px; }\r\n        #pth-pwgen-wrapper .pth-label-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }\r\n        #pth-pwgen-wrapper .pth-num { width: 76px; padding: 7px 9px; border: 2px solid #cbd5e1; border-radius: 7px; font-family: monospace; font-weight: 700; color: #0f172a; text-align: center; }\r\n        #pth-pwgen-wrapper .pth-slider { width: 100%; accent-color: #2563eb; }\r\n        #pth-pwgen-wrapper .pth-inp { width: 100%; padding: 11px 13px; border: 2px solid #cbd5e1; border-radius: 8px; font-family: 'Courier New', monospace; font-size: 0.95rem; color: #0f172a; background: #ffffff; font-weight: 600; }\r\n        #pth-pwgen-wrapper .pth-inp:focus, #pth-pwgen-wrapper .pth-num:focus { outline: none; border-color: #2563eb; box-shadow: 0 0 0 3px rgba(37,99,235,0.12); }\r\n        #pth-pwgen-wrapper select.pth-inp { font-family: inherit; }\r\n        #pth-pwgen-wrapper .pth-opts { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px,1fr)); gap: 9px; margin-bottom: 16px; }\r\n        #pth-pwgen-wrapper .pth-chk { display: flex; align-items: center; gap: 8px; font-size: 0.86rem; font-weight: 600; color: #334155; cursor: pointer; }\r\n        #pth-pwgen-wrapper .pth-row { display: flex; gap: 12px; flex-wrap: wrap; }\r\n        #pth-pwgen-wrapper .pth-row .pth-field { flex: 1 1 160px; }\r\n\r\n        #pth-pwgen-wrapper .pth-action-btn { background: #2563eb; color: #ffffff; border: none; padding: 12px 22px; font-size: 0.92rem; font-weight: 700; border-radius: 8px; cursor: pointer; font-family: inherit; }\r\n        #pth-pwgen-wrapper .pth-action-btn:hover { background: #1d4ed8; }\r\n        #pth-pwgen-wrapper .pth-mini-btn { background: #ffffff; color: #0f172a; border: 1px solid #cbd5e1; padding: 8px 13px; font-size: 0.8rem; font-weight: 700; border-radius: 6px; cursor: pointer; font-family: inherit; }\r\n        #pth-pwgen-wrapper .pth-mini-btn:hover { background: #f1f5f9; }\r\n        #pth-pwgen-wrapper .pth-btn-row { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px; }\r\n\r\n        \/* Info cards *\/\r\n        #pth-pwgen-wrapper .pth-metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px,1fr)); gap: 10px; margin-bottom: 16px; }\r\n        #pth-pwgen-wrapper .pth-metric { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 12px; text-align: center; }\r\n        #pth-pwgen-wrapper .pth-metric-val { font-size: 1.35rem; font-weight: 800; color: #1d4ed8; }\r\n        #pth-pwgen-wrapper .pth-metric-lbl { font-size: 0.72rem; color: #334155; font-weight: 700; text-transform: uppercase; letter-spacing: 0.3px; }\r\n\r\n        #pth-pwgen-wrapper .pth-finding { padding: 11px 14px; border-radius: 8px; font-size: 0.86rem; line-height: 1.55; margin-bottom: 9px; font-weight: 500; }\r\n        #pth-pwgen-wrapper .pth-f-ok { background: #ecfdf5; color: #047857; border: 1px solid #a7f3d0; }\r\n        #pth-pwgen-wrapper .pth-f-warn { background: #fefce8; color: #92400e; border: 1px solid #fde68a; }\r\n        #pth-pwgen-wrapper .pth-f-crit { background: #fef2f2; color: #991b1b; border: 1px solid #fecaca; }\r\n        #pth-pwgen-wrapper .pth-f-info { background: #eff6ff; color: #1d4ed8; border: 1px solid #bfdbfe; }\r\n        #pth-pwgen-wrapper .pth-finding strong { font-weight: 800; }\r\n\r\n        #pth-pwgen-wrapper .pth-table-wrap { overflow-x: auto; border: 1px solid #e2e8f0; border-radius: 8px; margin-top: 14px; }\r\n        #pth-pwgen-wrapper table.pth-table { width: 100%; border-collapse: collapse; }\r\n        #pth-pwgen-wrapper .pth-table th { background: #f1f5f9; color: #334155; font-weight: 800; padding: 10px 12px; text-align: left; font-size: 0.8rem; border-bottom: 2px solid #cbd5e1; }\r\n        #pth-pwgen-wrapper .pth-table td { padding: 9px 12px; border-bottom: 1px solid #f1f5f9; font-size: 0.86rem; color: #0f172a; }\r\n        #pth-pwgen-wrapper .pth-table td.mono { font-family: monospace; font-weight: 700; }\r\n\r\n        #pth-pwgen-wrapper .pth-list-box { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 14px; font-family: 'Courier New', monospace; font-size: 0.92rem; color: #0f172a; white-space: pre-wrap; word-break: break-all; min-height: 60px; margin-top: 14px; font-weight: 600; line-height: 1.75; }\r\n\r\n        #pth-pwgen-wrapper .pth-side-title { font-size: 0.95rem; font-weight: 800; margin: 0 0 10px; color: #0f172a; }\r\n        #pth-pwgen-wrapper .pth-side-title.mt { margin-top: 22px; }\r\n        #pth-pwgen-wrapper .pth-side-text { font-size: 0.84rem; color: #334155; line-height: 1.6; font-weight: 500; margin: 0 0 12px; }\r\n        #pth-pwgen-wrapper .pth-note { background: #fffbeb; border: 1px solid #fde68a; padding: 12px; border-radius: 8px; font-size: 0.83rem; color: #92400e; line-height: 1.55; font-weight: 500; margin-top: 16px; }\r\n        #pth-pwgen-wrapper .pth-note strong { font-weight: 800; }\r\n        #pth-pwgen-wrapper .pth-code { background: #e2e8f0; color: #0f172a; padding: 2px 6px; border-radius: 4px; font-family: monospace; font-size: 0.8rem; font-weight: 700; }\r\n        #pth-pwgen-wrapper .pth-related a { color: #1d4ed8; text-decoration: none; font-weight: 700; }\r\n        #pth-pwgen-wrapper .pth-related a:hover { text-decoration: underline; }\r\n        #pth-pwgen-wrapper .pth-related li { margin-bottom: 7px; font-size: 0.85rem; }\r\n\r\n        #pth-pwgen-wrapper .pth-toast-wrap { position: fixed; bottom: 20px; right: 20px; z-index: 999999; display: flex; flex-direction: column; gap: 8px; }\r\n        #pth-pwgen-wrapper .pth-toast { padding: 12px 18px; border-radius: 8px; color: #ffffff; font-size: 0.88rem; font-weight: 700; box-shadow: 0 8px 24px rgba(0,0,0,0.18); }\r\n        #pth-pwgen-wrapper .pth-toast.success { background: #059669; }\r\n        #pth-pwgen-wrapper .pth-toast.error { background: #dc2626; }\r\n        #pth-pwgen-wrapper .pth-toast.info { background: #2563eb; }\r\n\r\n        @media (max-width: 900px) {\r\n            #pth-pwgen-wrapper .pth-grid { grid-template-columns: 1fr; }\r\n            #pth-pwgen-wrapper .pth-main { border-right: none; border-bottom: 1px solid #e2e8f0; }\r\n        }\r\n    <\/style>\r\n\r\n    <div class=\"pth-topbar\">\r\n        <h2 class=\"pth-title\">Password Studio Pro<\/h2>\r\n        <div class=\"pth-toolbar\">\r\n            <button type=\"button\" class=\"pth-btn pth-sample-btn\" id=\"pth-pw-sample\">\ud83d\udca1 Load Sample<\/button>\r\n            <button type=\"button\" class=\"pth-btn pth-fw-btn\" id=\"pth-pw-fw\" aria-label=\"Toggle full width view\">\u26f6 Full Width<\/button>\r\n            <button type=\"button\" class=\"pth-btn pth-clear-btn\" id=\"pth-pw-clear\">\ud83d\uddd1\ufe0f Clear<\/button>\r\n        <\/div>\r\n    <\/div>\r\n    <div class=\"pth-meta-bar\">\r\n        <span class=\"pth-version-badge\">PASSWORD STUDIO v5.0<\/span>\r\n        <span class=\"pth-status-offline\">\ud83d\udd12 100% Offline<\/span>\r\n    <\/div>\r\n\r\n    <div class=\"pth-tabbar\" role=\"tablist\">\r\n        <button type=\"button\" class=\"pth-tab-btn active\" data-tab=\"gen\" role=\"tab\">\ud83d\udd10 Generator<\/button>\r\n        <button type=\"button\" class=\"pth-tab-btn\" data-tab=\"audit\" role=\"tab\">\ud83d\udd0d Strength Auditor<\/button>\r\n        <button type=\"button\" class=\"pth-tab-btn\" data-tab=\"pin\" role=\"tab\">\ud83d\udd22 PIN &amp; Recovery<\/button>\r\n        <button type=\"button\" class=\"pth-tab-btn\" data-tab=\"pattern\" role=\"tab\">\ud83d\udcd0 Custom Pattern<\/button>\r\n        <button type=\"button\" class=\"pth-tab-btn\" data-tab=\"phrase\" role=\"tab\">\ud83c\udfb2 Passphrase<\/button>\r\n    <\/div>\r\n\r\n    <div class=\"pth-grid\" id=\"pth-pw-grid\">\r\n        <div class=\"pth-main\">\r\n\r\n            <!-- TAB 1: GENERATOR -->\r\n            <div class=\"pth-panel active\" id=\"pth-panel-gen\" role=\"tabpanel\">\r\n                <p class=\"pth-panel-intro\">Random characters drawn from your browser's cryptographic generator, shuffled with an unbiased Fisher-Yates pass. The strength shown is real entropy, measured in bits.<\/p>\r\n\r\n                <div class=\"pth-out-box\">\r\n                    <div class=\"pth-out-text\" id=\"pth-gen-out\" aria-live=\"polite\">Press Generate<\/div>\r\n                    <button type=\"button\" class=\"pth-icon-btn\" id=\"pth-gen-copy\" aria-label=\"Copy password to clipboard\" title=\"Copy\">\ud83d\udccb<\/button>\r\n                <\/div>\r\n\r\n                <div class=\"pth-strength\">\r\n                    <div class=\"pth-strength-head\"><span>Strength<\/span><span id=\"pth-gen-verdict\" class=\"v-good\">&mdash;<\/span><\/div>\r\n                    <div class=\"pth-bars\" id=\"pth-gen-bars\"><div class=\"pth-bar\"><\/div><div class=\"pth-bar\"><\/div><div class=\"pth-bar\"><\/div><div class=\"pth-bar\"><\/div><div class=\"pth-bar\"><\/div><\/div>\r\n                <\/div>\r\n\r\n                <div class=\"pth-metrics\">\r\n                    <div class=\"pth-metric\"><div class=\"pth-metric-val\" id=\"pth-gen-bits\">0<\/div><div class=\"pth-metric-lbl\">Bits of entropy<\/div><\/div>\r\n                    <div class=\"pth-metric\"><div class=\"pth-metric-val\" id=\"pth-gen-pool\">0<\/div><div class=\"pth-metric-lbl\">Pool size<\/div><\/div>\r\n                    <div class=\"pth-metric\"><div class=\"pth-metric-val\" id=\"pth-gen-crack\" style=\"font-size:1rem;\">&mdash;<\/div><div class=\"pth-metric-lbl\">Offline GPU crack<\/div><\/div>\r\n                <\/div>\r\n\r\n                <div class=\"pth-field\">\r\n                    <div class=\"pth-label-row\">\r\n                        <label for=\"pth-gen-len\" style=\"margin:0;\">Length<\/label>\r\n                        <input type=\"number\" id=\"pth-gen-len\" class=\"pth-num\" value=\"20\" min=\"4\" max=\"128\" aria-label=\"Password length\">\r\n                    <\/div>\r\n                    <input type=\"range\" id=\"pth-gen-slider\" class=\"pth-slider\" min=\"4\" max=\"128\" value=\"20\" aria-label=\"Password length slider\">\r\n                <\/div>\r\n\r\n                <div class=\"pth-opts\">\r\n                    <label class=\"pth-chk\"><input type=\"checkbox\" id=\"pth-gen-upper\" checked> Uppercase (A&ndash;Z)<\/label>\r\n                    <label class=\"pth-chk\"><input type=\"checkbox\" id=\"pth-gen-lower\" checked> Lowercase (a&ndash;z)<\/label>\r\n                    <label class=\"pth-chk\"><input type=\"checkbox\" id=\"pth-gen-num\" checked> Numbers (0&ndash;9)<\/label>\r\n                    <label class=\"pth-chk\"><input type=\"checkbox\" id=\"pth-gen-sym\" checked> Symbols (!@#$&hellip;)<\/label>\r\n                    <label class=\"pth-chk\"><input type=\"checkbox\" id=\"pth-gen-amb\"> Exclude look-alikes (I l 1 O o 0 |)<\/label>\r\n                    <label class=\"pth-chk\"><input type=\"checkbox\" id=\"pth-gen-each\" checked> Guarantee one of each type<\/label>\r\n                <\/div>\r\n\r\n                <div class=\"pth-field\">\r\n                    <div class=\"pth-label-row\">\r\n                        <label for=\"pth-gen-count\" style=\"margin:0;\">How many passwords<\/label>\r\n                        <input type=\"number\" id=\"pth-gen-count\" class=\"pth-num\" value=\"1\" min=\"1\" max=\"100\" aria-label=\"Number of passwords to generate\">\r\n                    <\/div>\r\n                <\/div>\r\n\r\n                <button type=\"button\" class=\"pth-action-btn\" id=\"pth-gen-run\">\ud83d\udd10 Generate<\/button>\r\n                <div class=\"pth-btn-row\">\r\n                    <button type=\"button\" class=\"pth-mini-btn\" id=\"pth-gen-csv\">\u2b07\ufe0f Export CSV<\/button>\r\n                    <button type=\"button\" class=\"pth-mini-btn\" id=\"pth-gen-txt\">\u2b07\ufe0f Export .txt<\/button>\r\n                    <button type=\"button\" class=\"pth-mini-btn\" id=\"pth-gen-print\">\ud83d\udda8\ufe0f Print<\/button>\r\n                <\/div>\r\n                <div class=\"pth-list-box\" id=\"pth-gen-list\" style=\"display:none;\"><\/div>\r\n                <div class=\"pth-note\"><strong>About exports.<\/strong> A .txt, CSV or printed page stores the password in plain text. Use it to move a password to a manager, then delete the file and shred the paper.<\/div>\r\n            <\/div>\r\n\r\n            <!-- TAB 2: AUDITOR -->\r\n            <div class=\"pth-panel\" id=\"pth-panel-audit\" role=\"tabpanel\">\r\n                <p class=\"pth-panel-intro\">Paste a password you already use and see what an attacker sees: its real entropy, the patterns it leans on, and how long each kind of attack would take. Nothing is uploaded.<\/p>\r\n                <div class=\"pth-field\">\r\n                    <label for=\"pth-aud-input\">Password to audit<\/label>\r\n                    <p class=\"pth-hint\">\u2139\ufe0f This runs entirely on your device. Even so, prefer testing a close variant rather than the exact password of a live account.<\/p>\r\n                    <input type=\"text\" id=\"pth-aud-input\" class=\"pth-inp\" aria-label=\"Password to audit\" placeholder=\"Paste or type a password...\">\r\n                <\/div>\r\n                <button type=\"button\" class=\"pth-action-btn\" id=\"pth-aud-run\">\ud83d\udd0d Audit Password<\/button>\r\n\r\n                <div id=\"pth-aud-results\" style=\"display:none; margin-top:18px;\">\r\n                    <div class=\"pth-strength\">\r\n                        <div class=\"pth-strength-head\"><span>Verdict<\/span><span id=\"pth-aud-verdict\" class=\"v-good\">&mdash;<\/span><\/div>\r\n                        <div class=\"pth-bars\" id=\"pth-aud-bars\"><div class=\"pth-bar\"><\/div><div class=\"pth-bar\"><\/div><div class=\"pth-bar\"><\/div><div class=\"pth-bar\"><\/div><div class=\"pth-bar\"><\/div><\/div>\r\n                    <\/div>\r\n                    <div class=\"pth-metrics\">\r\n                        <div class=\"pth-metric\"><div class=\"pth-metric-val\" id=\"pth-aud-raw\">0<\/div><div class=\"pth-metric-lbl\">Raw bits<\/div><\/div>\r\n                        <div class=\"pth-metric\"><div class=\"pth-metric-val\" id=\"pth-aud-eff\">0<\/div><div class=\"pth-metric-lbl\">Effective bits<\/div><\/div>\r\n                        <div class=\"pth-metric\"><div class=\"pth-metric-val\" id=\"pth-aud-len\">0<\/div><div class=\"pth-metric-lbl\">Length<\/div><\/div>\r\n                        <div class=\"pth-metric\"><div class=\"pth-metric-val\" id=\"pth-aud-pool\">0<\/div><div class=\"pth-metric-lbl\">Pool size<\/div><\/div>\r\n                    <\/div>\r\n                    <h3 class=\"pth-side-title\">What was found<\/h3>\r\n                    <div id=\"pth-aud-findings\"><\/div>\r\n                    <h3 class=\"pth-side-title mt\">Time to crack, by attack type<\/h3>\r\n                    <div class=\"pth-table-wrap\">\r\n                        <table class=\"pth-table\">\r\n                            <thead><tr><th>Attack<\/th><th>Guesses \/ second<\/th><th>Average time<\/th><\/tr><\/thead>\r\n                            <tbody id=\"pth-aud-crack\"><\/tbody>\r\n                        <\/table>\r\n                    <\/div>\r\n                    <div class=\"pth-finding pth-f-info\" style=\"margin-top:14px;\"><strong>Read this honestly.<\/strong> Raw bits assume every character was chosen at random. Effective bits subtract the patterns found above, which is closer to what a modern cracking rig actually exploits. Both are estimates, not guarantees.<\/div>\r\n                <\/div>\r\n            <\/div>\r\n\r\n            <!-- TAB 3: PIN & RECOVERY -->\r\n            <div class=\"pth-panel\" id=\"pth-panel-pin\" role=\"tabpanel\">\r\n                <p class=\"pth-panel-intro\">Numeric PINs that avoid the patterns thieves try first, and grouped backup codes of the kind services hand out for two-factor recovery.<\/p>\r\n\r\n                <div class=\"pth-row\">\r\n                    <div class=\"pth-field\">\r\n                        <label for=\"pth-pin-len\">PIN digits<\/label>\r\n                        <input type=\"number\" id=\"pth-pin-len\" class=\"pth-inp\" value=\"6\" min=\"4\" max=\"12\" aria-label=\"Number of PIN digits\">\r\n                    <\/div>\r\n                    <div class=\"pth-field\">\r\n                        <label for=\"pth-pin-count\">How many PINs<\/label>\r\n                        <input type=\"number\" id=\"pth-pin-count\" class=\"pth-inp\" value=\"1\" min=\"1\" max=\"50\" aria-label=\"Number of PINs\">\r\n                    <\/div>\r\n                <\/div>\r\n                <label class=\"pth-chk\" style=\"margin-bottom:14px;\"><input type=\"checkbox\" id=\"pth-pin-safe\" checked> Reject weak patterns (1234, 0000, birth years, repeats)<\/label>\r\n                <button type=\"button\" class=\"pth-action-btn\" id=\"pth-pin-run\">\ud83d\udd22 Generate PINs<\/button>\r\n                <div class=\"pth-list-box\" id=\"pth-pin-out\" style=\"display:none;\"><\/div>\r\n\r\n                <h3 class=\"pth-side-title mt\">Recovery \/ backup codes<\/h3>\r\n                <div class=\"pth-row\">\r\n                    <div class=\"pth-field\">\r\n                        <label for=\"pth-rec-count\">How many codes<\/label>\r\n                        <input type=\"number\" id=\"pth-rec-count\" class=\"pth-inp\" value=\"8\" min=\"1\" max=\"24\" aria-label=\"Number of recovery codes\">\r\n                    <\/div>\r\n                    <div class=\"pth-field\">\r\n                        <label for=\"pth-rec-format\">Format<\/label>\r\n                        <select id=\"pth-rec-format\" class=\"pth-inp\" aria-label=\"Recovery code format\">\r\n                            <option value=\"5-5\">xxxxx-xxxxx (2 groups of 5)<\/option>\r\n                            <option value=\"4-4-4\">xxxx-xxxx-xxxx (3 groups of 4)<\/option>\r\n                            <option value=\"10\">xxxxxxxxxx (single group)<\/option>\r\n                        <\/select>\r\n                    <\/div>\r\n                <\/div>\r\n                <button type=\"button\" class=\"pth-action-btn\" id=\"pth-rec-run\">\ud83c\udf9f\ufe0f Generate Recovery Codes<\/button>\r\n                <div class=\"pth-btn-row\">\r\n                    <button type=\"button\" class=\"pth-mini-btn\" id=\"pth-rec-copy\">\ud83d\udccb Copy all<\/button>\r\n                    <button type=\"button\" class=\"pth-mini-btn\" id=\"pth-rec-txt\">\u2b07\ufe0f Export .txt<\/button>\r\n                <\/div>\r\n                <div class=\"pth-list-box\" id=\"pth-rec-out\" style=\"display:none;\"><\/div>\r\n                <div class=\"pth-note\"><strong>Codes use a look-alike-free alphabet.<\/strong> The letters I, L, O and the digits 0, 1 are left out, so a code read aloud or copied from paper does not become a support ticket.<\/div>\r\n            <\/div>\r\n\r\n            <!-- TAB 4: CUSTOM PATTERN -->\r\n            <div class=\"pth-panel\" id=\"pth-panel-pattern\" role=\"tabpanel\">\r\n                <p class=\"pth-panel-intro\">When a system demands a shape rather than a strength &mdash; a WiFi key, a licence code, a legacy field with a fixed mask &mdash; describe the shape and let the studio fill it randomly.<\/p>\r\n                <div class=\"pth-field\">\r\n                    <label for=\"pth-pat-input\">Pattern<\/label>\r\n                    <p class=\"pth-hint\">\u2139\ufe0f <span class=\"pth-code\">A<\/span> uppercase &nbsp; <span class=\"pth-code\">a<\/span> lowercase &nbsp; <span class=\"pth-code\">9<\/span> digit &nbsp; <span class=\"pth-code\">#<\/span> symbol &nbsp; <span class=\"pth-code\">H<\/span> hex &nbsp; <span class=\"pth-code\">*<\/span> any &nbsp; anything else is copied as-is.<\/p>\r\n                    <input type=\"text\" id=\"pth-pat-input\" class=\"pth-inp\" value=\"AAAA-9999-aaaa\" aria-label=\"Pattern template\">\r\n                <\/div>\r\n                <div class=\"pth-row\">\r\n                    <div class=\"pth-field\">\r\n                        <label for=\"pth-pat-count\">How many<\/label>\r\n                        <input type=\"number\" id=\"pth-pat-count\" class=\"pth-inp\" value=\"5\" min=\"1\" max=\"100\" aria-label=\"Number of pattern results\">\r\n                    <\/div>\r\n                <\/div>\r\n                <button type=\"button\" class=\"pth-action-btn\" id=\"pth-pat-run\">\ud83d\udcd0 Generate from Pattern<\/button>\r\n                <div class=\"pth-metrics\" style=\"margin-top:16px;\">\r\n                    <div class=\"pth-metric\"><div class=\"pth-metric-val\" id=\"pth-pat-bits\">0<\/div><div class=\"pth-metric-lbl\">Bits per result<\/div><\/div>\r\n                    <div class=\"pth-metric\"><div class=\"pth-metric-val\" id=\"pth-pat-slots\">0<\/div><div class=\"pth-metric-lbl\">Random slots<\/div><\/div>\r\n                <\/div>\r\n                <div id=\"pth-pat-warn\"><\/div>\r\n                <div class=\"pth-list-box\" id=\"pth-pat-out\" style=\"display:none;\"><\/div>\r\n                <div class=\"pth-btn-row\">\r\n                    <button type=\"button\" class=\"pth-mini-btn\" id=\"pth-pat-copy\">\ud83d\udccb Copy all<\/button>\r\n                    <button type=\"button\" class=\"pth-mini-btn\" id=\"pth-pat-ex1\">WiFi key<\/button>\r\n                    <button type=\"button\" class=\"pth-mini-btn\" id=\"pth-pat-ex2\">Licence key<\/button>\r\n                    <button type=\"button\" class=\"pth-mini-btn\" id=\"pth-pat-ex3\">Hex token<\/button>\r\n                <\/div>\r\n            <\/div>\r\n\r\n            <!-- TAB 5: PASSPHRASE -->\r\n            <div class=\"pth-panel\" id=\"pth-panel-phrase\" role=\"tabpanel\">\r\n                <p class=\"pth-panel-intro\">A passphrase trades character soup for words you can actually remember. Its strength comes only from the word count and the size of the list, never from how clever the words look.<\/p>\r\n\r\n                <div class=\"pth-out-box\">\r\n                    <div class=\"pth-out-text\" id=\"pth-ph-out\" style=\"font-size:1.05rem;\" aria-live=\"polite\">Press Generate<\/div>\r\n                    <button type=\"button\" class=\"pth-icon-btn\" id=\"pth-ph-copy\" aria-label=\"Copy passphrase to clipboard\" title=\"Copy\">\ud83d\udccb<\/button>\r\n                <\/div>\r\n                <div class=\"pth-metrics\">\r\n                    <div class=\"pth-metric\"><div class=\"pth-metric-val\" id=\"pth-ph-bits\">0<\/div><div class=\"pth-metric-lbl\">Bits of entropy<\/div><\/div>\r\n                    <div class=\"pth-metric\"><div class=\"pth-metric-val\" id=\"pth-ph-perword\">0<\/div><div class=\"pth-metric-lbl\">Bits per word<\/div><\/div>\r\n                    <div class=\"pth-metric\"><div class=\"pth-metric-val\" id=\"pth-ph-words\">0<\/div><div class=\"pth-metric-lbl\">Words in list<\/div><\/div>\r\n                <\/div>\r\n\r\n                <div class=\"pth-row\">\r\n                    <div class=\"pth-field\">\r\n                        <label for=\"pth-ph-count\">Number of words<\/label>\r\n                        <input type=\"number\" id=\"pth-ph-count\" class=\"pth-inp\" value=\"6\" min=\"3\" max=\"12\" aria-label=\"Number of words\">\r\n                    <\/div>\r\n                    <div class=\"pth-field\">\r\n                        <label for=\"pth-ph-sep\">Separator<\/label>\r\n                        <select id=\"pth-ph-sep\" class=\"pth-inp\" aria-label=\"Word separator\">\r\n                            <option value=\"-\">hyphen ( - )<\/option>\r\n                            <option value=\".\">dot ( . )<\/option>\r\n                            <option value=\"_\">underscore ( _ )<\/option>\r\n                            <option value=\" \">space<\/option>\r\n                        <\/select>\r\n                    <\/div>\r\n                <\/div>\r\n                <div class=\"pth-opts\">\r\n                    <label class=\"pth-chk\"><input type=\"checkbox\" id=\"pth-ph-cap\"> Capitalise each word<\/label>\r\n                    <label class=\"pth-chk\"><input type=\"checkbox\" id=\"pth-ph-num\"> Append a random digit<\/label>\r\n                <\/div>\r\n                <button type=\"button\" class=\"pth-action-btn\" id=\"pth-ph-run\">\ud83c\udfb2 Generate Passphrase<\/button>\r\n                <div id=\"pth-ph-advice\" style=\"margin-top:16px;\"><\/div>\r\n                <div class=\"pth-note\"><strong>Honest maths.<\/strong> Entropy is <span class=\"pth-code\">words &times; log2(list size)<\/span>. Capitalising or adding a digit adds only a bit or two &mdash; adding one more word adds far more. The EFF's 7776-word list gives 12.9 bits per word; this built-in list is smaller, so it needs more words to reach the same strength.<\/div>\r\n            <\/div>\r\n\r\n        <\/div>\r\n\r\n        <div class=\"pth-side\">\r\n            <h3 class=\"pth-side-title\">\ud83d\udee1\ufe0f What \"strong\" actually means<\/h3>\r\n            <p class=\"pth-side-text\">Strength is entropy: how many equally likely possibilities an attacker must search. It is measured in bits, and every extra bit doubles the work. A password is not strong because it looks messy; it is strong because it was chosen unpredictably.<\/p>\r\n\r\n            <h3 class=\"pth-side-title mt\">Rough targets<\/h3>\r\n            <div class=\"pth-table-wrap\" style=\"margin-top:0;\">\r\n                <table class=\"pth-table\">\r\n                    <thead><tr><th>Bits<\/th><th>Verdict<\/th><\/tr><\/thead>\r\n                    <tbody>\r\n                        <tr><td class=\"mono\">&lt; 28<\/td><td>Very weak<\/td><\/tr>\r\n                        <tr><td class=\"mono\">28&ndash;35<\/td><td>Weak<\/td><\/tr>\r\n                        <tr><td class=\"mono\">36&ndash;59<\/td><td>Fair<\/td><\/tr>\r\n                        <tr><td class=\"mono\">60&ndash;79<\/td><td>Strong<\/td><\/tr>\r\n                        <tr><td class=\"mono\">80+<\/td><td>Excellent<\/td><\/tr>\r\n                    <\/tbody>\r\n                <\/table>\r\n            <\/div>\r\n\r\n            <div class=\"pth-note\"><strong>\ud83d\udd12 Nothing leaves this page.<\/strong> Randomness comes from <span class=\"pth-code\">crypto.getRandomValues<\/span>. No password is stored, logged, sent, or written to browser storage.<\/div>\r\n\r\n            <h3 class=\"pth-side-title mt\">Related Tools<\/h3>\r\n            <ul class=\"pth-related\" style=\"list-style:none; padding:0; margin:0;\">\r\n                <li><a href=\"https:\/\/schoolict.net\/tools\/free-secure-hash-generator\/\">SHA Hash Generator<\/a><\/li>\r\n                <li><a href=\"https:\/\/schoolict.net\/tools\/bcrypt-hash-generator-verifier\/\">Bcrypt Hash &amp; Verify<\/a><\/li>\r\n                <li><a href=\"https:\/\/schoolict.net\/tools\/uuid-guid-generator-validator\/\">UUID \/ GUID Generator<\/a><\/li>\r\n                <li><a href=\"https:\/\/schoolict.net\/tools\/free-random-word-generator-nouns-verbs-adjectives\/\">Random Word Generator<\/a><\/li>\r\n            <\/ul>\r\n        <\/div>\r\n    <\/div>\r\n\r\n    <div class=\"pth-toast-wrap\" id=\"pth-pw-toast-wrap\" aria-live=\"assertive\"><\/div>\r\n\r\n    <script data-no-optimize=\"1\" data-no-minify=\"1\" data-cfasync=\"false\">\r\n    (function () {\r\n        'use strict';\r\n        if (window.pthPasswordApp) { return; }\r\n\r\n        window.pthPasswordApp = {\r\n            initialized: false,\r\n            lastList: [],\r\n            lastPin: [],\r\n            lastRec: [],\r\n            lastPat: [],\r\n\r\n            SETS: {\r\n                upper: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',\r\n                lower: 'abcdefghijklmnopqrstuvwxyz',\r\n                num: '0123456789',\r\n                sym: '!@#$%^&*()_+-=[]{};:,.?\/~'\r\n            },\r\n            AMBIGUOUS: 'IlL1OoO0|',\r\n            RECSET: '23456789abcdefghjkmnpqrstuvwxyz',\r\n            HEX: '0123456789ABCDEF',\r\n\r\n            WORDS: ['able','acid','acorn','actor','agent','alarm','album','alert','alien','alley','alpha','amber','amuse','angle','ankle','apple','apron','arbor','arena','armor','arrow','asset','atlas','attic','audio','avoid','awake','badge','bagel','baker','balmy','banjo','barge','basil','basin','batch','beach','beard','beast','bench','berry','birch','bison','blade','blaze','bleak','blend','bliss','block','bloom','blush','board','bonus','boost','booth','brace','braid','brand','brass','brave','bread','brick','brief','brisk','broad','broom','brush','buddy','buggy','build','bulky','bunch','cabin','cable','cadet','camel','candy','canoe','canon','canyon','cargo','carve','cedar','chalk','charm','chase','cheer','chess','chief','chime','chunk','cider','cigar','civic','claim','clamp','clash','clean','clerk','cliff','climb','cloak','clock','close','cloud','clove','coast','cobra','cocoa','comet','coral','couch','cough','court','cover','crane','crash','crate','crawl','cream','creek','crest','crisp','crown','crumb','crush','curve','cycle','daisy','dance','dandy','debut','decay','decoy','delay','delta','dense','depth','derby','diary','digit','dimly','diner','ditch','dizzy','dodge','donor','dough','dozen','draft','drama','drape','dream','dress','drift','drill','drink','drive','drone','drown','dwell','eager','eagle','early','earth','easel','ebony','eight','elbow','elder','elite','ember','empty','enact','enemy','enjoy','enter','entry','envoy','equal','equip','erase','error','essay','ethic','event','exact','exile','exist','extra','fable','faint','fairy','faith','false','fancy','farce','fatal','fault','favor','feast','fence','ferry','fever','fiber','field','fiery','fifty','fight','final','finch','flair','flame','flash','fleet','flint','float','flock','flood','floor','flour','fluid','flute','focal','foggy','forge','forte','forty','forum','found','frame','fresh','frost','fruit','fudge','funny','gauge','ghost','giant','giddy','glade','glass','gleam','globe','gloom','glory','glove','grace','grade','grain','grand','grant','grape','graph','grasp','grass','grave','great','green','greet','grill','groom','grove','guard','guess','guest','guide','guild','habit','handy','happy','harsh','haven','heart','heavy','hedge','hefty','hinge','hobby','honey','honor','horse','hotel','house','hover','human','humid','humor','hurry','ideal','image','inbox','index','inlet','inner','input','irony','issue','ivory','jelly','jewel','joint','jolly','judge','juice','jumbo','karma','kayak','kneel','knife','knock','koala','label','labor','lager','lance','lapse','large','larva','laser','latch','later','laugh','layer','ledge','lemon','level','lever','light','lilac','limbo','linen','liner','lithe','liver','llama','lobby','local','locus','lodge','logic','loose','lotus','lower','loyal','lucid','lucky','lunar','lunch','lyric','macro','madam','magic','major','maker','mango','manor','maple','march','marsh','match','maybe','mayor','medal','media','melon','mercy','merit','merry','metal','meter','micro','midst','might','mimic','miner','minor','mirth','model','moist','money','month','moral','motor','mount','mourn','mouse','mouth','movie','mural','music','naval','nerve','never','newly','niche','night','ninja','noble','noise','north','notch','novel','nudge','nurse','oasis','ocean','offer','olive','omega','onion','opera','orbit','order','organ','other','otter','ought','ounce','outer','owner','oxide','ozone','paddy','paint','panel','paper','parka','party','pasta','patch','patio','pause','peach','pearl','pedal','penny','perch','petal','phase','phone','photo','piano','picky','piece','pilot','pinch','pitch','pivot','pixel','pizza','place','plaid','plain','plane','plank','plant','plate','plaza','pluck','plumb','poems','point','polar','porch','poser','pound','power','prawn','press','price','pride','prime','print','prism','prize','probe','prone','proof','proud','prove','prune','pulse','punch','pupil','puppy','purse','quake','quart','queen','query','quest','queue','quick','quiet','quill','quilt','quirk','quota','quote','radar','radio','raise','rally','ranch','range','rapid','ratio','raven','reach','realm','rebel','refer','reign','relax','relay','remit','renew','repay','reply','rider','ridge','rifle','right','rigid','rinse','ripen','risky','rival','river','roast','robin','robot','rocky','rodeo','rogue','roman','roost','rotor','rouge','rough','round','route','royal','rugby','ruler','rumor','rural','saber','saint','salad','salon','salsa','salty','sandy','satin','sauce','scale','scarf','scene','scent','scoop','scope','score','scout','scrap','screw','scrub','sedan','seize','sense','serve','seven','shade','shaft','shale','shape','share','shark','sharp','shawl','shear','sheep','sheet','shelf','shell','shine','shiny','shirt','shock','shore','short','shove','shown','shrub','sight','sigma','silky','silly','siren','sixty','skate','skier','skill','skirt','slate','sleek','sleep','slice','slide','slope','small','smart','smell','smile','smoke','snack','snail','snake','sneak','solar','solid','solve','sonic','sorry','sound','south','space','spade','spare','spark','speak','spear','speed','spell','spend','spice','spike','spine','spiral','spoke','spoon','sport','spray','sprig','squad','squat','stack','staff','stage','stain','stair','stake','stalk','stamp','stand','stark','start','state','steam','steel','steep','steer','stern','stick','still','sting','stock','stone','stool','store','storm','stout','stove','strap','straw','strip','study','stump','sugar','suite','sunny','super','surge','swamp','swarm','swear','sweat','sweep','sweet','swell','swift','swing','sword','syrup','table','tacit','tally','tango','tapir','tarot','taste','tawny','teach','tempo','tenor','tense','tepid','thank','theft','theme','thick','thief','thigh','thing','think','third','thorn','three','throw','thumb','tiger','tight','tilde','timer','timid','tonic','tooth','topaz','torch','total','touch','tough','tower','toxic','trace','track','trade','trail','train','trait','tramp','trend','tribe','trick','tried','tropic','trout','truce','truck','truly','trunk','trust','truth','tulip','tumor','tunic','turbo','tutor','twice','twist','udder','ultra','uncle','under','union','unite','unity','upper','upset','urban','usage','usher','usual','utter','vague','valet','valid','value','valve','vapor','vault','venue','verse','video','vigor','villa','vinyl','viola','viper','viral','virus','visit','vital','vivid','vocal','vodka','voice','voter','vowel','wafer','wager','wagon','waist','waltz','waste','watch','water','weary','weave','wedge','weigh','weird','whale','wharf','wheat','wheel','where','whirl','whisk','white','whole','widen','width','wield','wince','windy','witty','woken','woman','world','worry','worth','wound','woven','wrist','write','wrong','yacht','yeast','yield','young','youth','zebra','zesty'],\r\n\r\n            RATES: [\r\n                { n: 'Online, rate-limited', r: 100, note: '100 \/ s' },\r\n                { n: 'Online, unthrottled', r: 10000, note: '10 thousand \/ s' },\r\n                { n: 'Offline, bcrypt (cost 12)', r: 20000, note: '20 thousand \/ s' },\r\n                { n: 'Offline, fast hash on GPU rig', r: 1e11, note: '100 billion \/ s' }\r\n            ],\r\n\r\n            \/* ============ INIT ============ *\/\r\n            init: function () {\r\n                this.wrapper = document.getElementById('pth-pwgen-wrapper');\r\n                if (!this.wrapper) { return; }\r\n                this.grid = document.getElementById('pth-pw-grid');\r\n                this.activeTab = 'gen';\r\n                this.attachEvents();\r\n                this.runGenerate();\r\n                this.updatePatternMetrics();\r\n                this.applyDeepLink();\r\n            },\r\n\r\n            applyDeepLink: function () {\r\n                try {\r\n                    var t = new URLSearchParams(window.location.search).get('tab');\r\n                    if (t) { this.switchTab(t); }\r\n                } catch (e) {}\r\n            },\r\n\r\n            attachEvents: function () {\r\n                var self = this, i;\r\n                var tabs = this.wrapper.querySelectorAll('.pth-tab-btn');\r\n                for (i = 0; i < tabs.length; i++) {\r\n                    tabs[i].addEventListener('click', function () { self.switchTab(this.getAttribute('data-tab')); });\r\n                }\r\n                document.getElementById('pth-pw-sample').addEventListener('click', function () { self.loadSample(); });\r\n                document.getElementById('pth-pw-clear').addEventListener('click', function () { self.clearActive(); });\r\n                document.getElementById('pth-pw-fw').addEventListener('click', function () { self.toggleWidth(); });\r\n\r\n                \/\/ Generator\r\n                var slider = document.getElementById('pth-gen-slider');\r\n                var lenNum = document.getElementById('pth-gen-len');\r\n                slider.addEventListener('input', function () { lenNum.value = this.value; self.runGenerate(); });\r\n                lenNum.addEventListener('input', function () {\r\n                    var v = parseInt(this.value, 10);\r\n                    if (isNaN(v)) { return; }\r\n                    if (v < 4) { v = 4; } if (v > 128) { v = 128; }\r\n                    slider.value = v;\r\n                    self.runGenerate();\r\n                });\r\n                var chk = ['pth-gen-upper','pth-gen-lower','pth-gen-num','pth-gen-sym','pth-gen-amb','pth-gen-each'];\r\n                for (i = 0; i < chk.length; i++) {\r\n                    document.getElementById(chk[i]).addEventListener('change', function () { self.runGenerate(); });\r\n                }\r\n                document.getElementById('pth-gen-count').addEventListener('input', function () { self.runGenerate(); });\r\n                document.getElementById('pth-gen-run').addEventListener('click', function () { self.runGenerate(); self.showToast('Generated', 'success'); });\r\n                document.getElementById('pth-gen-copy').addEventListener('click', function () { self.copyText(document.getElementById('pth-gen-out').textContent); });\r\n                document.getElementById('pth-gen-csv').addEventListener('click', function () { self.exportCsv(); });\r\n                document.getElementById('pth-gen-txt').addEventListener('click', function () { self.exportTxt(); });\r\n                document.getElementById('pth-gen-print').addEventListener('click', function () { self.printPasswords(); });\r\n\r\n                \/\/ Auditor\r\n                document.getElementById('pth-aud-run').addEventListener('click', function () { self.runAudit(); });\r\n                document.getElementById('pth-aud-input').addEventListener('input', function () { if (this.value) { self.runAudit(); } });\r\n\r\n                \/\/ PIN + recovery\r\n                document.getElementById('pth-pin-run').addEventListener('click', function () { self.runPins(); });\r\n                document.getElementById('pth-rec-run').addEventListener('click', function () { self.runRecovery(); });\r\n                document.getElementById('pth-rec-copy').addEventListener('click', function () { self.copyText(self.lastRec.join('\\n')); });\r\n                document.getElementById('pth-rec-txt').addEventListener('click', function () {\r\n                    if (!self.lastRec.length) { self.showToast('Generate codes first', 'info'); return; }\r\n                    self.downloadBlob('Recovery codes generated offline via primetoolhub.com\\nStore these somewhere safe and offline.\\n\\n' + self.lastRec.join('\\n'), 'recovery-codes.txt', 'text\/plain');\r\n                });\r\n\r\n                \/\/ Pattern\r\n                document.getElementById('pth-pat-run').addEventListener('click', function () { self.runPattern(); });\r\n                document.getElementById('pth-pat-input').addEventListener('input', function () { self.updatePatternMetrics(); });\r\n                document.getElementById('pth-pat-copy').addEventListener('click', function () { self.copyText(self.lastPat.join('\\n')); });\r\n                document.getElementById('pth-pat-ex1').addEventListener('click', function () { self.setPattern('****-****-****-****'); });\r\n                document.getElementById('pth-pat-ex2').addEventListener('click', function () { self.setPattern('AAAAA-AAAAA-AAAAA-99999'); });\r\n                document.getElementById('pth-pat-ex3').addEventListener('click', function () { self.setPattern('HHHHHHHH-HHHHHHHH'); });\r\n\r\n                \/\/ Passphrase\r\n                document.getElementById('pth-ph-run').addEventListener('click', function () { self.runPhrase(); });\r\n                document.getElementById('pth-ph-copy').addEventListener('click', function () { self.copyText(document.getElementById('pth-ph-out').textContent); });\r\n                var pc = ['pth-ph-count','pth-ph-sep','pth-ph-cap','pth-ph-num'];\r\n                for (i = 0; i < pc.length; i++) {\r\n                    document.getElementById(pc[i]).addEventListener('change', function () { self.runPhrase(); });\r\n                }\r\n            },\r\n\r\n            switchTab: function (name) {\r\n                var valid = { gen:1, audit:1, pin:1, pattern:1, phrase:1 };\r\n                if (!valid[name]) { return; }\r\n                this.activeTab = name;\r\n                var i, tabs = this.wrapper.querySelectorAll('.pth-tab-btn');\r\n                for (i = 0; i < tabs.length; i++) {\r\n                    if (tabs[i].getAttribute('data-tab') === name) { tabs[i].classList.add('active'); }\r\n                    else { tabs[i].classList.remove('active'); }\r\n                }\r\n                var panels = this.wrapper.querySelectorAll('.pth-panel');\r\n                for (i = 0; i < panels.length; i++) {\r\n                    if (panels[i].id === 'pth-panel-' + name) { panels[i].classList.add('active'); }\r\n                    else { panels[i].classList.remove('active'); }\r\n                }\r\n                if (name === 'phrase' && document.getElementById('pth-ph-out').textContent === 'Press Generate') { this.runPhrase(); }\r\n            },\r\n\r\n            toggleWidth: function () {\r\n                this.grid.classList.toggle('full-width');\r\n                var b = document.getElementById('pth-pw-fw');\r\n                b.textContent = this.grid.classList.contains('full-width') ? '\\u25E7 Split View' : '\\u26F6 Full Width';\r\n            },\r\n\r\n            loadSample: function () {\r\n                var t = this.activeTab;\r\n                if (t === 'gen') {\r\n                    document.getElementById('pth-gen-len').value = 20;\r\n                    document.getElementById('pth-gen-slider').value = 20;\r\n                    document.getElementById('pth-gen-count').value = 5;\r\n                    this.runGenerate();\r\n                } else if (t === 'audit') {\r\n                    document.getElementById('pth-aud-input').value = 'P@ssw0rd2024!';\r\n                    this.runAudit();\r\n                } else if (t === 'pin') {\r\n                    document.getElementById('pth-pin-len').value = 6;\r\n                    document.getElementById('pth-pin-count').value = 5;\r\n                    this.runPins();\r\n                    this.runRecovery();\r\n                } else if (t === 'pattern') {\r\n                    this.setPattern('AAAA-9999-aaaa');\r\n                } else if (t === 'phrase') {\r\n                    document.getElementById('pth-ph-count').value = 6;\r\n                    this.runPhrase();\r\n                }\r\n                this.showToast('Sample loaded', 'success');\r\n            },\r\n\r\n            clearActive: function () {\r\n                var t = this.activeTab;\r\n                if (t === 'gen') {\r\n                    document.getElementById('pth-gen-out').textContent = 'Press Generate';\r\n                    document.getElementById('pth-gen-list').style.display = 'none';\r\n                    this.lastList = [];\r\n                } else if (t === 'audit') {\r\n                    document.getElementById('pth-aud-input').value = '';\r\n                    document.getElementById('pth-aud-results').style.display = 'none';\r\n                } else if (t === 'pin') {\r\n                    document.getElementById('pth-pin-out').style.display = 'none';\r\n                    document.getElementById('pth-rec-out').style.display = 'none';\r\n                    this.lastPin = []; this.lastRec = [];\r\n                } else if (t === 'pattern') {\r\n                    document.getElementById('pth-pat-out').style.display = 'none';\r\n                    this.lastPat = [];\r\n                } else if (t === 'phrase') {\r\n                    document.getElementById('pth-ph-out').textContent = 'Press Generate';\r\n                    document.getElementById('pth-ph-advice').innerHTML = '';\r\n                }\r\n                this.showToast('Cleared', 'info');\r\n            },\r\n\r\n            \/* ============ SECURE RANDOM (rejection sampling, no modulo bias) ============ *\/\r\n            secureInt: function (max) {\r\n                if (max <= 0) { return 0; }\r\n                var limit = Math.floor(4294967296 \/ max) * max;\r\n                var a = new Uint32Array(1);\r\n                do { window.crypto.getRandomValues(a); } while (a[0] >= limit);\r\n                return a[0] % max;\r\n            },\r\n\r\n            pick: function (str) { return str.charAt(this.secureInt(str.length)); },\r\n\r\n            \/* Fisher-Yates, unbiased *\/\r\n            shuffle: function (arr) {\r\n                for (var i = arr.length - 1; i > 0; i--) {\r\n                    var j = this.secureInt(i + 1);\r\n                    var tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp;\r\n                }\r\n                return arr;\r\n            },\r\n\r\n            stripAmbiguous: function (s) {\r\n                var out = '', i;\r\n                for (i = 0; i < s.length; i++) {\r\n                    if (this.AMBIGUOUS.indexOf(s.charAt(i)) === -1) { out += s.charAt(i); }\r\n                }\r\n                return out;\r\n            },\r\n\r\n            \/* ============ ENTROPY + CRACK TIME ============ *\/\r\n            bits: function (length, pool) {\r\n                if (pool < 2 || length < 1) { return 0; }\r\n                return length * (Math.log(pool) \/ Math.log(2));\r\n            },\r\n\r\n            verdictFor: function (b) {\r\n                if (b < 28) { return { t:'Very weak', s:1, c:'v-weak' }; }\r\n                if (b < 36) { return { t:'Weak', s:2, c:'v-weak' }; }\r\n                if (b < 60) { return { t:'Fair', s:3, c:'v-fair' }; }\r\n                if (b < 80) { return { t:'Strong', s:4, c:'v-good' }; }\r\n                return { t:'Excellent', s:5, c:'v-strong' };\r\n            },\r\n\r\n            setBars: function (barsId, verdictId, v) {\r\n                var bars = document.getElementById(barsId);\r\n                bars.className = 'pth-bars s' + v.s;\r\n                var el = document.getElementById(verdictId);\r\n                el.textContent = v.t;\r\n                el.className = v.c;\r\n            },\r\n\r\n            \/* average guesses = 2^(bits-1) *\/\r\n            crackSeconds: function (b, rate) {\r\n                if (b <= 0) { return 0; }\r\n                return Math.pow(2, b - 1) \/ rate;\r\n            },\r\n\r\n            formatTime: function (sec) {\r\n                if (!isFinite(sec)) { return 'effectively forever'; }\r\n                if (sec < 1) { return 'instantly'; }\r\n                if (sec < 60) { return Math.round(sec) + ' seconds'; }\r\n                var mins = sec \/ 60;\r\n                if (mins < 60) { return Math.round(mins) + ' minutes'; }\r\n                var hrs = mins \/ 60;\r\n                if (hrs < 24) { return Math.round(hrs) + ' hours'; }\r\n                var days = hrs \/ 24;\r\n                if (days < 365) { return Math.round(days) + ' days'; }\r\n                var yrs = days \/ 365;\r\n                if (yrs < 1000) { return Math.round(yrs) + ' years'; }\r\n                if (yrs < 1e6) { return Math.round(yrs \/ 1000) + ' thousand years'; }\r\n                if (yrs < 1e9) { return Math.round(yrs \/ 1e6) + ' million years'; }\r\n                if (yrs < 1e12) { return Math.round(yrs \/ 1e9) + ' billion years'; }\r\n                if (yrs < 1e15) { return Math.round(yrs \/ 1e12) + ' trillion years'; }\r\n                var exp = Math.floor(Math.log(yrs) \/ Math.log(10));\r\n                return '10^' + exp + ' years';\r\n            },\r\n\r\n            \/* ============ TAB 1: GENERATOR ============ *\/\r\n            buildPools: function () {\r\n                var amb = document.getElementById('pth-gen-amb').checked;\r\n                var pools = [];\r\n                if (document.getElementById('pth-gen-upper').checked) { pools.push(amb ? this.stripAmbiguous(this.SETS.upper) : this.SETS.upper); }\r\n                if (document.getElementById('pth-gen-lower').checked) { pools.push(amb ? this.stripAmbiguous(this.SETS.lower) : this.SETS.lower); }\r\n                if (document.getElementById('pth-gen-num').checked) { pools.push(amb ? this.stripAmbiguous(this.SETS.num) : this.SETS.num); }\r\n                if (document.getElementById('pth-gen-sym').checked) { pools.push(amb ? this.stripAmbiguous(this.SETS.sym) : this.SETS.sym); }\r\n                return pools;\r\n            },\r\n\r\n            makePassword: function (length, pools, guarantee) {\r\n                var all = pools.join(''), chars = [], i;\r\n                if (guarantee && pools.length <= length) {\r\n                    for (i = 0; i < pools.length; i++) { chars.push(this.pick(pools[i])); }\r\n                }\r\n                while (chars.length < length) { chars.push(this.pick(all)); }\r\n                this.shuffle(chars);\r\n                return chars.join('');\r\n            },\r\n\r\n            runGenerate: function () {\r\n                var out = document.getElementById('pth-gen-out');\r\n                var pools = this.buildPools();\r\n                if (!pools.length) {\r\n                    out.textContent = 'Select at least one character type';\r\n                    out.className = 'pth-out-text err';\r\n                    document.getElementById('pth-gen-bits').textContent = '0';\r\n                    document.getElementById('pth-gen-pool').textContent = '0';\r\n                    document.getElementById('pth-gen-crack').textContent = '\\u2014';\r\n                    this.setBars('pth-gen-bars', 'pth-gen-verdict', { t:'None', s:0, c:'v-weak' });\r\n                    this.lastList = [];\r\n                    return;\r\n                }\r\n                out.className = 'pth-out-text';\r\n\r\n                var length = parseInt(document.getElementById('pth-gen-len').value, 10);\r\n                if (isNaN(length) || length < 4) { length = 4; }\r\n                if (length > 128) { length = 128; }\r\n                var count = parseInt(document.getElementById('pth-gen-count').value, 10);\r\n                if (isNaN(count) || count < 1) { count = 1; }\r\n                if (count > 100) { count = 100; }\r\n                var guarantee = document.getElementById('pth-gen-each').checked;\r\n\r\n                var list = [], i;\r\n                for (i = 0; i < count; i++) { list.push(this.makePassword(length, pools, guarantee)); }\r\n                this.lastList = list;\r\n                out.textContent = list[0];\r\n\r\n                var pool = pools.join('').length;\r\n                var b = this.bits(length, pool);\r\n                document.getElementById('pth-gen-bits').textContent = Math.round(b);\r\n                document.getElementById('pth-gen-pool').textContent = pool;\r\n                document.getElementById('pth-gen-crack').textContent = this.formatTime(this.crackSeconds(b, 1e11));\r\n                this.setBars('pth-gen-bars', 'pth-gen-verdict', this.verdictFor(b));\r\n\r\n                var listBox = document.getElementById('pth-gen-list');\r\n                if (count > 1) { listBox.textContent = list.join('\\n'); listBox.style.display = 'block'; }\r\n                else { listBox.style.display = 'none'; }\r\n            },\r\n\r\n            exportCsv: function () {\r\n                if (!this.lastList.length) { this.showToast('Generate first', 'info'); return; }\r\n                var csv = 'index,password\\n', i;\r\n                for (i = 0; i < this.lastList.length; i++) {\r\n                    csv += (i + 1) + ',\"' + this.lastList[i].replace(\/\"\/g, '\"\"') + '\"\\n';\r\n                }\r\n                this.downloadBlob(csv, 'passwords.csv', 'text\/csv');\r\n                this.showToast('CSV exported', 'success');\r\n            },\r\n\r\n            exportTxt: function () {\r\n                if (!this.lastList.length) { this.showToast('Generate first', 'info'); return; }\r\n                var txt = 'Generated offline via primetoolhub.com\\nDelete this file once the passwords are stored safely.\\n\\n' + this.lastList.join('\\n');\r\n                this.downloadBlob(txt, 'passwords.txt', 'text\/plain');\r\n                this.showToast('Text file exported', 'success');\r\n            },\r\n\r\n            printPasswords: function () {\r\n                if (!this.lastList.length) { this.showToast('Generate first', 'info'); return; }\r\n                var win = window.open('', '', 'height=520,width=680');\r\n                if (!win) { this.showToast('Popup blocked by browser', 'error'); return; }\r\n                var css = 'body{font-family:sans-serif;color:#0f172a;padding:36px;text-align:center;}'\r\n                    + '.pw{background:#f8fafc;border:2px dashed #cbd5e1;border-radius:8px;padding:14px;margin:10px 0;'\r\n                    + 'font-family:monospace;font-size:19px;word-break:break-all;}'\r\n                    + '.foot{font-size:12px;color:#334155;margin-top:28px;}'\r\n                    + 'body,.pw{ print-color-adjust:exact; -webkit-print-color-adjust:exact; }';\r\n                var body = '<h2>Your generated passwords<\/h2>', i;\r\n                for (i = 0; i < this.lastList.length; i++) {\r\n                    body += '<div class=\"pw\">' + this.escapeHtml(this.lastList[i]) + '<\/div>';\r\n                }\r\n                body += '<p class=\"foot\">Generated offline via primetoolhub.com. Destroy this page once the passwords are stored.<\/p>';\r\n                var doc = '<' + 'html><' + 'head><title>Passwords<' + '\/title>';\r\n                doc += '<' + 'style>' + css + '<' + '\/style><' + '\/head>';\r\n                doc += '<' + 'body>' + body + '<' + '\/body><' + '\/html>';\r\n                win.document.write(doc);\r\n                win.document.close();\r\n                win.focus();\r\n                setTimeout(function () { win.print(); win.close(); }, 350);\r\n            },\r\n\r\n            \/* ============ TAB 2: AUDITOR ============ *\/\r\n            COMMON: ['password','123456','12345678','qwerty','abc123','monkey','letmein','dragon','111111','baseball',\r\n                'iloveyou','trustno1','sunshine','master','welcome','shadow','ashley','football','jesus','michael',\r\n                'ninja','mustang','admin','login','princess','qwertyuiop','solo','starwars','freedom','whatever',\r\n                'hello','charlie','superman','batman','access','flower','hottie','loveme','zaq1zaq1','password1'],\r\n\r\n            leetNormalise: function (s) {\r\n                return s.toLowerCase()\r\n                    .replace(\/[@4]\/g, 'a').replace(\/[3]\/g, 'e').replace(\/[1!|]\/g, 'i')\r\n                    .replace(\/[0]\/g, 'o').replace(\/[$5]\/g, 's').replace(\/[7]\/g, 't').replace(\/[8]\/g, 'b');\r\n            },\r\n\r\n            detectPool: function (pw) {\r\n                var pool = 0, i, c;\r\n                var hasLower = false, hasUpper = false, hasDigit = false, hasSym = false, hasSpace = false;\r\n                for (i = 0; i < pw.length; i++) {\r\n                    c = pw.charAt(i);\r\n                    if (c >= 'a' && c <= 'z') { hasLower = true; }\r\n                    else if (c >= 'A' && c <= 'Z') { hasUpper = true; }\r\n                    else if (c >= '0' && c <= '9') { hasDigit = true; }\r\n                    else if (c === ' ') { hasSpace = true; }\r\n                    else { hasSym = true; }\r\n                }\r\n                if (hasLower) { pool += 26; }\r\n                if (hasUpper) { pool += 26; }\r\n                if (hasDigit) { pool += 10; }\r\n                if (hasSym) { pool += 32; }\r\n                if (hasSpace) { pool += 1; }\r\n                return { pool: pool, lower: hasLower, upper: hasUpper, digit: hasDigit, sym: hasSym };\r\n            },\r\n\r\n            hasSequence: function (pw, minRun) {\r\n                var lower = pw.toLowerCase(), run = 1, i, d;\r\n                for (i = 1; i < lower.length; i++) {\r\n                    d = lower.charCodeAt(i) - lower.charCodeAt(i - 1);\r\n                    if (d === 1 || d === -1) { run++; if (run >= minRun) { return true; } }\r\n                    else { run = 1; }\r\n                }\r\n                return false;\r\n            },\r\n\r\n            hasKeyboardWalk: function (pw) {\r\n                var rows = ['qwertyuiop','asdfghjkl','zxcvbnm','1234567890'];\r\n                var lower = pw.toLowerCase(), i, j, seg;\r\n                for (i = 0; i < rows.length; i++) {\r\n                    for (j = 0; j + 4 <= rows[i].length; j++) {\r\n                        seg = rows[i].substr(j, 4);\r\n                        if (lower.indexOf(seg) !== -1) { return true; }\r\n                        if (lower.indexOf(seg.split('').reverse().join('')) !== -1) { return true; }\r\n                    }\r\n                }\r\n                return false;\r\n            },\r\n\r\n            hasRepeat: function (pw) {\r\n                var i, run = 1;\r\n                for (i = 1; i < pw.length; i++) {\r\n                    if (pw.charAt(i) === pw.charAt(i - 1)) { run++; if (run >= 3) { return true; } }\r\n                    else { run = 1; }\r\n                }\r\n                return false;\r\n            },\r\n\r\n            hasRepeatedBlock: function (pw) {\r\n                var n = pw.length, size, block, ok, i;\r\n                for (size = 1; size <= Math.floor(n \/ 2); size++) {\r\n                    if (n % size !== 0) { continue; }\r\n                    block = pw.substr(0, size); ok = true;\r\n                    for (i = size; i < n; i += size) { if (pw.substr(i, size) !== block) { ok = false; break; } }\r\n                    if (ok) { return true; }\r\n                }\r\n                return false;\r\n            },\r\n\r\n            hasYear: function (pw) { return \/(19[0-9]{2}|20[0-2][0-9])\/.test(pw); },\r\n\r\n            matchesCommon: function (pw) {\r\n                var norm = this.leetNormalise(pw).replace(\/[^a-z]\/g, ''), i;\r\n                if (!norm) { return false; }\r\n                for (i = 0; i < this.COMMON.length; i++) {\r\n                    if (norm === this.COMMON[i] || norm.indexOf(this.COMMON[i]) !== -1) {\r\n                        if (this.COMMON[i].length >= 4) { return this.COMMON[i]; }\r\n                    }\r\n                }\r\n                return false;\r\n            },\r\n\r\n            escapeHtml: function (s) {\r\n                return String(s).replace(\/&\/g, '&amp;').replace(\/<\/g, '&lt;').replace(\/>\/g, '&gt;').replace(\/\"\/g, '&quot;');\r\n            },\r\n\r\n            runAudit: function () {\r\n                var pw = document.getElementById('pth-aud-input').value;\r\n                var box = document.getElementById('pth-aud-results');\r\n                if (!pw) { box.style.display = 'none'; return; }\r\n\r\n                var info = this.detectPool(pw);\r\n                var raw = this.bits(pw.length, info.pool);\r\n                var eff = raw, findings = [], hit;\r\n\r\n                hit = this.matchesCommon(pw);\r\n                if (hit) {\r\n                    eff = Math.min(eff, 12);\r\n                    findings.push({ c:'pth-f-crit', t:'<strong>Built on a known password.<\/strong> After undoing common letter-for-symbol swaps this contains \"' + this.escapeHtml(hit) + '\", which sits in every cracking wordlist. Substitutions like @ for a and 0 for o are the first thing a cracker tries.' });\r\n                }\r\n                if (this.hasRepeatedBlock(pw) && pw.length > 3) {\r\n                    eff = eff \/ 2;\r\n                    findings.push({ c:'pth-f-crit', t:'<strong>Repeated block.<\/strong> The whole password is one short chunk copied over. Its real strength is that of the chunk alone.' });\r\n                }\r\n                if (this.hasKeyboardWalk(pw)) {\r\n                    eff -= 10;\r\n                    findings.push({ c:'pth-f-warn', t:'<strong>Keyboard walk.<\/strong> A run of adjacent keys such as qwer or asdf is in every rule-based attack.' });\r\n                }\r\n                if (this.hasSequence(pw, 4)) {\r\n                    eff -= 8;\r\n                    findings.push({ c:'pth-f-warn', t:'<strong>Alphabet or number run.<\/strong> Sequences like abcd or 2345 are guessed cheaply.' });\r\n                }\r\n                if (this.hasRepeat(pw)) {\r\n                    eff -= 6;\r\n                    findings.push({ c:'pth-f-warn', t:'<strong>Three or more identical characters in a row.<\/strong> Crackers expand these patterns first.' });\r\n                }\r\n                if (this.hasYear(pw)) {\r\n                    eff -= 6;\r\n                    findings.push({ c:'pth-f-warn', t:'<strong>A year is embedded.<\/strong> Appending a birth year or the current year adds almost nothing, because only about a hundred years are ever tried.' });\r\n                }\r\n                if (pw.length < 8) {\r\n                    findings.push({ c:'pth-f-crit', t:'<strong>Too short.<\/strong> Under eight characters, no mixture of symbols saves a password from a modern GPU.' });\r\n                }\r\n                if (!info.sym && !info.digit && info.pool <= 52) {\r\n                    findings.push({ c:'pth-f-warn', t:'<strong>Letters only.<\/strong> The search pool is small. Extra length fixes this more cheaply than exotic symbols do.' });\r\n                }\r\n                if (!findings.length) {\r\n                    findings.push({ c:'pth-f-ok', t:'<strong>No obvious patterns found.<\/strong> No dictionary base, no runs, no repeats, no year. Its strength rests on length and pool size, which is exactly where it should rest.' });\r\n                }\r\n                if (eff < 0) { eff = 0; }\r\n\r\n                document.getElementById('pth-aud-raw').textContent = Math.round(raw);\r\n                document.getElementById('pth-aud-eff').textContent = Math.round(eff);\r\n                document.getElementById('pth-aud-len').textContent = pw.length;\r\n                document.getElementById('pth-aud-pool').textContent = info.pool;\r\n                this.setBars('pth-aud-bars', 'pth-aud-verdict', this.verdictFor(eff));\r\n\r\n                var fh = '', i;\r\n                for (i = 0; i < findings.length; i++) {\r\n                    fh += '<div class=\"pth-finding ' + findings[i].c + '\">' + findings[i].t + '<\/div>';\r\n                }\r\n                document.getElementById('pth-aud-findings').innerHTML = fh;\r\n\r\n                var rows = '';\r\n                for (i = 0; i < this.RATES.length; i++) {\r\n                    rows += '<tr><td>' + this.RATES[i].n + '<\/td><td class=\"mono\">' + this.RATES[i].note + '<\/td><td class=\"mono\">'\r\n                        + this.formatTime(this.crackSeconds(eff, this.RATES[i].r)) + '<\/td><\/tr>';\r\n                }\r\n                document.getElementById('pth-aud-crack').innerHTML = rows;\r\n                box.style.display = 'block';\r\n            },\r\n\r\n            \/* ============ TAB 3: PIN + RECOVERY ============ *\/\r\n            WEAK_PINS: { '1234':1, '0000':1, '1111':1, '1212':1, '7777':1, '1004':1, '2000':1, '4444':1, '2222':1, '6969':1, '9999':1, '3333':1, '5555':1, '6666':1, '1122':1, '1313':1, '8888':1, '4321':1, '2580':1, '1010':1 },\r\n\r\n            weakPin: function (p) {\r\n                var i, allSame = true, asc = true, desc = true;\r\n                if (this.WEAK_PINS[p]) { return true; }\r\n                for (i = 1; i < p.length; i++) {\r\n                    if (p.charAt(i) !== p.charAt(0)) { allSame = false; }\r\n                    if (p.charCodeAt(i) - p.charCodeAt(i - 1) !== 1) { asc = false; }\r\n                    if (p.charCodeAt(i) - p.charCodeAt(i - 1) !== -1) { desc = false; }\r\n                }\r\n                if (allSame || asc || desc) { return true; }\r\n                if (p.length === 4 && \/^(19[0-9]{2}|20[0-2][0-9])$\/.test(p)) { return true; }\r\n                return false;\r\n            },\r\n\r\n            runPins: function () {\r\n                var len = parseInt(document.getElementById('pth-pin-len').value, 10);\r\n                if (isNaN(len) || len < 4) { len = 4; } if (len > 12) { len = 12; }\r\n                var count = parseInt(document.getElementById('pth-pin-count').value, 10);\r\n                if (isNaN(count) || count < 1) { count = 1; } if (count > 50) { count = 50; }\r\n                var safe = document.getElementById('pth-pin-safe').checked;\r\n\r\n                var list = [], i, pin, tries;\r\n                for (i = 0; i < count; i++) {\r\n                    tries = 0;\r\n                    do {\r\n                        pin = '';\r\n                        for (var k = 0; k < len; k++) { pin += this.pick(this.SETS.num); }\r\n                        tries++;\r\n                    } while (safe && this.weakPin(pin) && tries < 200);\r\n                    list.push(pin);\r\n                }\r\n                this.lastPin = list;\r\n                var box = document.getElementById('pth-pin-out');\r\n                var b = this.bits(len, 10);\r\n                box.textContent = list.join('\\n') + '\\n\\n\\u2192 ' + Math.round(b) + ' bits of entropy per PIN (' + Math.pow(10, len).toLocaleString() + ' possibilities).';\r\n                box.style.display = 'block';\r\n                this.showToast('PINs generated', 'success');\r\n            },\r\n\r\n            runRecovery: function () {\r\n                var count = parseInt(document.getElementById('pth-rec-count').value, 10);\r\n                if (isNaN(count) || count < 1) { count = 1; } if (count > 24) { count = 24; }\r\n                var fmt = document.getElementById('pth-rec-format').value;\r\n                var groups = fmt === '5-5' ? [5,5] : (fmt === '4-4-4' ? [4,4,4] : [10]);\r\n\r\n                var list = [], i, g, k, parts, code;\r\n                for (i = 0; i < count; i++) {\r\n                    parts = [];\r\n                    for (g = 0; g < groups.length; g++) {\r\n                        code = '';\r\n                        for (k = 0; k < groups[g]; k++) { code += this.pick(this.RECSET); }\r\n                        parts.push(code);\r\n                    }\r\n                    list.push(parts.join('-'));\r\n                }\r\n                this.lastRec = list;\r\n                var total = 0;\r\n                for (g = 0; g < groups.length; g++) { total += groups[g]; }\r\n                var b = this.bits(total, this.RECSET.length);\r\n                var box = document.getElementById('pth-rec-out');\r\n                box.textContent = list.join('\\n') + '\\n\\n\\u2192 ' + Math.round(b) + ' bits per code, from a ' + this.RECSET.length + '-character alphabet.';\r\n                box.style.display = 'block';\r\n                this.showToast('Recovery codes generated', 'success');\r\n            },\r\n\r\n            \/* ============ TAB 4: CUSTOM PATTERN ============ *\/\r\n            setPattern: function (p) {\r\n                document.getElementById('pth-pat-input').value = p;\r\n                this.updatePatternMetrics();\r\n                this.runPattern();\r\n            },\r\n\r\n            tokenPool: function (ch) {\r\n                if (ch === 'A') { return this.SETS.upper; }\r\n                if (ch === 'a') { return this.SETS.lower; }\r\n                if (ch === '9') { return this.SETS.num; }\r\n                if (ch === '#') { return this.SETS.sym; }\r\n                if (ch === 'H') { return this.HEX; }\r\n                if (ch === '*') { return this.SETS.upper + this.SETS.lower + this.SETS.num + this.SETS.sym; }\r\n                return null;\r\n            },\r\n\r\n            patternStats: function (pat) {\r\n                var slots = 0, b = 0, i, pool;\r\n                for (i = 0; i < pat.length; i++) {\r\n                    pool = this.tokenPool(pat.charAt(i));\r\n                    if (pool) { slots++; b += Math.log(pool.length) \/ Math.log(2); }\r\n                }\r\n                return { slots: slots, bits: b };\r\n            },\r\n\r\n            updatePatternMetrics: function () {\r\n                var pat = document.getElementById('pth-pat-input').value;\r\n                var st = this.patternStats(pat);\r\n                document.getElementById('pth-pat-bits').textContent = Math.round(st.bits);\r\n                document.getElementById('pth-pat-slots').textContent = st.slots;\r\n                var warn = document.getElementById('pth-pat-warn');\r\n                if (st.slots === 0) {\r\n                    warn.innerHTML = '<div class=\"pth-finding pth-f-warn\"><strong>No random slots.<\/strong> Use A, a, 9, #, H or * to mark the positions that should be filled randomly.<\/div>';\r\n                } else if (st.bits < 60) {\r\n                    warn.innerHTML = '<div class=\"pth-finding pth-f-warn\"><strong>Only ' + Math.round(st.bits) + ' bits.<\/strong> Fixed separators add shape, never strength. Add more random slots if this guards anything valuable.<\/div>';\r\n                } else {\r\n                    warn.innerHTML = '<div class=\"pth-finding pth-f-ok\"><strong>' + Math.round(st.bits) + ' bits per result.<\/strong> Strong enough for a key that faces offline attack.<\/div>';\r\n                }\r\n            },\r\n\r\n            runPattern: function () {\r\n                var pat = document.getElementById('pth-pat-input').value;\r\n                var count = parseInt(document.getElementById('pth-pat-count').value, 10);\r\n                if (isNaN(count) || count < 1) { count = 1; } if (count > 100) { count = 100; }\r\n                if (!pat) { this.showToast('Enter a pattern', 'info'); return; }\r\n\r\n                var list = [], i, k, out, pool;\r\n                for (i = 0; i < count; i++) {\r\n                    out = '';\r\n                    for (k = 0; k < pat.length; k++) {\r\n                        pool = this.tokenPool(pat.charAt(k));\r\n                        out += pool ? this.pick(pool) : pat.charAt(k);\r\n                    }\r\n                    list.push(out);\r\n                }\r\n                this.lastPat = list;\r\n                var box = document.getElementById('pth-pat-out');\r\n                box.textContent = list.join('\\n');\r\n                box.style.display = 'block';\r\n                this.updatePatternMetrics();\r\n                this.showToast('Generated from pattern', 'success');\r\n            },\r\n\r\n            \/* ============ TAB 5: PASSPHRASE ============ *\/\r\n            runPhrase: function () {\r\n                var n = parseInt(document.getElementById('pth-ph-count').value, 10);\r\n                if (isNaN(n) || n < 3) { n = 3; } if (n > 12) { n = 12; }\r\n                var sep = document.getElementById('pth-ph-sep').value;\r\n                var cap = document.getElementById('pth-ph-cap').checked;\r\n                var addNum = document.getElementById('pth-ph-num').checked;\r\n\r\n                var words = [], i, w;\r\n                for (i = 0; i < n; i++) {\r\n                    w = this.WORDS[this.secureInt(this.WORDS.length)];\r\n                    if (cap) { w = w.charAt(0).toUpperCase() + w.substring(1); }\r\n                    words.push(w);\r\n                }\r\n                var phrase = words.join(sep);\r\n                if (addNum) { phrase += sep + this.secureInt(10); }\r\n\r\n                var perWord = Math.log(this.WORDS.length) \/ Math.log(2);\r\n                var b = n * perWord;\r\n                if (addNum) { b += Math.log(10) \/ Math.log(2); }\r\n\r\n                document.getElementById('pth-ph-out').textContent = phrase;\r\n                document.getElementById('pth-ph-bits').textContent = Math.round(b);\r\n                document.getElementById('pth-ph-perword').textContent = perWord.toFixed(1);\r\n                document.getElementById('pth-ph-words').textContent = this.WORDS.length;\r\n\r\n                var v = this.verdictFor(b), advice;\r\n                if (b < 60) {\r\n                    advice = '<div class=\"pth-finding pth-f-warn\"><strong>' + Math.round(b) + ' bits &mdash; ' + v.t + '.<\/strong> Add words until you pass 60 bits. Each extra word adds ' + perWord.toFixed(1) + ' bits; capitalising adds almost none.<\/div>';\r\n                } else if (b < 80) {\r\n                    advice = '<div class=\"pth-finding pth-f-info\"><strong>' + Math.round(b) + ' bits &mdash; ' + v.t + '.<\/strong> Fine for most accounts. One more word takes it comfortably past 80.<\/div>';\r\n                } else {\r\n                    advice = '<div class=\"pth-finding pth-f-ok\"><strong>' + Math.round(b) + ' bits &mdash; ' + v.t + '.<\/strong> Beyond the reach of offline attack, and still something you can say out loud.<\/div>';\r\n                }\r\n                document.getElementById('pth-ph-advice').innerHTML = advice;\r\n            },\r\n\r\n            \/* ============ UTIL ============ *\/\r\n            downloadBlob: function (content, filename, mime) {\r\n                var blob = new Blob([content], { type: mime });\r\n                var url = URL.createObjectURL(blob);\r\n                var a = document.createElement('a');\r\n                a.href = url; a.download = filename;\r\n                document.body.appendChild(a); a.click(); document.body.removeChild(a);\r\n                setTimeout(function () { URL.revokeObjectURL(url); }, 1000);\r\n            },\r\n\r\n            copyText: function (text) {\r\n                var self = this;\r\n                if (!text || text === 'Press Generate') { self.showToast('Nothing to copy yet', 'info'); return; }\r\n                if (navigator.clipboard && navigator.clipboard.writeText) {\r\n                    navigator.clipboard.writeText(text).then(function () { self.showToast('Copied to clipboard', 'success'); })\r\n                        .catch(function () { self.showToast('Copy blocked by browser', 'error'); });\r\n                } else { self.showToast('Clipboard not supported', 'error'); }\r\n            },\r\n\r\n            showToast: function (msg, type) {\r\n                var wrap = document.getElementById('pth-pw-toast-wrap');\r\n                if (!wrap) { return; }\r\n                var t = document.createElement('div');\r\n                t.className = 'pth-toast ' + (type || 'info');\r\n                t.setAttribute('role', 'status');\r\n                t.textContent = msg;\r\n                wrap.appendChild(t);\r\n                setTimeout(function () { if (t.parentNode) { t.parentNode.removeChild(t); } }, 3000);\r\n            }\r\n        };\r\n\r\n        \/* ---------- 4-Layer Bootstrap ---------- *\/\r\n        function pthPwInit() {\r\n            if (!window.pthPasswordApp.initialized && document.getElementById('pth-pwgen-wrapper')) {\r\n                window.pthPasswordApp.initialized = true;\r\n                window.pthPasswordApp.init();\r\n            }\r\n        }\r\n        pthPwInit();\r\n        if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', pthPwInit); }\r\n        window.addEventListener('load', function () { setTimeout(pthPwInit, 50); });\r\n        if (!window.pthPasswordApp.initialized && typeof MutationObserver !== 'undefined') {\r\n            var pthPwObs = new MutationObserver(function () {\r\n                if (document.getElementById('pth-pwgen-wrapper')) { pthPwObs.disconnect(); pthPwInit(); }\r\n            });\r\n            pthPwObs.observe(document.body || document.documentElement, { childList: true, subtree: true });\r\n            setTimeout(function () { pthPwObs.disconnect(); }, 10000);\r\n        }\r\n    })();\r\n    <\/script>\r\n<\/div>\n\n\n\n\n<style>\n.pth-container{max-width:1480px;margin:0 auto;padding:0 20px;box-sizing:border-box}\n.pth-feature-wrap{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:20px;margin-bottom:40px}\n.pth-feat-box{border:1px solid #e2e8f0;background:#fff;padding:24px;border-radius:12px;box-shadow:0 4px 6px -1px rgba(0,0,0,0.02);transition:all .3s ease}\n.pth-feat-box:hover{border-color:#bfdbfe;transform:translateY(-3px);box-shadow:0 10px 15px -3px rgba(0,0,0,0.05)}\n.pth-feat-title{font-size:1.05rem;font-weight:800;color:#0f172a;margin:0 0 10px;display:flex;align-items:center;gap:10px}\n.pth-feat-text{font-size:.9rem;color:#1e293b;line-height:1.6;margin:0;font-weight:500}\n.pth-steps-wrap{border:1px solid #e2e8f0;background:#fff;padding:40px 30px;border-radius:16px;text-align:center;box-shadow:0 4px 6px -1px rgba(0,0,0,0.02);margin-bottom:40px;width:100%;box-sizing:border-box}\n.pth-steps-title{font-size:1.2rem;font-weight:800;color:#0f172a;text-transform:uppercase;margin:0 0 35px;letter-spacing:1px}\n.pth-steps-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:30px}\n.pth-step-item{display:flex;flex-direction:column;align-items:center}\n.pth-step-num{width:45px;height:45px;background:#2563eb;color:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:1.2rem;margin-bottom:15px;box-shadow:0 4px 10px rgba(37,99,235,.3)}\n.pth-step-name{font-weight:800;color:#0f172a;margin:0 0 8px;font-size:1rem}\n.pth-step-desc{font-size:.9rem;color:#1e293b;line-height:1.5;margin:0;font-weight:500}\n<\/style>\n<div class=\"pth-container\">\n  <div class=\"pth-feature-wrap\">\n    <div class=\"pth-feat-box\">\n      <p class=\"pth-feat-title\"><span aria-hidden=\"true\">\ud83d\udfe2<\/span> Generate without the hidden bias<\/p>\n      <p class=\"pth-feat-text\">Characters are drawn from your browser&#8217;s cryptographic generator and shuffled with an unbiased Fisher-Yates pass, so no position is more predictable than another. The strength shown is real entropy in bits, not a vague label, and you can mint up to a hundred at once.<\/p>\n    <\/div>\n    <div class=\"pth-feat-box\">\n      <p class=\"pth-feat-title\"><span aria-hidden=\"true\">\ud83d\udd35<\/span> Audit a password you already use<\/p>\n      <p class=\"pth-feat-text\">Paste an existing password and the studio shows what an attacker sees: its raw bits, the effective bits after common patterns are subtracted, and the exact weaknesses it found. It catches keyboard walks, dates, repeats and leetspeak versions of known passwords.<\/p>\n    <\/div>\n    <div class=\"pth-feat-box\">\n      <p class=\"pth-feat-title\"><span aria-hidden=\"true\">\ud83d\udfe3<\/span> Build PINs, codes and passphrases<\/p>\n      <p class=\"pth-feat-text\">Numeric PINs that reject 1234 and birth years, grouped recovery codes in a look-alike-free alphabet, pattern-based keys for WiFi and licences, and memorable word passphrases with honest per-word entropy all live in their own tabs.<\/p>\n    <\/div>\n  <\/div>\n  <div class=\"pth-steps-wrap\">\n    <p class=\"pth-steps-title\">How to Use the Password Studio<\/p>\n    <div class=\"pth-steps-grid\">\n      <div class=\"pth-step-item\">\n        <div class=\"pth-step-num\" aria-hidden=\"true\">1<\/div>\n        <p class=\"pth-step-name\">Generator<\/p>\n        <p class=\"pth-step-desc\">Set the length, tick the character types, and press Generate. Watch the entropy and crack-time update live.<\/p>\n      <\/div>\n      <div class=\"pth-step-item\">\n        <div class=\"pth-step-num\" aria-hidden=\"true\">2<\/div>\n        <p class=\"pth-step-name\">Strength Auditor<\/p>\n        <p class=\"pth-step-desc\">Paste any password to see its effective bits, the patterns it leans on, and how each attack type would fare.<\/p>\n      <\/div>\n      <div class=\"pth-step-item\">\n        <div class=\"pth-step-num\" aria-hidden=\"true\">3<\/div>\n        <p class=\"pth-step-name\">PIN &amp; Recovery<\/p>\n        <p class=\"pth-step-desc\">Create safe numeric PINs and backup codes for two-factor recovery, in the format your service expects.<\/p>\n      <\/div>\n      <div class=\"pth-step-item\">\n        <div class=\"pth-step-num\" aria-hidden=\"true\">4<\/div>\n        <p class=\"pth-step-name\">Pattern &amp; Passphrase<\/p>\n        <p class=\"pth-step-desc\">Fill a fixed template for keys, or build a word passphrase you can actually remember and type.<\/p>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/div>\n \n\n\n\n<p class=\"wp-block-paragraph\">Last updated: July 2026<\/p>\n\n\n\n<h2 id=\"\ud83d\udd34-why-a-random-looking-password-is-not-the-same-as-a-random-one\" class=\"wp-block-heading\">\ud83d\udd34&nbsp;<strong>Why a random-looking password is not the same as a random one<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Most password tools generate something that looks scrambled and call it done. The trouble hides in how the characters are ordered. A common shortcut is to force one uppercase, one digit and one symbol into the string, then shuffle with a quick sort trick. That sort is not a real shuffle, and it leaves the first character heavily weighted toward the uppercase letter that was placed first. An attacker who knows the tool can lean on that. This studio uses a Fisher-Yates shuffle instead, the only method that gives every arrangement an equal chance, so the guaranteed character types are spread genuinely at random.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The randomness itself comes from&nbsp;crypto.getRandomValues, your browser&#8217;s cryptographic generator, never from the ordinary random function that is fine for shuffling a playlist but not for guarding an account. There is even a subtle bias lurking in how random numbers get mapped onto a character set: a naive modulo makes the first few characters slightly more likely. The generator here rejects and re-rolls the rare values that would cause that skew, so every character in the pool is equally probable. These are the kinds of details that separate a password you can trust from one that merely looks the part.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You will notice the strength readout is a number of bits, not a colour word. That is deliberate, and the next section explains why it is the only honest measure.<\/p>\n\n\n\n<h2 id=\"\ud83d\udfe1-reading-the-strength-meter-and-trusting-the-auditor\" class=\"wp-block-heading\">\ud83d\udfe1&nbsp;<strong>Reading the strength meter, and trusting the auditor<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Strength is entropy, and entropy is bits. The formula is plain: length multiplied by the base-two logarithm of the pool size. A twenty-character password drawn from ninety-four possible symbols carries about a hundred and thirty bits, and every single bit doubles the attacker&#8217;s work. This matters because meters that score on looks get it badly wrong. A sixty-four-character passphrase of nothing but lowercase letters holds around three hundred bits and is essentially uncrackable, yet a naive checker often rates it merely &#8220;fair&#8221; because it lacks symbols. The Generator here reports the real figure, so length gets the credit it deserves.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The Strength Auditor turns that lens on a password you already have. It reports raw bits, then effective bits, and the gap between them is the interesting part. Raw bits assume every character was a fair coin toss. Effective bits subtract for the shortcuts real crackers exploit. Feed it&nbsp;P@ssw0rd2024!&nbsp;and it does not see a strong mix of four character types; it undoes the leetspeak, recognises the base word &#8220;password&#8221;, spots the appended year, and collapses the score accordingly. That is exactly what a modern cracking rig does in its first pass. The crack-time table then shows how long each kind of attacker would take, from a rate-limited login form to an offline GPU rig, because a password that is hopeless against an offline attack can still be fine behind a good lockout policy.<\/p>\n\n\n\n<h3 id=\"\ud83d\udfe2-the-patterns-the-auditor-hunts-for\" class=\"wp-block-heading\">\ud83d\udfe2&nbsp;<strong>The patterns the auditor hunts for<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\ud83d\udd35 Keyboard walks such as&nbsp;qwer&nbsp;or&nbsp;asdf, and their reverses.<br>\ud83d\udfe0 Alphabet or number runs like&nbsp;abcd&nbsp;and&nbsp;2345, three-in-a-row repeats, and whole passwords built from one repeated block.<br>\ud83d\udfe3 Embedded years, and dictionary words hiding under symbol swaps, which is why&nbsp;P@ssw0rd&nbsp;is treated as the word it plainly is.<\/p>\n\n\n<figure class=\"pth-article-figure pth-img-left\" style=\"float:left; width:700px; max-width:100%; margin:4px 28px 16px 0; clear:left;\"><img decoding=\"async\" src=\"https:\/\/schoolict.net\/tools\/wp-content\/uploads\/2026\/07\/password-attack-rates-800x447.jpeg\" alt=\"Table of crack times for different attacker speeds from throttled login to GPU rig\" width=\"700\" height=\"394\" loading=\"lazy\" data-no-lazy=\"1\" class=\"pth-article-img\" style=\"width:100%;height:auto;display:block;border-radius:10px;border:1px solid #e2e8f0;\"><\/figure>\n\n\n\n<h2 id=\"\ud83d\udfe2-pi-ns-recovery-codes-and-keys-with-a-fixed-shape\" class=\"wp-block-heading\">\ud83d\udfe2&nbsp;<strong>PINs, recovery codes and keys with a fixed shape<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Not every secret is a website password. A phone unlock is a PIN, and PINs have their own trap: a huge share of people pick&nbsp;1234,&nbsp;0000&nbsp;or a birth year, so a thief with three guesses tries those first. The PIN generator rejects that whole family of predictable choices, including ascending and descending runs and any four digits that look like a recent year, and it tells you how many possibilities your chosen length actually spans.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The recovery-code generator produces the grouped codes services hand out for two-factor backup, in a deliberately reduced alphabet that omits the characters people misread. Leaving out capital I, lowercase l, the letter O and the digits zero and one means a code copied from a screenshot or read down a phone line does not turn into a failed login. The Custom Pattern tab covers the odder requirements: give it a template like&nbsp;AAAA-9999-aaaa&nbsp;and it fills each marked slot at random while keeping your separators, and it still tells you how many real bits of randomness the result carries, so a pretty-looking key with too few slots cannot fool you into a false sense of safety.<\/p>\n\n\n\n<h2 id=\"\ud83d\udd34-what-this-tool-refuses-to-do-on-purpose\" class=\"wp-block-heading\">\ud83d\udd34&nbsp;<strong>What this tool refuses to do, on purpose<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Everything runs in your browser, and it stays that way by design. No password is saved, logged, or written to browser storage, and nothing is ever sent to a server, which is what makes it safe to audit a password you actually use. That last point rules out one feature you might expect: a check against breach databases like Have I Been Pwned. That check needs a network request, and adding it would quietly break the promise that nothing leaves your device, so it is left out. The exports carry the same honesty. A .txt file, a CSV or a printed page holds the password in plain text, so the tool tells you to move it into a manager and then destroy the copy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The larger point is that a generator is one link in a chain. A strong, unique password per site is only as good as where you keep it, which for most people means a password manager rather than memory or a notebook. Passwords also pair with other building blocks: when you need a one-way fingerprint of data rather than a login secret, the&nbsp;<a href=\"https:\/\/schoolict.net\/tools\/free-secure-hash-generator\/\" rel=\"noreferrer noopener\" target=\"_blank\">Secure Hash Generator<\/a>&nbsp;covers SHA hashing, and for how sites should store passwords safely, the&nbsp;<a href=\"https:\/\/schoolict.net\/tools\/bcrypt-hash-generator-verifier\/\" rel=\"noreferrer noopener\" target=\"_blank\">Bcrypt Hash and Verify<\/a>&nbsp;tool shows the slow hashing they ought to use. If you like the passphrase idea and want more raw word material to play with, the&nbsp;<a href=\"https:\/\/schoolict.net\/tools\/free-random-word-generator-nouns-verbs-adjectives\/\" rel=\"noreferrer noopener\" target=\"_blank\">Random Word Generator<\/a>&nbsp;is next door. The reasoning behind favouring length and passphrases over forced complexity is set out in the widely cited&nbsp;<a href=\"https:\/\/pages.nist.gov\/800-63-3\/sp800-63b.html\" rel=\"noreferrer noopener\" target=\"_blank\">NIST digital identity guidelines<\/a>.<\/p>\n\n\n\n\n<style>\n.pth-faq-section{margin:50px auto 40px;font-family:inherit;max-width:1480px;padding:0 20px;box-sizing:border-box}\n.pth-faq-header{font-size:1.8rem;font-weight:800;color:#0f172a;margin-bottom:25px;border-bottom:2px solid #e2e8f0;padding-bottom:10px;display:flex;align-items:center;gap:10px}\n.pth-faq-grid{display:grid;grid-template-columns:1fr;gap:20px}\n@media(min-width:768px){.pth-faq-grid{grid-template-columns:repeat(2,1fr)}}\n@media(min-width:1024px){.pth-faq-grid{grid-template-columns:repeat(3,1fr)}}\n.pth-faq-card{background:#f8fafc;padding:24px;border-radius:12px;border:1px solid #e2e8f0;transition:transform .2s ease;break-inside:avoid}\n.pth-faq-card:hover{transform:translateY(-3px);box-shadow:0 4px 12px rgba(0,0,0,.05)}\n.pth-faq-q{color:#0f172a;font-size:1rem;font-weight:700;margin:0 0 12px;line-height:1.4}\n.pth-faq-a{margin:0;font-size:.95rem;color:#1e293b;line-height:1.6;font-weight:500}\n<\/style>\n<div class=\"pth-faq-section\">\n  <div class=\"pth-faq-header\">\u2753 Frequently Asked Questions<\/div>\n  <div class=\"pth-faq-grid\">\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Is my password sent anywhere?<\/p>\n      <p class=\"pth-faq-a\">No. Generation and auditing run entirely in your browser using its cryptographic generator. Nothing is uploaded, logged, or saved to browser storage, so even the Auditor is safe to use.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">What does &#8220;bits of entropy&#8221; mean?<\/p>\n      <p class=\"pth-faq-a\">It measures how many equally likely possibilities an attacker must search, as length times log2 of the pool size. Every extra bit doubles the effort. Sixty bits or more is strong for most accounts.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Why is a long lowercase phrase stronger than a short complex one?<\/p>\n      <p class=\"pth-faq-a\">Because length raises entropy faster than symbol variety. A long passphrase can hold far more bits than a short string with mixed characters, even though it looks simpler.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Why did the Auditor rate P@ssw0rd as weak?<\/p>\n      <p class=\"pth-faq-a\">Because crackers undo predictable swaps first. It reverses @ to a and 0 to o, finds the base word &#8220;password&#8221;, and treats it as the common word it is, regardless of the symbols.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Does it check breach databases?<\/p>\n      <p class=\"pth-faq-a\">No, and that is intentional. Checking a breach list needs a network request, which would break the promise that nothing leaves your device. Use a dedicated breach-check service separately if you need one.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Are the exported files safe?<\/p>\n      <p class=\"pth-faq-a\">A .txt, CSV or printout stores the password in plain text. Use them only to move a password into a manager, then delete the file and destroy any printout.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">What makes a PIN weak?<\/p>\n      <p class=\"pth-faq-a\">Predictable choices like 1234, 0000, repeated digits, ascending or descending runs, and birth years. The generator rejects these so your PIN is not among the first a thief tries.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Why leave letters out of recovery codes?<\/p>\n      <p class=\"pth-faq-a\">Capital I, lowercase l, the letter O and the digits 0 and 1 are easily confused. Removing them means a code read aloud or copied from an image still works the first time.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Should I still use a password manager?<\/p>\n      <p class=\"pth-faq-a\">Yes. A unique strong password per site is only useful if you can store them all. A manager remembers them so you do not reuse one, which is the single biggest real-world risk.<\/p>\n    <\/div>\n  <\/div>\n<\/div>\n \n","protected":false},"excerpt":{"rendered":"<p>Password Studio Free offline password studio. Generate cryptographically strong passwords, audit your own for real entropy and weak patterns, and build PINs, recovery codes and passphrases. Nothing is uploaded. \ud83d\udfe2 Generate without the hidden bias Characters are drawn from your browser&#8217;s cryptographic generator and shuffled with an unbiased Fisher-Yates pass, so no position is more &#8230; <a title=\"Free Strong Password Generator |  Generate, Audit &amp; Build Secure Passwords Offline\" class=\"read-more\" href=\"https:\/\/schoolict.net\/tools\/free-strong-password-generator\/\" aria-label=\"Read more about Free Strong Password Generator |  Generate, Audit &amp; Build Secure Passwords Offline\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":487,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-481","page","type-page","status-publish","has-post-thumbnail"],"_links":{"self":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages\/481","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/comments?post=481"}],"version-history":[{"count":0,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages\/481\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/media\/487"}],"wp:attachment":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/media?parent=481"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}