Correct Horse
Battery Staple
Password Generator
← Back to app

📖 Wiki

How the site works — architecture, the 13 supported UI languages, the 15 word lists, settings, achievements, and the developer API.

How It Works

The site is a pure client-side web app — no server, no data sent anywhere. Everything runs in the browser.

Key files

FilePurpose
index.htmlSingle-page UI
js/main.min.jsCore logic — CorrectHorseBatteryStaple class, exposed as window.CHBS
js/achievements.jsAchievement system — CHBSAchievements. Also exposes badge metadata (CHBSAchievements.badges) in all 13 languages, which this wiki renders live
js/lang-detect.jsRuns before anything else; picks which starter-data script to preload (only Dutch, English, French and German have one) based on the browser's language
js/starter.XX.jsInline starter word data for zero-latency first render — only exists for nl, en, fr and de; the other 11 word lists load via AJAX instead
js/i18n.XX.jsUI translation strings — one file per supported language, loaded on demand
data/*.txtFull word lists (15 files), loaded on demand via AJAX
css/styles.min.cssMain styles
css/achievements.cssAchievement toast & panel styles

Load sequence

  1. Page loads; an inline script resolves the UI language (URL lang param → saved CHBS_lang → browser language, defaulting to English), preloads its js/i18n.<lang>.min.js, and hides the page until that language is confirmed applied — avoiding a flash of the raw Dutch fallback text baked into the HTML. js/lang-detect.js separately picks a starter-data script (Dutch, English, French or German only) and inserts it before main.min.js runs.
  2. main.min.js initialises window.CHBS, which knows about all 13 UI languages via CHBS_SUPPORTED_LANGS.
  3. Saved options are read from localStorage (or URL params).
  4. The active UI language is resolved again and applied via applyLanguage(); only once its callback confirms the UI text is translated does init() correct the wordlist checkboxes and reveal the page — this ordering prevents a flash of the wrong language or wordlist before things settle.
  5. If the active word list is Dutch, English, French or German, its starter data lets a password appear instantly; the other 11 word lists wait for their first AJAX fetch before the first password appears.
  6. The full word list for the active dictionary loads in the background and silently replaces the starter data.
  7. achievements.js wraps CHBS.trackGenerate, trackCopy, and trackShare to intercept events and check badge conditions.
Randomness: all random selection uses window.crypto.getRandomValues with a Uint32Array and rejection sampling to avoid modulo bias. Math.random() is never used.

Dictionaries & Word Lists

There are 15 word lists in total: 13 language lists, plus the Heroes and HoHoHo novelty lists. Exactly one is active at a time (or none, in Random mode).

KeyFileWordsDescription
primarydutch.txt62,123Dutch words — active by default
englishenglish.txt20,000English words
frenchfrench.txt20,384French words
germangerman.txt20,000German words
spanishspanish.txt20,000Spanish words
italianitalian.txt20,000Italian words
portugueseportuguese.txt20,000Portuguese words
finnishfinnish.txt20,000Finnish words
turkishturkish.txt20,000Turkish words
greekgreek.txt20,000Greek words
russianrussian.txt20,000Russian words
esperantoesperanto.txt20,000Esperanto words
japanesejapanese.txt13,479Japanese words
heroesheroes.txt219Superhero / character names (secret — see Easter Eggs)
hohohohohoho.txt368Christmas-themed words (secret — see Easter Eggs)
randomFully random character mode — no word list used
Word counts: these reflect the current data/*.txt files (each a single comma-separated line) and will drift slightly whenever a list is edited.

Selection rules (passphrase mode)

Password Generation

Passphrase mode

  1. Random words are drawn from the active dictionary (or mixed per the Heroes/HoHoHo rules above).
  2. Words are capitalised if the "Uppercase" option is on.
  3. Words are joined with the chosen separator character(s). When multiple separators are provided, one is chosen at random per gap.
  4. If "Append number" is on, a random digit (0–9) is added after the last separator.
  5. If the result is shorter than "Minimum length", one extra word is added and the process repeats until the target is reached.

Random mode

Generates a string of the configured length from a combined charset of uppercase, lowercase, digits, and the configured special characters. Duplicate characters are removed from the charset before sampling.

Strength scoring

ModeHow score is calculated
RandomPrimarily by length (1–5). Loses 1 point if fewer than 3 character types are present and score > 2.
PassphraseBy word count (1–5). Gains +1 bonus point if total length ≥ 40 characters.

Settings & Storage

Settings are saved under the localStorage key CHBSOptions as JSON, and can also be shared via URL query params.

Option keyTypeDefaultDescription
minWordsnumber6Minimum number of words
minLengthnumber10Minimum total character length
separatorstring-Separator character(s) between words
firstUpperbooleantrueCapitalise the first letter of each word
appendNumbersbooleantrueAppend a random digit at the end
randomLengthnumber36Length in random mode
randomCharsstring!@#$%^&*()_+-=[]{}|;:,.?Special characters for random mode
dict-<name>booleanfalseMarks a word list as active (see Dictionaries above) — e.g. dict-french, dict-heroes, dict-random. At most one is ever true; none being true means Dutch/primary is active.
saveOptionsbooleanfalseWhether to persist options to localStorage

Internationalisation

13 UI languages are supported. Only one language's translation file is kept in memory at a time — switching languages loads the new one and unloads the previous one.

CodeLanguage
nlNederlands (Dutch)
enEnglish
frFrançais (French)
deDeutsch (German)
esEspañol (Spanish)
itItaliano (Italian)
ptPortuguês (Portuguese)
fiSuomi (Finnish)
trTürkçe (Turkish)
elΕλληνικά (Greek)
ruРусский (Russian)
eoEsperanto
ja日本語 (Japanese)

Language resolution order

  1. lang URL query parameter.
  2. CHBS_lang in localStorage.
  3. Browser's navigator.languages list — first match wins.
  4. Falls back to Dutch.

Auto dictionary selection

On first visit (no saved options, no URL dict param), the detected browser language pre-selects the matching word list:

Browser languageAuto-selected word list
enenglish
frfrench
degerman
esspanish
ititalian
ptportuguese
fifinnish
trturkish
elgreek
rurussian
eoesperanto
jajapanese
nl / otherprimary (Dutch, the default)

Stats & Sessions

Stats are persisted under the localStorage key CHBS_stats. A new session is counted once per tab (tracked via sessionStorage). Achievement badges are stored separately under CHBS_badges.

Stat keyDescription
totalGeneratedTotal passwords generated
totalCopiedTotal passwords copied
totalSharedTotal share-links copied
sessionsNumber of distinct visits
firstUsedISO date string of first use
lastActiveDateISO date of last activity (used for streak)
streakCurrent consecutive-day streak
genNL / genEN / genFR / genDE / genES / genIT / genPT / genFI / genTR / genEL / genRU / genEO / genJAPasswords generated per language — one counter per word list, tracked for all 13 languages
heroesGeneratedPasswords generated with the Heroes list
hohohoGeneratedPasswords generated with the HoHoHo list
randomGeneratedPasswords generated in random mode
longGeneratedPasswords with 20+ characters
veryStrongGeneratedPasswords with strength score 5
veryWeakGeneratedPasswords with strength score 1
settingsChangedSettings changed between generates
statsOpenedTimes the stats panel was opened
quickdrawCountTimes a password was copied within 2 s of generating
longWordGeneratedTimes a password with 8+ words was generated

Easter Eggs

There are two hidden word lists, each unlocked by clicking a language label a specific number of times in quick succession (about 600ms between clicks).

🎅 HoHoHo (Christmas)

Triple-click the Dutch (NL) label (not the checkbox itself) while Dutch is the active word list. On the third click, the label changes to "🎅 HoHoHo" and the hohoho dictionary is activated. The Santa's Dirty Secret achievement unlocks at this point.

Click the label again while the Easter egg is active to reset it back to the Dutch list.

🦸 Secret Lair (Heroes)

Click the English (EN) label five times while English is the active word list. On the fifth click, the label changes to "🦸 Heroes" and the heroes dictionary is activated. The Secret Lair achievement unlocks at this point.

Click the label again while the Easter egg is active to reset it back to the English list.

Achievements

Achievements unlock automatically and appear as a toast notification. All unlocked badges are visible in the Stats panel. A handful — the three flagged as hidden (Hacker Man, The Matrix, Root Access) plus the two Easter-egg badges above — don't reveal themselves until earned.

Badge names and conditions below are pulled live from CHBSAchievements.badges and shown in the language you're currently reading this page in.

Generation milestones

BadgeConditionID

Copy milestones

BadgeConditionID

Sharing

BadgeConditionID

Languages & word lists

BadgeConditionID

Security & quality

BadgeConditionID

Speed

BadgeConditionID

Behaviour

BadgeConditionID

Streaks & longevity

BadgeConditionID

Time of day

BadgeConditionID

Calendar dates

BadgeConditionID

Password length novelties

BadgeConditionID

Secret badges

These are hidden until earned and do not appear in the stats panel count beforehand.

BadgeConditionID

Console API (Dev / Cheat)

Open the browser console and type CHBSAchievements.help() to see the full cheat sheet.

CommandDescription
CHBSAchievements.help()Show the cheat sheet in the console
CHBSAchievements.list()List all badge ids — appends ✓ next to already-unlocked ones
CHBSAchievements.missing()List only the badges you haven't unlocked yet, with their IDs
CHBSAchievements.unlock('id')Unlock a specific badge by ID (IDs listed in the achievement tables above)
CHBSAchievements.unlockAll()Unlock every badge
CHBSAchievements.revoke('id')Remove a specific badge (revoking hacker-man grants The Matrix)
CHBSAchievements.maxStats()Set all stats to their maximum badge-triggering values
CHBSAchievements.setStats({key: val})Override specific stats, e.g. {totalGenerated: 1000}
CHBSAchievements.fakeTime(h, m)Test time-of-day badges, e.g. fakeTime(13, 37)
CHBSAchievements.fakeDate(month0, day)Test calendar badges, e.g. fakeDate(11, 25) for Christmas
CHBSAchievements.unlockHohoho()Unlock Santa's Dirty Secret directly
CHBSAchievements.unlockHeroesSecret()Unlock Secret Lair directly
CHBSAchievements.check()Re-run all badge checks against current state
CHBSAchievements.badgesRead-only array of every badge's id/icon/name/desc/secret flag, in all 13 languages — this is what powers the tables above
Note: fakeDate uses 0-indexed months (January = 0, December = 11).
Any use of unlock, unlockAll, maxStats, setStats, fakeTime, or fakeDate automatically grants the Hacker Man secret badge.