Skip to content

Instantly share code, notes, and snippets.

@EncodeTheCode
Created May 29, 2026 01:42
Show Gist options
  • Select an option

  • Save EncodeTheCode/6581fbb7ba4aa539925004c0376ad4b1 to your computer and use it in GitHub Desktop.

Select an option

Save EncodeTheCode/6581fbb7ba4aa539925004c0376ad4b1 to your computer and use it in GitHub Desktop.
<!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('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#39;');
}
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