All projects
2026 PERSONAL Design + Engineering

Password Generator

A privacy-first password generator that creates strong, random passwords entirely in your browser — no servers, no tracking, no requests. Pick a length and character set, copy the result, and see a real-time strength estimate before you commit to it.

  • Astro
  • TypeScript
  • Vitest
  • Playwright
View live
Password generator — case study hero 100 CASE STUDY PASSWORD GENERATOR RANDOM NUMBER GENERATOR Web Crypto Rejection-sampled · no modulo bias STRENGTH ESTIMATOR zxcvbn Lazy-loaded · off the critical path LIGHTHOUSE · MOBILE + DESKTOP PERF A11Y BEST SEO

Overview

A local-only password generator that never sends a password over the wire. Generation, strength scoring, and clipboard copy all run in the user’s browser; the page itself is a static Astro build that hydrates a single vanilla-TS island.

Goals

  • Unbiased random output (no modulo bias on small alphabets).
  • Guarantee at least one character from every enabled class.
  • Honest strength signal without paying the cost of zxcvbn on the critical path.
  • Pass WCAG 2.0 AA including color contrast in real Chromium, not just headless DOM.
  • Lighthouse 100/100/100/100 on both mobile and desktop.

Approach

Rejection-sampled RNG

randomIntBelow(n, crypto) draws a 32-bit value from crypto.getRandomValues and rejects draws above the largest multiple of n that fits in 2^32. No modulo bias on small alphabets like the 19-symbol pool or the 8-digit “avoid ambiguous” set. The rejection branch is unit-tested with a stub Crypto that queues specific u32 values.

Class-presence guarantee

The generator seeds the buffer with one character from each enabled class pool, fills the remainder from the merged pool, then Fisher–Yates shuffles. A 100-trial test loop locks in the contract so a future refactor to a single merged-pool loop fails loudly.

Lazy strength

zxcvbn is ~700 KB minified. It loads via a dynamic import('zxcvbn') on the first strength update, with a .catch that surfaces “strength meter unavailable” through the page’s live region rather than hanging the UI on chunk-load failures.

Accessibility

The DOM is built imperatively in mount-app.ts with semantic landmarks, aria-labelledby on every section, a real <button> for copy, and a live region for strength + error feedback. Two axe gates: happy-dom for structure, real Chromium for color contrast.

Outcomes

  • 19 unit tests covering the RNG rejection branch, class-presence guarantee across 100 trials, subset coverage, MAX_LEN bound, and the avoidAmbiguous invariant (never emits 0, O, 1, I, l).
  • 2 Playwright axe tests gate WCAG A/AA including color contrast.
  • Lighthouse desktop also clean at 100/100/100/100.

What’s next

  • CSP headers via Hostinger .htaccess (Phase 7 deploy).
  • A 301 from the legacy /pw-gen/ path to /password-generator at cutover.