Created
May 29, 2026 01:42
-
-
Save EncodeTheCode/6581fbb7ba4aa539925004c0376ad4b1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>Leetspeak Name Tracker</title> | |
| <style> | |
| :root { | |
| --bg: #0b1020; | |
| --panel: #121936; | |
| --panel-2: #171f42; | |
| --text: #e8ecff; | |
| --muted: #aab3d6; | |
| --accent: #7aa2ff; | |
| --good: #59d39b; | |
| --warn: #ffcb6b; | |
| --bad: #ff7a90; | |
| --line: rgba(255,255,255,0.10); | |
| --chip: rgba(122,162,255,0.14); | |
| } | |
| * { box-sizing: border-box; } | |
| body { | |
| margin: 0; | |
| font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; | |
| background: radial-gradient(circle at top, #18224a 0%, var(--bg) 45%, #060914 100%); | |
| color: var(--text); | |
| min-height: 100vh; | |
| } | |
| .wrap { | |
| max-width: 1120px; | |
| margin: 0 auto; | |
| padding: 24px; | |
| } | |
| .hero { | |
| background: linear-gradient(180deg, rgba(122,162,255,0.16), rgba(122,162,255,0.05)); | |
| border: 1px solid var(--line); | |
| border-radius: 20px; | |
| padding: 24px; | |
| box-shadow: 0 18px 50px rgba(0,0,0,0.28); | |
| } | |
| h1 { | |
| margin: 0 0 8px; | |
| font-size: clamp(28px, 4vw, 44px); | |
| letter-spacing: -0.03em; | |
| } | |
| p { color: var(--muted); line-height: 1.6; } | |
| .grid { | |
| display: grid; | |
| grid-template-columns: 1.08fr 0.92fr; | |
| gap: 18px; | |
| margin-top: 18px; | |
| } | |
| .card { | |
| background: rgba(18,25,54,0.86); | |
| border: 1px solid var(--line); | |
| border-radius: 18px; | |
| padding: 18px; | |
| backdrop-filter: blur(10px); | |
| } | |
| .row { display: grid; gap: 10px; } | |
| .row.two { grid-template-columns: 1fr 180px; } | |
| label { | |
| font-size: 13px; | |
| color: var(--muted); | |
| margin-bottom: 4px; | |
| display: block; | |
| } | |
| input, textarea, button { | |
| width: 100%; | |
| border-radius: 14px; | |
| border: 1px solid var(--line); | |
| background: var(--panel-2); | |
| color: var(--text); | |
| padding: 12px 14px; | |
| font: inherit; | |
| outline: none; | |
| } | |
| textarea { min-height: 180px; resize: vertical; } | |
| input::placeholder, textarea::placeholder { color: #7f89b7; } | |
| input:focus, textarea:focus { border-color: rgba(122,162,255,0.5); box-shadow: 0 0 0 3px rgba(122,162,255,0.14); } | |
| .actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 12px; } | |
| button { | |
| width: auto; | |
| cursor: pointer; | |
| background: linear-gradient(180deg, rgba(122,162,255,0.24), rgba(122,162,255,0.12)); | |
| transition: transform 0.14s ease, border-color 0.14s ease; | |
| } | |
| button:hover { transform: translateY(-1px); border-color: rgba(122,162,255,0.5); } | |
| button.secondary { background: rgba(255,255,255,0.04); } | |
| .stats { | |
| display: grid; | |
| grid-template-columns: repeat(4, 1fr); | |
| gap: 10px; | |
| margin-top: 14px; | |
| } | |
| .stat { | |
| background: rgba(255,255,255,0.03); | |
| border: 1px solid var(--line); | |
| border-radius: 14px; | |
| padding: 12px; | |
| } | |
| .stat .k { font-size: 12px; color: var(--muted); } | |
| .stat .v { font-size: 18px; font-weight: 700; margin-top: 4px; } | |
| .results { | |
| display: grid; | |
| gap: 10px; | |
| margin-top: 12px; | |
| } | |
| .result { | |
| border: 1px solid var(--line); | |
| background: rgba(255,255,255,0.03); | |
| border-radius: 14px; | |
| padding: 12px 14px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 12px; | |
| } | |
| .result strong { font-size: 15px; } | |
| .result small { display: block; color: var(--muted); margin-top: 3px; line-height: 1.4; } | |
| .pill { | |
| flex: none; | |
| padding: 7px 10px; | |
| border-radius: 999px; | |
| font-size: 12px; | |
| border: 1px solid var(--line); | |
| background: var(--chip); | |
| white-space: nowrap; | |
| } | |
| .pill.good { background: rgba(89,211,155,0.15); color: #a9f0ce; } | |
| .pill.warn { background: rgba(255,203,107,0.15); color: #ffe2a4; } | |
| .pill.bad { background: rgba(255,122,144,0.14); color: #ffb2bf; } | |
| .muted { color: var(--muted); } | |
| .chips { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; } | |
| .chip { | |
| padding: 7px 10px; | |
| border-radius: 999px; | |
| background: rgba(255,255,255,0.05); | |
| border: 1px solid var(--line); | |
| font-size: 12px; | |
| color: var(--muted); | |
| } | |
| .toggles { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 10px; | |
| margin-top: 12px; | |
| padding: 12px; | |
| border-radius: 14px; | |
| border: 1px solid var(--line); | |
| background: rgba(255,255,255,0.03); | |
| } | |
| .toggle { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| font-size: 13px; | |
| color: var(--muted); | |
| user-select: none; | |
| } | |
| .toggle input { width: 16px; height: 16px; padding: 0; } | |
| code { | |
| background: rgba(255,255,255,0.06); | |
| padding: 2px 6px; | |
| border-radius: 7px; | |
| color: #dce4ff; | |
| } | |
| @media (max-width: 920px) { | |
| .grid { grid-template-columns: 1fr; } | |
| .row.two { grid-template-columns: 1fr; } | |
| .stats { grid-template-columns: 1fr 1fr; } | |
| } | |
| @media (max-width: 560px) { | |
| .stats { grid-template-columns: 1fr; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="wrap"> | |
| <div class="hero"> | |
| <h1>Leetspeak Name Tracker</h1> | |
| <p> | |
| This version keeps the fast canonical matcher, then adds smarter fallback layers: token matching, prefix matching, | |
| initials matching, and bounded typo tolerance. That gives better search coverage without generating every possible 1337 variant. | |
| </p> | |
| <div class="chips"> | |
| <span class="chip">Canonical normalization</span> | |
| <span class="chip">Token index</span> | |
| <span class="chip">Prefix search</span> | |
| <span class="chip">Typos / fuzzy match</span> | |
| <span class="chip">Single-file HTML + JS</span> | |
| </div> | |
| </div> | |
| <div class="grid"> | |
| <div class="card"> | |
| <div class="row two"> | |
| <div> | |
| <label for="search">Search name</label> | |
| <input id="search" type="text" placeholder="Type a name, e.g. Sarah Witmore" /> | |
| </div> | |
| <div> | |
| <label for="mode">Search mode</label> | |
| <input id="mode" type="text" value="smart ranked" readonly /> | |
| </div> | |
| </div> | |
| <div class="toggles"> | |
| <label class="toggle"><input id="optPrefix" type="checkbox" checked /> Prefix / containment</label> | |
| <label class="toggle"><input id="optTokens" type="checkbox" checked /> Token matching</label> | |
| <label class="toggle"><input id="optInitials" type="checkbox" checked /> Initials matching</label> | |
| <label class="toggle"><input id="optFuzzy" type="checkbox" checked /> Fuzzy typo tolerance</label> | |
| </div> | |
| <div class="actions"> | |
| <button id="btnSearch">Search</button> | |
| <button id="btnReset" class="secondary">Reset</button> | |
| </div> | |
| <div class="stats"> | |
| <div class="stat"> | |
| <div class="k">Names loaded</div> | |
| <div class="v" id="countLoaded">0</div> | |
| </div> | |
| <div class="stat"> | |
| <div class="k">Matches found</div> | |
| <div class="v" id="countFound">0</div> | |
| </div> | |
| <div class="stat"> | |
| <div class="k">Search key</div> | |
| <div class="v" id="searchKey">—</div> | |
| </div> | |
| <div class="stat"> | |
| <div class="k">Best score</div> | |
| <div class="v" id="bestScore">—</div> | |
| </div> | |
| </div> | |
| <div class="results" id="results"></div> | |
| </div> | |
| <div class="card"> | |
| <label for="dataset">Tracked names</label> | |
| <textarea id="dataset" spellcheck="false">Sarah Witmore | |
| Sar4h W1tmor3 | |
| S4r4h W!tm0r3 | |
| S@r4h W1tmore | |
| Jordan Pike | |
| J0rd4n P!k3 | |
| Alicia Moore | |
| Al1c1a M00r3</textarea> | |
| <div class="actions"> | |
| <button id="btnLoad">Load / Reindex</button> | |
| <button id="btnDemo" class="secondary">Load larger demo</button> | |
| </div> | |
| <p class="muted" style="margin-top: 12px;"> | |
| Normalization maps common substitutions such as <code>4 → a</code>, <code>3 → e</code>, <code>1 / ! / l → i</code>, | |
| <code>0 → o</code>, <code>5 → s</code>, <code>7 → t</code>, and strips punctuation. The heavy lifting happens once at | |
| indexing time, not by generating all combinations. | |
| </p> | |
| <p class="muted"> | |
| Search results are ranked so exact canonical matches appear first, then close variants and typo-like matches. | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const LEET_MAP = new Map([ | |
| ['4', 'a'], ['@', 'a'], | |
| ['8', 'b'], | |
| ['(', 'c'], ['<', 'c'], ['{', 'c'], ['[', 'c'], | |
| ['3', 'e'], | |
| ['6', 'g'], ['9', 'g'], | |
| ['1', 'i'], ['!', 'i'], ['|', 'i'], | |
| ['0', 'o'], | |
| ['5', 's'], ['$', 's'], | |
| ['7', 't'], ['+', 't'], | |
| ['2', 'z'], | |
| ['_', ' '], ['-', ' '], ['.', ' '], [',', ' '], ['/',' '], ['\\',' '], | |
| ['?', ''], ['*', ''], ['"', ''], ['\'', ''], ['`', ''] | |
| ]); | |
| const state = { | |
| records: [], | |
| indexByKey: new Map(), | |
| tokenIndex: new Map(), | |
| prefixIndex: new Map(), | |
| initialsIndex: new Map() | |
| }; | |
| function normalizeName(input) { | |
| if (!input) return ''; | |
| const decomposed = input.normalize('NFKD').replace(/[\u0300-\u036f]/g, '').toLowerCase(); | |
| let out = ''; | |
| for (const ch of decomposed) { | |
| if (LEET_MAP.has(ch)) { | |
| out += LEET_MAP.get(ch); | |
| } else if (/^[a-z ]$/.test(ch)) { | |
| out += ch; | |
| } | |
| } | |
| return out.replace(/\s+/g, ' ').trim(); | |
| } | |
| function keyOnly(input) { | |
| return normalizeName(input).replace(/\s+/g, ''); | |
| } | |
| function tokensFromKey(key) { | |
| return key.split(' ').filter(Boolean).map(t => t.replace(/\s+/g, '')); | |
| } | |
| function initialsOfTokens(tokens) { | |
| return tokens.map(t => t[0]).join(''); | |
| } | |
| function addToMapList(map, key, value) { | |
| if (!map.has(key)) map.set(key, []); | |
| map.get(key).push(value); | |
| } | |
| function buildIndexes(lines) { | |
| state.records = []; | |
| state.indexByKey.clear(); | |
| state.tokenIndex.clear(); | |
| state.prefixIndex.clear(); | |
| state.initialsIndex.clear(); | |
| for (const original of lines) { | |
| const keyWithSpaces = normalizeName(original); | |
| const key = keyWithSpaces.replace(/\s+/g, ''); | |
| const tokens = keyWithSpaces.split(' ').filter(Boolean); | |
| const initials = initialsOfTokens(tokens); | |
| const record = { original, key, keyWithSpaces, tokens, initials }; | |
| state.records.push(record); | |
| addToMapList(state.indexByKey, key, record); | |
| for (const token of tokens) { | |
| addToMapList(state.tokenIndex, token, record); | |
| } | |
| const prefix = key.slice(0, 3); | |
| if (prefix) addToMapList(state.prefixIndex, prefix, record); | |
| if (initials) addToMapList(state.initialsIndex, initials, record); | |
| } | |
| document.getElementById('countLoaded').textContent = String(state.records.length); | |
| } | |
| function boundedLevenshtein(a, b, maxDistance) { | |
| if (Math.abs(a.length - b.length) > maxDistance) return maxDistance + 1; | |
| if (a === b) return 0; | |
| const prev = new Array(b.length + 1); | |
| const curr = new Array(b.length + 1); | |
| for (let j = 0; j <= b.length; j++) prev[j] = j; | |
| for (let i = 1; i <= a.length; i++) { | |
| curr[0] = i; | |
| let rowMin = curr[0]; | |
| const ca = a.charCodeAt(i - 1); | |
| for (let j = 1; j <= b.length; j++) { | |
| const cost = ca === b.charCodeAt(j - 1) ? 0 : 1; | |
| const del = prev[j] + 1; | |
| const ins = curr[j - 1] + 1; | |
| const sub = prev[j - 1] + cost; | |
| const val = Math.min(del, ins, sub); | |
| curr[j] = val; | |
| if (val < rowMin) rowMin = val; | |
| } | |
| if (rowMin > maxDistance) return maxDistance + 1; | |
| for (let j = 0; j <= b.length; j++) prev[j] = curr[j]; | |
| } | |
| return prev[b.length]; | |
| } | |
| function candidateSetFromMap(map, key) { | |
| const out = new Set(); | |
| for (const [k, records] of map.entries()) { | |
| if (k === key || k.startsWith(key) || key.startsWith(k) || k.includes(key) || key.includes(k)) { | |
| for (const r of records) out.add(r); | |
| } | |
| } | |
| return out; | |
| } | |
| function search(query) { | |
| const queryKey = keyOnly(query); | |
| const queryTokens = normalizeName(query).split(' ').filter(Boolean); | |
| const queryInitials = initialsOfTokens(queryTokens); | |
| const optPrefix = document.getElementById('optPrefix').checked; | |
| const optTokens = document.getElementById('optTokens').checked; | |
| const optInitials = document.getElementById('optInitials').checked; | |
| const optFuzzy = document.getElementById('optFuzzy').checked; | |
| document.getElementById('searchKey').textContent = queryKey || '—'; | |
| if (!queryKey) { | |
| document.getElementById('countFound').textContent = '0'; | |
| document.getElementById('bestScore').textContent = '—'; | |
| renderResults([]); | |
| return; | |
| } | |
| const scored = new Map(); | |
| const addScore = (rec, score, reason) => { | |
| const prev = scored.get(rec.original); | |
| if (!prev || score > prev.score) { | |
| scored.set(rec.original, { ...rec, score, reason }); | |
| } | |
| }; | |
| const exact = state.indexByKey.get(queryKey) || []; | |
| for (const rec of exact) addScore(rec, 100, 'exact canonical'); | |
| if (optTokens) { | |
| for (const token of queryTokens) { | |
| const candidates = state.tokenIndex.get(token) || []; | |
| for (const rec of candidates) addScore(rec, token === rec.key ? 98 : 86, 'token match'); | |
| } | |
| } | |
| if (optPrefix) { | |
| const prefixSeed = queryKey.slice(0, 3); | |
| if (prefixSeed) { | |
| const prefixCandidates = candidateSetFromMap(state.prefixIndex, prefixSeed); | |
| for (const rec of prefixCandidates) { | |
| let score = 0; | |
| if (rec.key === queryKey) score = 100; | |
| else if (rec.key.startsWith(queryKey) || queryKey.startsWith(rec.key)) score = 82; | |
| else if (rec.key.includes(queryKey) || queryKey.includes(rec.key)) score = 72; | |
| if (score) addScore(rec, score, 'prefix / containment'); | |
| } | |
| } | |
| } | |
| if (optInitials && queryInitials) { | |
| const initialsCandidates = state.initialsIndex.get(queryInitials) || []; | |
| for (const rec of initialsCandidates) addScore(rec, 78, 'initials match'); | |
| } | |
| if (optFuzzy) { | |
| const maxDistance = queryKey.length <= 5 ? 1 : queryKey.length <= 10 ? 2 : 3; | |
| for (const rec of state.records) { | |
| if (scored.has(rec.original)) continue; | |
| const dist = boundedLevenshtein(queryKey, rec.key, maxDistance); | |
| if (dist <= maxDistance) { | |
| const score = Math.max(40, 68 - dist * 10); | |
| addScore(rec, score, `fuzzy (distance ${dist})`); | |
| } | |
| } | |
| } | |
| const results = [...scored.values()].sort((a, b) => b.score - a.score || a.original.localeCompare(b.original)); | |
| document.getElementById('countFound').textContent = String(results.length); | |
| document.getElementById('bestScore').textContent = results.length ? String(results[0].score) : '—'; | |
| renderResults(results, queryKey); | |
| } | |
| function renderResults(items, queryKey = '') { | |
| const box = document.getElementById('results'); | |
| box.innerHTML = ''; | |
| if (!items.length) { | |
| box.innerHTML = '<div class="muted">No matches yet. Load a dataset, then search a name.</div>'; | |
| return; | |
| } | |
| for (const item of items) { | |
| const same = item.key === queryKey; | |
| const div = document.createElement('div'); | |
| div.className = 'result'; | |
| div.innerHTML = ` | |
| <div> | |
| <strong>${escapeHtml(item.original)}</strong> | |
| <small> | |
| Canonical: <code>${escapeHtml(item.key)}</code> | |
| • Tokens: <code>${escapeHtml(item.tokens.join(' | ') || '—')}</code> | |
| • ${escapeHtml(item.reason)} | |
| </small> | |
| </div> | |
| <div class="pill ${same ? 'good' : item.score >= 80 ? 'warn' : 'bad'}">${item.score}</div> | |
| `; | |
| box.appendChild(div); | |
| } | |
| } | |
| function escapeHtml(str) { | |
| return String(str) | |
| .replaceAll('&', '&') | |
| .replaceAll('<', '<') | |
| .replaceAll('>', '>') | |
| .replaceAll('"', '"') | |
| .replaceAll("'", '''); | |
| } | |
| function loadDataset(text) { | |
| const lines = text.split(/\r?\n/).map(s => s.trim()).filter(Boolean); | |
| buildIndexes(lines); | |
| renderResults([]); | |
| document.getElementById('countFound').textContent = '0'; | |
| document.getElementById('bestScore').textContent = '—'; | |
| } | |
| function debounce(fn, delay = 120) { | |
| let t = 0; | |
| return (...args) => { | |
| clearTimeout(t); | |
| t = setTimeout(() => fn(...args), delay); | |
| }; | |
| } | |
| const doSearch = debounce(() => search(document.getElementById('search').value), 120); | |
| document.getElementById('btnLoad').addEventListener('click', () => { | |
| loadDataset(document.getElementById('dataset').value); | |
| }); | |
| document.getElementById('btnDemo').addEventListener('click', () => { | |
| document.getElementById('dataset').value = [ | |
| 'Sarah Witmore', | |
| 'Sar4h W1tmor3', | |
| 'S4r4h W!tm0r3', | |
| 'S@r4h W1tmore', | |
| 'S a r a h W i t m o r e', | |
| 'Jordan Pike', | |
| 'J0rd4n P!k3', | |
| 'Jor-dan Pike', | |
| 'Alicia Moore', | |
| 'Al1c1a M00r3', | |
| 'A. Moore' | |
| ].join('\n'); | |
| loadDataset(document.getElementById('dataset').value); | |
| }); | |
| document.getElementById('btnSearch').addEventListener('click', () => { | |
| search(document.getElementById('search').value); | |
| }); | |
| document.getElementById('btnReset').addEventListener('click', () => { | |
| document.getElementById('search').value = ''; | |
| document.getElementById('searchKey').textContent = '—'; | |
| document.getElementById('countFound').textContent = '0'; | |
| document.getElementById('bestScore').textContent = '—'; | |
| renderResults([]); | |
| }); | |
| document.getElementById('search').addEventListener('input', doSearch); | |
| document.getElementById('search').addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter') search(e.target.value); | |
| }); | |
| for (const id of ['optPrefix', 'optTokens', 'optInitials', 'optFuzzy']) { | |
| document.getElementById(id).addEventListener('change', () => { | |
| if (document.getElementById('search').value.trim()) search(document.getElementById('search').value); | |
| }); | |
| } | |
| loadDataset(document.getElementById('dataset').value); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment