Contact / Keep secrets out of messages

Let’s make security clearer.

Ask a question, report an accessibility issue, or tell us where the checker could be more useful. Never include a password or recovery code.

Prepare your message

This form checks your details locally, then prepares a link to your email application. It does not send a message or create a support ticket.

20–1,500 characters. Describe the behavior, not the secret you entered.

Direct contact

A simple email route.

[email protected]

For a technical issue, include your browser, operating system, and the action that failed. Do not attach a screenshot containing credentials.

This static website cannot verify mailbox availability or provide a delivery receipt. No response-time commitment is made.

Business identity

Operator transparency

Proposed entity: Sortsptscenegarden Digital Ltd
Company number: not issued
Registered address: not established
No public office or postal contact is available.

The proposed entity is illustrative and unregistered. No invented registration number or deliverable business address is presented as fact.

Abstract street-map illustrationA decorative grid of streets and a curved waterway. No business location or geographical coordinates are represented.
Online contact only

Static illustration—not a location map. No mapping provider is loaded and no location permission is requested.

This site has no optional tracking technologies. Accepting and rejecting both leave analytics and advertising off.

Essential preference storageAlways active after your choice
Analytics & advertisingNot installed

Your choice is stored only in this browser. Read the cookie policy.

===ENDFILE=== ===FILE: styles.css=== /* Visual system: Swiss minimalist layout; consistent top-border-accent cards. */ :root { --paper: #f6f7f2; --surface: #ffffff; --graphite: #202c2a; --muted: #53625c; --green: #0b6550; --green-dark: #074c3b; --line: #d3ddd5; --cyan: #a8e2e2; --danger: #a72f33; --space-1: 4px; --space-2: 8px; --space-3: 16px; --space-4: 24px; --space-5: 40px; --space-6: 64px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; color-scheme: light; } * { box-sizing: border-box; } html { scroll-behavior: smooth; scroll-padding-bottom: 240px; } body { margin: 0; background: var(--paper); color: var(--graphite); font-size: 16px; line-height: 1.65; } body > * { min-width: 0; } h1, h2, h3, p, figure, ul, ol, dl { margin: 0; } h1, h2, h3, nav, .brand, a, address, strong, td, th, button { overflow-wrap: anywhere; } h1, h2, h3 { line-height: 1.1; font-weight: 650; letter-spacing: -.04em; } h1 { font-size: clamp(2.7rem, 5.5vw, 5.2rem); } h2 { font-size: clamp(1.7rem, 3vw, 2.7rem); } h3 { font-size: 1.4rem; letter-spacing: -.025em; } p, li, dd { overflow-wrap: anywhere; } a { color: var(--green); text-underline-offset: 5px; text-decoration-thickness: 1px; } a:hover { color: var(--green-dark); } button, input, select, textarea { font: inherit; max-width: 100%; } button, .button { touch-action: manipulation; } button { cursor: pointer; } button:disabled { cursor: not-allowed; } a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible, [tabindex]:focus-visible { outline: 3px solid #087c91; outline-offset: 4px; } [hidden] { display: none !important; } svg { display: block; max-width: 100%; } ul, ol { padding-left: 24px; } li + li { margin-top: 8px; } address { font-style: normal; } .page-width { width: min(1200px, calc(100% - 80px)); margin-inline: auto; } main > section + section { margin-top: 64px; } main { padding-bottom: 64px; } .small, .field-help { font-size: .85rem; color: var(--muted); line-height: 1.6; } .lead { font-size: 1.2rem; line-height: 1.65; color: var(--muted); max-width: 680px; } .eyebrow { font-size: .72rem; letter-spacing: .14em; text-transform: uppercase; font-weight: 750; color: var(--green); line-height: 1.5; } .text-green { color: var(--green); } .text-link { display: inline-flex; gap: 16px; align-items: center; font-weight: 650; text-decoration: none; width: fit-content; } .text-link:hover { text-decoration: underline; } .button, button:not(.text-button):not(.menu-toggle) { display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 12px 16px; min-height: 44px; border: 1px solid transparent; border-radius: 4px; background: var(--green); color: #fff; text-decoration: none; line-height: 1.4; font-size: .9rem; font-weight: 650; transition: background-color .18s ease, border-color .18s ease; } .button:hover, button:not(.text-button):not(.menu-toggle):hover { background: var(--green-dark); color: #fff; } .button.secondary, button.secondary { border-color: var(--line); background: var(--surface); color: var(--graphite); } .button.secondary:hover, button.secondary:hover { background: #e7eee7; color: var(--graphite); border-color: #9aafa1; } button:disabled, button.secondary:disabled, button:disabled:hover { background: #e6ebe5; color: #54625c; border-color: #d3ddd5; } .text-button { display: inline-block; width: fit-content; padding: 0; border: 0; background: transparent; color: var(--green); font-weight: 600; text-align: left; text-decoration: underline; text-underline-offset: 5px; } .text-button:hover { color: var(--green-dark); } .skip-link { position: absolute; top: 8px; left: 16px; z-index: 50; padding: 16px; background: var(--graphite); color: white; transform: translateY(-180%); } .skip-link:focus { transform: translateY(0); } .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; } .site-header { height: 64px; display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 24px; padding: 0 40px; border-bottom: 1px solid var(--line); background: var(--paper); color: var(--graphite); } .site-header > *, .brand > *, .hero > *, .section-heading > *, .guide-grid > *, .supporting > *, .footer-grid > *, .tool-heading > *, .input-group > *, .score-heading > *, .analysis-facts > *, .article-layout > *, .contact-grid > *, .cookie-banner > *, .dialog-heading > *, .checkbox-label > * { min-width: 0; } .brand { display: inline-flex; align-items: center; gap: 8px; color: var(--graphite); text-decoration: none; font-weight: 750; font-size: .96rem; line-height: 1.2; width: fit-content; letter-spacing: -.035em; } .brand svg { width: 26px; height: 26px; flex: 0 0 26px; color: var(--green); } .desktop-nav { display: flex; gap: 24px; font-size: .85rem; } .desktop-nav a { color: var(--muted); text-decoration: none; } .desktop-nav a:hover, .desktop-nav a[aria-current="page"] { color: var(--green); text-decoration: underline; } .header-cta { justify-self: end; } .button.compact { min-height: 40px; padding: 8px 16px; font-size: .8rem; } .menu-toggle { display: none; border: 1px solid var(--line); padding: 8px; border-radius: 4px; background: var(--surface); color: var(--graphite); line-height: 1.2; min-height: 40px; font-size: .8rem; } .menu-toggle:hover { background: #e7eee7; color: var(--graphite); } dialog { padding: 24px; border: 1px solid var(--line); background: var(--paper); color: var(--graphite); max-height: calc(100dvh - 48px); max-width: calc(100% - 32px); } dialog::backdrop { background: rgb(20 35 30 / .65); } .dialog-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; } .dialog-heading h2 { font-size: 1.8rem; } .dialog-heading button { flex-shrink: 0; } .mobile-navigation { margin: 0; top: 52px; left: 0; width: 100%; max-width: 100%; height: calc(100dvh - 52px); max-height: calc(100dvh - 52px); border: 0; } .mobile-navigation[open] { display: flex; flex-direction: column; gap: 40px; } .mobile-navigation nav { display: grid; gap: 24px; } .mobile-navigation nav a { font-size: 1.15rem; text-decoration: none; } .mobile-navigation nav a[aria-current="page"] { text-decoration: underline; } .hero { padding-block: 64px; display: grid; grid-template-columns: 1fr 1fr; gap: 64px; align-items: start; } .hero-copy { display: grid; gap: 24px; padding-top: 24px; } .hero-copy .eyebrow { display: flex; align-items: center; gap: 8px; } .status-dot { display: inline-block; flex: 0 0 8px; width: 8px; height: 8px; border-radius: 50%; background: var(--green); color: #fff; } .hero-notes { display: flex; flex-wrap: wrap; gap: 8px 16px; font-size: .78rem; color: var(--muted); } .hero-notes span { display: flex; align-items: center; gap: 8px; } .hero-notes span::before { content: "✓"; color: var(--green); } .tool-shell { padding: 24px; background: radial-gradient(ellipse at top right, #244e4a, transparent 70%), #1c2c28; color: #fff; border-top: 3px solid var(--green); } .tool-panel { display: grid; gap: 24px; padding: 24px; background: rgb(249 252 247 / .96); color: var(--graphite); backdrop-filter: blur(16px); } .tool-heading { display: flex; justify-content: space-between; gap: 24px; align-items: flex-start; } .tool-heading > div { display: grid; gap: 8px; } .tool-heading h2 { font-size: 1.45rem; } .shield-illustration { width: 40px; height: 40px; flex: 0 0 40px; color: var(--green); } .password-control { display: grid; gap: 8px; } label { font-size: .9rem; font-weight: 650; } input:not([type="checkbox"]), select, textarea { width: 100%; min-width: 0; border: 1px solid #a6b5ab; border-radius: 4px; padding: 12px 16px; min-height: 48px; background: #fff; color: var(--graphite); } textarea { resize: vertical; } .input-group { display: flex; align-items: stretch; gap: 8px; } .input-group input { flex: 1 1 0; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; letter-spacing: .08em; } .input-group button { flex: 0 0 auto; } .tool-actions { display: flex; flex-wrap: wrap; gap: 8px; } .tool-actions .button { flex: 1 1 auto; font-size: .79rem; } .local-note { padding-top: 24px; border-top: 1px solid var(--line); font-size: .78rem; color: var(--muted); } .tool-message:empty, [data-contact-status]:empty { display: none; } .tool-message { font-size: .85rem; color: var(--green); } .results { display: grid; gap: 24px; border-top: 1px solid var(--line); padding-top: 24px; } .score-heading { display: flex; gap: 24px; align-items: center; justify-content: space-between; } .score-value { display: flex; align-items: baseline; gap: 12px; } .score-value output { font-size: 3rem; line-height: 1; font-weight: 700; letter-spacing: -.06em; } .score-value span { font-size: .85rem; color: var(--muted); white-space: nowrap; } .strength-label { font-size: .85rem; font-weight: 700; text-align: right; } .results[data-grade="weak"] .strength-label, .results[data-grade="limited"] .strength-label { color: var(--danger); } .results[data-grade="moderate"] .strength-label { color: #825600; } .results[data-grade="strong"] .strength-label { color: var(--green); } progress { appearance: none; display: block; width: 100%; height: 8px; border: 0; border-radius: 4px; background: #dbe4dc; color: var(--green); accent-color: var(--green); } progress::-webkit-progress-bar { background: #dbe4dc; color: var(--graphite); border-radius: 4px; } progress::-webkit-progress-value { background: var(--green); color: #fff; border-radius: 4px; transition: width .3s ease; } progress::-moz-progress-bar { background: var(--green); color: #fff; border-radius: 4px; transition: width .3s ease; } .results[data-grade="weak"] progress, .results[data-grade="limited"] progress { accent-color: var(--danger); } .results[data-grade="weak"] progress::-webkit-progress-value, .results[data-grade="limited"] progress::-webkit-progress-value { background: var(--danger); color: #fff; } .results[data-grade="weak"] progress::-moz-progress-bar, .results[data-grade="limited"] progress::-moz-progress-bar { background: var(--danger); color: #fff; } .results[data-grade="moderate"] progress { accent-color: #825600; } .results[data-grade="moderate"] progress::-webkit-progress-value { background: #825600; color: #fff; } .results[data-grade="moderate"] progress::-moz-progress-bar { background: #825600; color: #fff; } .analysis-facts { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; } .analysis-facts > div { display: flex; flex-direction: column; gap: 12px; } .analysis-facts dt { color: var(--muted); font-size: .78rem; } .analysis-facts dd { margin: 0; font-weight: 650; font-size: .9rem; } .results h3 { font-size: 1rem; } .requirements { list-style: none; padding: 0; display: grid; gap: 8px; } .requirements li { margin: 0; display: grid; grid-template-columns: 16px minmax(0, 1fr); gap: 8px; font-size: .82rem; line-height: 1.5; } .requirements li[data-pass="true"] { color: var(--green); } .requirements li[data-pass="false"] { color: var(--danger); } .result-note { font-size: .85rem; font-weight: 600; } .section-heading { display: flex; align-items: end; justify-content: space-between; gap: 40px; margin-bottom: 40px; } .section-heading > div { display: grid; gap: 16px; } .section-heading > p { max-width: 280px; color: var(--muted); font-size: .9rem; } .guide-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 24px; } .card { padding: 24px; border: 0; border-top: 3px solid var(--green); background: var(--surface); color: var(--graphite); display: flex; flex-direction: column; gap: 24px; } .card p:not(.eyebrow), .card li { color: var(--muted); } .card h3 { font-size: 1.5rem; } .card .text-link { margin-top: auto; font-size: .88rem; } .card h2 { font-size: 1.75rem; } .guide-grid .card { transition: border-color .2s ease, background-color .2s ease; } .guide-grid .card:hover { border-color: #188c82; background: #f0f7f2; color: var(--graphite); } .supporting { border-top: 1px solid var(--line); padding-top: 64px; display: grid; grid-template-columns: 1fr 1fr; gap: 64px; } .supporting > div { display: grid; gap: 24px; align-content: start; } .supporting p:not(.eyebrow) { color: var(--muted); } .site-footer { padding-block: 64px; border-top: 1px solid var(--line); background: #eaf0e9; color: var(--graphite); } .footer-grid { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr); gap: 64px; } .footer-grid > div, .footer-grid nav { display: flex; flex-direction: column; align-items: flex-start; gap: 16px; } .footer-grid h2 { font-size: .8rem; text-transform: uppercase; letter-spacing: .1em; margin-bottom: 8px; } .footer-grid a:not(.brand), .footer-grid button { font-size: .85rem; } .footer-grid .small { font-size: .75rem; max-width: 440px; } .article-layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 280px); gap: 64px; align-items: start; } .article-header { padding-top: 64px; display: grid; gap: 24px; grid-column: 1 / -1; } .article-header h1 { max-width: 950px; } .article-header .text-link { font-size: .85rem; } .prose { max-width: 780px; } .prose section { display: grid; gap: 24px; } .prose section + section { margin-top: 64px; } .prose h2 { font-size: 1.8rem; } .prose p, .prose li { color: #44554d; } .prose .button { justify-self: start; } .article-aside { align-self: start; } .article-aside ol, .article-aside ul { font-size: .9rem; } .legal-layout { max-width: 960px; } .legal-layout .article-header { margin-bottom: 64px; } .legal-layout .prose { max-width: none; } .table-wrapper { overflow-x: auto; border: 1px solid var(--line); } table { width: 100%; border-collapse: collapse; font-size: .85rem; } caption { padding: 16px; text-align: left; font-weight: 650; background: #e9f0e9; color: var(--graphite); } th, td { text-align: left; vertical-align: top; padding: 16px; border-bottom: 1px solid var(--line); } thead { background: #edf2ec; color: var(--graphite); } tbody tr:last-child th, tbody tr:last-child td { border-bottom: 0; } .contact-grid { margin-top: 64px; display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: 40px; align-items: start; } .contact-details { display: grid; gap: 40px; } form { display: grid; gap: 24px; } .form-field { display: grid; gap: 8px; } .form-actions { display: flex; flex-wrap: wrap; gap: 16px; } .checkbox-label { display: flex; gap: 16px; align-items: flex-start; font-weight: 400; } input[type="checkbox"] { width: 20px; height: 20px; margin: 4px 0 0; flex: 0 0 20px; accent-color: var(--green); } input.is-invalid, select.is-invalid, textarea.is-invalid { border-color: var(--danger); box-shadow: 0 0 0 1px var(--danger); } p.field-error { color: var(--danger); font-size: .85rem; } .notice { padding: 24px; border-left: 3px solid var(--green); background: #e8f3ec; color: var(--graphite); } .email-ready { display: grid; gap: 24px; } .email-ready h3 { font-size: 1.2rem; } .static-map svg { width: 100%; height: auto; } .static-map figcaption { display: grid; gap: 16px; font-size: .85rem; } .cookie-banner { position: fixed; z-index: 20; left: 24px; right: 24px; bottom: 24px; display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 24px; padding: 24px; border-top: 3px solid var(--green); background: rgb(255 255 255 / .98); color: var(--graphite); box-shadow: 0 8px 40px rgb(23 46 34 / .15); backdrop-filter: blur(16px); max-height: calc(100dvh - 80px); overflow-y: auto; } .cookie-banner p { font-size: .82rem; color: var(--muted); margin-top: 8px; } .cookie-actions { display: flex; flex-wrap: wrap; gap: 8px; } .cookie-actions .button { font-size: .78rem; } .preferences-dialog { width: 560px; } .preferences-dialog[open] { display: grid; gap: 24px; } .preferences-dialog p { font-size: .95rem; } .preference-row { display: grid; gap: 8px; padding-block: 16px; border-top: 1px solid var(--line); } .preference-row span { font-size: .85rem; color: var(--muted); } @media (max-width: 1100px) { .hero { gap: 40px; } .tool-shell { padding: 16px; } .site-header { grid-template-columns: minmax(0, 1fr) auto auto; gap: 16px; padding-inline: 24px; } .desktop-nav { gap: 16px; } .guide-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } .footer-grid { gap: 40px; } } @media (max-width: 900px) { .hero { grid-template-columns: 1fr; } .hero-copy { max-width: 720px; padding-top: 0; } .hero-copy h1 { font-size: clamp(2.8rem, 8vw, 4.5rem); } .tool-panel { padding: 24px; } .article-layout { grid-template-columns: minmax(0, 1fr); } .article-aside { max-width: none; } .article-aside h2 { max-width: 500px; } .contact-grid { grid-template-columns: minmax(0, 1fr); } .section-heading { align-items: start; } } @media (max-width: 767px) { .page-width { width: calc(100% - 32px); } .site-header { height: 52px; padding-inline: 16px; grid-template-columns: minmax(0, 1fr) auto; gap: 16px; } .site-header .brand { font-size: .9rem; } .brand svg { width: 24px; height: 24px; flex-basis: 24px; } .desktop-nav, .header-cta { display: none; } .menu-toggle { display: inline-flex; gap: 8px; align-items: center; } .hero { padding-block: 64px 0; gap: 40px; } .hero-copy { gap: 24px; } .guide-grid { grid-template-columns: minmax(0, 1fr); } .section-heading { flex-direction: column; gap: 24px; margin-bottom: 24px; } .section-heading > p { max-width: none; } .supporting { grid-template-columns: minmax(0, 1fr); gap: 24px; } .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 40px 24px; } .footer-grid > div { grid-column: 1 / -1; } .article-header h1 { font-size: clamp(2.5rem, 8vw, 4rem); } .article-layout { gap: 64px; } .cookie-banner { left: 16px; right: 16px; bottom: 16px; padding: 16px; gap: 16px; } .cookie-actions { width: 100%; } .cookie-actions .button { flex: 1 1 auto; } .contact-grid { gap: 40px; } .contact-details { gap: 40px; } .lead { font-size: 1.08rem; } } @media (max-width: 399px) { .site-header { gap: 8px; } .site-header .brand { font-size: .78rem; } .brand svg { width: 20px; height: 20px; flex-basis: 20px; } .menu-toggle { font-size: .75rem; gap: 4px; } .hero-copy h1 { font-size: 2.65rem; } .tool-shell { padding: 0; } .tool-panel, .card { padding: 24px 16px; } .tool-heading { gap: 16px; } .tool-heading h2 { font-size: 1.25rem; } .shield-illustration { width: 32px; height: 32px; flex-basis: 32px; } .score-heading { gap: 16px; } .score-value output { font-size: 2.5rem; } .analysis-facts { gap: 16px; } .cookie-actions { display: grid; grid-template-columns: minmax(0, 1fr); } .cookie-banner { max-height: min(430px, calc(100dvh - 80px)); } .dialog-heading { flex-direction: column; gap: 24px; } .preferences-dialog, .mobile-navigation { padding: 24px 16px; } .footer-grid { grid-template-columns: minmax(0, 1fr); } th, td { padding: 16px; } } @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } *, *::before, *::after { animation: none !important; transition: none !important; } } @media print { .site-header, .site-footer, .cookie-banner, dialog, .tool-actions, .skip-link { display: none !important; } body { background: white; color: black; } .page-width { width: 100%; } .hero, .article-layout, .contact-grid { display: block; } .tool-shell { display: none; } .article-aside { margin-top: 40px; } a { color: black; } } ===ENDFILE=== ===FILE: script.js=== "use strict"; (() => { /* Shared navigation. Native dialogs provide focus containment and Escape. */ const navigation = document.querySelector("[data-navigation]"); const menuOpen = document.querySelector("[data-menu-open]"); const menuClose = document.querySelector("[data-menu-close]"); if (navigation && menuOpen && menuClose) { menuOpen.addEventListener("click", () => navigation.showModal()); menuClose.addEventListener("click", () => navigation.close()); navigation.addEventListener("click", (event) => { if (event.target.closest("a")) navigation.close(); }); const desktop = window.matchMedia("(min-width: 768px)"); desktop.addEventListener("change", (event) => { if (event.matches && navigation.open) navigation.close(); }); } /* No optional cookies, analytics, advertising, or remote requests are used. This key stores only one of three literal preference values. */ const preferenceKey = "securecheck-cookie-choice-v1"; const validChoices = new Set(["accept", "reject", "essential"]); const banner = document.querySelector("[data-cookie-banner]"); const cookieDialog = document.querySelector("[data-cookie-dialog]"); const cookieClose = document.querySelector("[data-cookie-close]"); const cookieSave = document.querySelector("[data-cookie-save]"); function readPreference() { try { const value = window.localStorage.getItem(preferenceKey); return validChoices.has(value) ? value : null; } catch { return null; } } function savePreference(choice) { if (!validChoices.has(choice)) return; try { window.localStorage.setItem(preferenceKey, choice); } catch { /* Storage may be blocked. Dismiss only for this page lifetime. */ } if (cookieDialog && cookieDialog.open) cookieDialog.close(); if (banner) banner.hidden = true; } if (banner) banner.hidden = Boolean(readPreference()); document.querySelectorAll("[data-cookie-choice]").forEach((button) => { button.addEventListener("click", () => { const choice = button.dataset.cookieChoice; if (choice === "preferences") { if (cookieDialog && !cookieDialog.open) cookieDialog.showModal(); } else { savePreference(choice); } }); }); if (cookieClose && cookieDialog) { cookieClose.addEventListener("click", () => cookieDialog.close()); } if (cookieSave) cookieSave.addEventListener("click", () => savePreference("essential")); window.addEventListener("storage", (event) => { if (event.key === preferenceKey || event.key === null) { if (banner) banner.hidden = Boolean(readPreference()); } }); /* Password tool: all analysis and generation remain in page memory. Secret values are never logged, persisted, placed in URLs, or transmitted. */ const passwordInput = document.querySelector("[data-password]"); if (passwordInput) { const results = document.querySelector("[data-results]"); const scoreOutput = document.querySelector("[data-score]"); const strengthLabel = document.querySelector("[data-strength]"); const meter = document.querySelector("[data-meter]"); const lengthOutput = document.querySelector("[data-length]"); const requirementsList = document.querySelector("[data-requirements]"); const missingOutput = document.querySelector("[data-missing]"); const visibilityButton = document.querySelector("[data-visibility]"); const generateButton = document.querySelector("[data-generate]"); const copyButton = document.querySelector("[data-copy]"); const clearButton = document.querySelector("[data-clear]"); const message = document.querySelector("[data-tool-message]"); const live = document.querySelector("[data-live]"); let generated = false; let announcementTimer = null; let revision = 0; const commonTerms = [ "password", "passcode", "welcome", "letmein", "admin", "administrator", "login", "iloveyou", "monkey", "dragon", "football", "baseball", "sunshine", "princess", "trustnoone", "secret", "changeme" ]; const sequences = [ "abcdefghijklmnopqrstuvwxyz", "01234567890", "qwertyuiop", "asdfghjkl", "zxcvbnm" ]; const substitutionMap = { "0": "o", "1": "i", "3": "e", "4": "a", "5": "s", "7": "t", "@": "a", "$": "s", "!": "i" }; function detectRepetition(characters) { const reasons = []; let run = 1; for (let index = 1; index < characters.length; index += 1) { run = characters[index] === characters[index - 1] ? run + 1 : 1; if (run >= 3) { reasons.push("three or more identical characters in a row"); break; } } for (let unitLength = 1; unitLength <= Math.floor(characters.length / 2); unitLength += 1) { if (characters.length % unitLength !== 0) continue; let repeated = true; for (let index = unitLength; index < characters.length; index += 1) { if (characters[index] !== characters[index % unitLength]) { repeated = false; break; } } if (repeated) { reasons.push("a repeated unit making up the entire password"); break; } } const counts = new Map(); for (const character of characters) { counts.set(character, (counts.get(character) || 0) + 1); } for (const count of counts.values()) { if (count >= 4 && count / characters.length > 0.25) { reasons.push("one character occupying more than a quarter of the password"); break; } } return reasons; } function detectPatterns(value) { const reasons = []; const lower = value.toLowerCase(); const normalized = lower.replace(/[013457@$!]/g, (character) => substitutionMap[character]); if (commonTerms.some((term) => lower.includes(term) || normalized.includes(term))) { reasons.push("a common term or familiar substitution"); } let sequential = false; for (const sequence of sequences) { const reverse = Array.from(sequence).reverse().join(""); for (let index = 0; index <= sequence.length - 4; index += 1) { if (lower.includes(sequence.slice(index, index + 4)) || lower.includes(reverse.slice(index, index + 4))) { sequential = true; break; } } if (sequential) break; } if (sequential) reasons.push("a four-character alphabet, number, or keyboard sequence"); if (/(?:19|20)\d{2}/.test(value)) reasons.push("a four-digit year-like pattern"); return reasons; } function analyze(value) { const characters = Array.from(value); const length = characters.length; const uppercase = /\p{Lu}/u.test(value); const lowercase = /\p{Ll}/u.test(value); const number = /\p{Nd}/u.test(value); const special = /[^\p{L}\p{N}\s]/u.test(value); const repeats = detectRepetition(characters); const patterns = detectPatterns(value); const categories = [uppercase, lowercase, number, special].filter(Boolean).length; let score = Math.min(length, 20) * 3 + categories * 10; if (patterns.length) score -= 30; if (repeats.length) score -= 20; if (length < 12) score = Math.min(score, 39); else if (length < 16) score = Math.min(score, 69); if (patterns.length || repeats.length) score = Math.min(score, 49); if (categories < 4) score = Math.min(score, 79); score = Math.max(0, Math.min(100, score)); const requirements = [ { pass: length >= 16, ok: "At least 16 characters", missing: `Add ${Math.max(0, 16 - length)} more character${16 - length === 1 ? "" : "s"} to reach 16` }, { pass: uppercase, ok: "Uppercase letter detected", missing: "Add an uppercase letter" }, { pass: lowercase, ok: "Lowercase letter detected", missing: "Add a lowercase letter" }, { pass: number, ok: "Number detected", missing: "Add a decimal digit" }, { pass: special, ok: "Special character detected", missing: "Add a special character; spaces do not count" }, { pass: repeats.length === 0, ok: "No repetition detected by local rules", missing: `Replace repetition: ${repeats.join("; ")}` }, { pass: patterns.length === 0, ok: "No common pattern detected by local rules", missing: `Replace predictable structure: ${patterns.join("; ")}` } ]; let grade = "weak"; let label = "Weak"; if (score >= 80) { grade = "strong"; label = "Strong by this checklist"; } else if (score >= 60) { grade = "moderate"; label = "Moderate"; } else if (score >= 40) { grade = "limited"; label = "Limited"; } return { score, length, grade, label, requirements }; } function hideResults() { window.clearTimeout(announcementTimer); results.hidden = true; results.removeAttribute("data-grade"); scoreOutput.textContent = ""; strengthLabel.textContent = ""; lengthOutput.textContent = ""; requirementsList.replaceChildren(); missingOutput.textContent = ""; meter.value = 0; live.textContent = ""; } function renderAnalysis() { const value = passwordInput.value; if (!value.length) { hideResults(); return; } const analysis = analyze(value); results.hidden = false; results.dataset.grade = analysis.grade; scoreOutput.textContent = String(analysis.score); strengthLabel.textContent = analysis.label; lengthOutput.textContent = String(analysis.length); meter.value = analysis.score; const fragment = document.createDocumentFragment(); for (const requirement of analysis.requirements) { const item = document.createElement("li"); item.dataset.pass = String(requirement.pass); const icon = document.createElement("span"); icon.setAttribute("aria-hidden", "true"); icon.textContent = requirement.pass ? "✓" : "!"; const text = document.createElement("span"); text.textContent = `${requirement.pass ? "Met: " : "Missing: "}${requirement.pass ? requirement.ok : requirement.missing}`; item.append(icon, text); fragment.append(item); } requirementsList.replaceChildren(fragment); const missing = analysis.requirements.filter((requirement) => !requirement.pass).length; missingOutput.textContent = missing ? `${missing} requirement${missing === 1 ? "" : "s"} still missing. See the marked items above.` : "All local checklist requirements are met. Use this password for one account only."; window.clearTimeout(announcementTimer); announcementTimer = window.setTimeout(() => { live.textContent = `Strength ${analysis.score} out of 100. ${analysis.label}. ${missing} requirements missing.`; }, 500); } function resetTool(focusInput = false) { revision += 1; passwordInput.value = ""; passwordInput.type = "password"; visibilityButton.textContent = "Show"; visibilityButton.setAttribute("aria-pressed", "false"); generated = false; copyButton.disabled = true; message.textContent = ""; hideResults(); if (focusInput) passwordInput.focus(); } passwordInput.addEventListener("input", () => { revision += 1; generated = false; copyButton.disabled = true; message.textContent = ""; renderAnalysis(); }); visibilityButton.addEventListener("click", () => { const visible = passwordInput.type === "password"; passwordInput.type = visible ? "text" : "password"; visibilityButton.textContent = visible ? "Hide" : "Show"; visibilityButton.setAttribute("aria-pressed", String(visible)); }); function secureIndex(alphabetLength) { const range = 0x100000000; const limit = Math.floor(range / alphabetLength) * alphabetLength; const buffer = new Uint32Array(1); let random; do { window.crypto.getRandomValues(buffer); random = buffer[0]; } while (random >= limit); buffer.fill(0); return random % alphabetLength; } function generatePassword() { if (!window.crypto || typeof window.crypto.getRandomValues !== "function") { throw new Error("Secure randomness unavailable"); } const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+[]{}:,.?"; for (let attempt = 0; attempt < 256; attempt += 1) { let candidate = ""; for (let index = 0; index < 24; index += 1) { candidate += alphabet[secureIndex(alphabet.length)]; } if (analyze(candidate).requirements.every((requirement) => requirement.pass)) { return candidate; } } throw new Error("Generation attempt limit reached"); } generateButton.addEventListener("click", () => { try { const value = generatePassword(); revision += 1; passwordInput.value = value; generated = true; copyButton.disabled = false; renderAnalysis(); message.textContent = "A new 24-character password was generated locally. Save it securely before leaving."; } catch { message.textContent = "Secure generation is unavailable in this environment. Your current input has not been changed. Use a trusted password manager or an updated browser."; } }); copyButton.addEventListener("click", async () => { if (!generated || !passwordInput.value) return; const requestedRevision = revision; copyButton.disabled = true; try { if (!window.isSecureContext || !navigator.clipboard || typeof navigator.clipboard.writeText !== "function") { throw new Error("Clipboard unavailable"); } await navigator.clipboard.writeText(passwordInput.value); if (revision === requestedRevision) { message.textContent = "Generated password copied. Clipboard history may retain it; clear or replace clipboard contents when finished."; } } catch { if (revision === requestedRevision) { message.textContent = "Copy was blocked. Use HTTPS and allow clipboard access, or select the field and copy manually on a trusted device."; } } finally { copyButton.disabled = !generated; } }); clearButton.addEventListener("click", () => resetTool(true)); /* Prevent restoring an old secret through back-forward cache or reload. */ resetTool(); window.addEventListener("pageshow", () => resetTool()); window.addEventListener("pagehide", () => resetTool()); } /* Contact: validate locally and prepare a mailto link. No simulated send. */ const contactForm = document.querySelector("[data-contact-form]"); if (contactForm) { const fields = Array.from(contactForm.querySelectorAll("[data-field]")); const ready = document.querySelector("[data-email-ready]"); const draft = document.querySelector("[data-email-draft]"); const status = document.querySelector("[data-contact-status]"); function errorElement(field) { return contactForm.querySelector(`[data-error-for="${field.dataset.field}"]`); } function validateField(field) { let error = ""; const value = field.type === "checkbox" ? "" : field.value.trim(); switch (field.dataset.field) { case "name": if (value.length < 2 || value.length > 80) error = "Enter a name between 2 and 80 characters."; break; case "email": if (!value || !field.validity.valid) error = "Enter a valid email address of no more than 254 characters."; break; case "topic": if (!value) error = "Select a topic for your message."; break; case "message": if (value.length < 20 || value.length > 1500) error = "Enter a message between 20 and 1,500 characters."; break; case "consent": if (!field.checked) error = "Confirm that you understand how the email step works."; break; } const output = errorElement(field); field.classList.toggle("is-invalid", Boolean(error)); field.setAttribute("aria-invalid", String(Boolean(error))); output.textContent = error; output.hidden = !error; return !error; } function clearDraft() { ready.hidden = true; draft.href = "mailto:[email protected]"; status.textContent = ""; } fields.forEach((field) => { field.addEventListener("input", () => { clearDraft(); if (field.getAttribute("aria-invalid") === "true") validateField(field); }); field.addEventListener("change", () => { clearDraft(); if (field.getAttribute("aria-invalid") === "true") validateField(field); }); }); contactForm.addEventListener("submit", (event) => { event.preventDefault(); clearDraft(); let firstInvalid = null; fields.forEach((field) => { if (!validateField(field) && !firstInvalid) firstInvalid = field; }); if (firstInvalid) { status.textContent = "Please correct the marked fields. Nothing has been sent."; firstInvalid.focus(); return; } const values = Object.fromEntries(fields.map((field) => [field.dataset.field, field.value.trim()])); const subject = `SecureCheck: ${values.topic}`; const body = `Name: ${values.name}\nReply email: ${values.email}\nTopic: ${values.topic}\n\n${values.message}`; draft.href = `mailto:[email protected]?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; ready.hidden = false; status.textContent = "Validation complete. Open your email application below to review and send. This website has not sent your message."; draft.focus(); }); contactForm.addEventListener("reset", () => { clearDraft(); fields.forEach((field) => { field.classList.remove("is-invalid"); field.removeAttribute("aria-invalid"); const output = errorElement(field); output.textContent = ""; output.hidden = true; }); }); window.addEventListener("pagehide", () => contactForm.reset()); } })(); ===ENDFILE===