<!-- Soundex phonetic matching demo — type a name and see its 4-char code -->
<p class="hint">Type any name to see its <b>Soundex code</b> — a 4-character phonetic hash. Names with the same code are phonetic matches, meaning a search for one would find all the others.</p>
<div class="input-row">
<input id="name-input" type="text" placeholder="Type a name..." autocomplete="off" spellcheck="false" />
<div class="code-badge" id="code-badge">----</div>
</div>
<div class="steps-box" id="steps-box"></div>
<div class="collisions-section">
<div class="collisions-label" id="collisions-label">Known names with this code:</div>
<div class="collisions-list" id="collisions-list"></div>
</div>
<button id="reset-btn" type="button" class="ghost">Reset</button>
/* Main layout styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.input-row { display: flex; align-items: center; gap: .6rem; margin-bottom: .7rem; }
#name-input { flex: 1; font-size: 1.1rem; padding: .4rem .7rem; border: 1.5px solid #cdd9e3;
border-radius: 8px; outline: none; }
#name-input:focus { border-color: #1d3557; }
.code-badge { font: 700 1.6rem ui-monospace, monospace; letter-spacing: .12em;
background: #1d3557; color: #fff; border-radius: 10px;
padding: .25rem .8rem; min-width: 6ch; text-align: center; }
.code-badge.match { background: #0a7d33; }
/* Step-by-step breakdown box */
.steps-box { background: #f0f4f8; border-radius: 8px; padding: .55rem .75rem;
font-size: .82rem; line-height: 1.6; margin-bottom: .7rem; min-height: 2.4rem; }
.step { margin-bottom: .1rem; }
.step .label { color: #555; }
.step .val { font-family: ui-monospace, monospace; color: #1d3557; font-weight: 600; }
/* Collision chips listing matching names */
.collisions-section { margin-bottom: .7rem; }
.collisions-label { font-size: .82rem; font-weight: 600; color: #555; margin-bottom: .35rem; }
.collisions-list { display: flex; flex-wrap: wrap; gap: .35rem; min-height: 2rem; }
.collision-chip { background: #e8eef3; border: 1px solid #cdd9e3; border-radius: 20px;
padding: .18rem .65rem; font-size: .82rem; color: #1d3557; }
.collision-chip.self { background: #1d3557; color: #fff; border-color: #1d3557; }
.collision-chip.empty { color: #888; background: transparent; border: none; font-style: italic; }
button.ghost { font: 600 13px system-ui, sans-serif; padding: .35rem .8rem;
border: 1px solid #1d3557; background: #fff; color: #1d3557;
border-radius: 8px; cursor: pointer; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Soundex algorithm — returns a 4-char code: 1 letter + 3 digits
function soundex(name) {
if (!name) return '----';
// Step 1: keep first letter, strip non-alpha
const upper = name.toUpperCase().replace(/[^A-Z]/g, '');
if (!upper) return '----';
const first = upper[0];
// Consonant-to-digit mapping table (standard Soundex)
const MAP = { B:1,F:1,P:1,V:1, C:2,G:2,J:2,K:2,Q:2,S:2,X:2,Z:2,
D:3,T:3, L:4, M:5,N:5, R:6 };
// Step 2: encode remaining consonants
const digits = [];
let prev = MAP[first] ?? 0;
for (let i = 1; i < upper.length; i++) {
const ch = upper[i];
// H, W, Y are ignored (they do not change the phonetic code)
if ('HWY'.includes(ch)) continue;
const code = MAP[ch] ?? 0;
// Vowels (code 0) reset the dedup guard but are not appended
if (code === 0) { prev = 0; continue; }
// Adjacent same-code consonants are merged into one digit
if (code !== prev) { digits.push(code); prev = code; }
if (digits.length === 3) break;
}
// Pad with zeros to always produce exactly 3 digits after the letter
while (digits.length < 3) digits.push(0);
return first + digits.join('');
}
// Built-in list of names used to show phonetic collisions
const NAMES = [
'Smith','Smyth','Schmidt','Smithe',
'Johnson','Jonson','Johnsen','Jensen',
'Robert','Rupert','Roberta',
'Catherine','Katherine','Kathryn','Katarina',
'Thompson','Thomson','Tomson',
'Williams','Williamson',
'Anderson','Andersen',
'Jackson','Jaxon','Jessen',
'Garcia','Garza','Garsia',
'Rodriguez','Rodrigues',
'Martinez','Martines',
'Brown','Braun','Brawn',
'Taylor','Tailor','Tayler',
'Wilson','Willson',
'Lee','Leigh','Lea',
'Clark','Clarke','Clerk',
'Walker','Walcker',
'White','Wight','Whyte',
'Hall','Haul','Halle',
'Allen','Alan','Allan'
];
// Render the step-by-step breakdown inside the steps box
function renderSteps(name, code) {
const box = document.getElementById('steps-box');
if (!name) { box.innerHTML = ''; return; }
const upper = name.toUpperCase().replace(/[^A-Z]/g, '') || '?';
const labels = [
{ label: 'Input:', val: name },
{ label: 'Uppercase, letters only:', val: upper },
{ label: 'Soundex code:', val: code }
];
box.innerHTML = labels.map(l =>
`<div class="step"><span class="label">${l.label}</span> <span class="val">${l.val}</span></div>`
).join('');
}
// Render the list of known names that share the computed code
function renderCollisions(code, inputName) {
const label = document.getElementById('collisions-label');
const list = document.getElementById('collisions-list');
if (!inputName || code === '----') {
label.textContent = 'Known names with this code:';
list.innerHTML = '';
return;
}
const matches = NAMES.filter(n => soundex(n) === code);
label.textContent = i18nFmt('Known names sharing code %code%:', { code: code });
if (matches.length === 0) {
list.innerHTML = `<span class="collision-chip empty">No known names share this code.</span>`;
return;
}
const norm = inputName.trim().toLowerCase();
list.innerHTML = matches.map(n => {
const isSelf = n.toLowerCase() === norm;
return `<span class="collision-chip${isSelf ? ' self' : ''}">${n}</span>`;
}).join('');
}
function update() {
const raw = document.getElementById('name-input').value;
const code = soundex(raw.trim());
const badge = document.getElementById('code-badge');
badge.textContent = code;
badge.className = 'code-badge' + (code !== '----' ? ' match' : '');
renderSteps(raw.trim(), code);
renderCollisions(code, raw.trim());
}
document.getElementById('name-input').addEventListener('input', update);
document.getElementById('reset-btn').addEventListener('click', () => {
document.getElementById('name-input').value = '';
document.getElementById('code-badge').textContent = '----';
document.getElementById('code-badge').className = 'code-badge';
document.getElementById('steps-box').innerHTML = '';
document.getElementById('collisions-label').textContent = 'Known names with this code:';
document.getElementById('collisions-list').innerHTML = '';
});
// Pre-fill with 'Smith' to show a result on load
document.getElementById('name-input').value = 'Smith';
update();
<!-- Soundex phonetic matching demo — type a name and see its 4-char code -->
<p class="hint">Type any name to see its <b>Soundex code</b> — a 4-character phonetic hash. Names with the same code are phonetic matches, meaning a search for one would find all the others.</p>
<div class="input-row">
<input id="name-input" type="text" placeholder="Type a name..." autocomplete="off" spellcheck="false" />
<div class="code-badge" id="code-badge">----</div>
</div>
<div class="steps-box" id="steps-box"></div>
<div class="collisions-section">
<div class="collisions-label" id="collisions-label">Known names with this code:</div>
<div class="collisions-list" id="collisions-list"></div>
</div>
<button id="reset-btn" type="button" class="ghost">Reset</button>
/* Main layout styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.input-row { display: flex; align-items: center; gap: .6rem; margin-bottom: .7rem; }
#name-input { flex: 1; font-size: 1.1rem; padding: .4rem .7rem; border: 1.5px solid #cdd9e3;
border-radius: 8px; outline: none; }
#name-input:focus { border-color: #1d3557; }
.code-badge { font: 700 1.6rem ui-monospace, monospace; letter-spacing: .12em;
background: #1d3557; color: #fff; border-radius: 10px;
padding: .25rem .8rem; min-width: 6ch; text-align: center; }
.code-badge.match { background: #0a7d33; }
/* Step-by-step breakdown box */
.steps-box { background: #f0f4f8; border-radius: 8px; padding: .55rem .75rem;
font-size: .82rem; line-height: 1.6; margin-bottom: .7rem; min-height: 2.4rem; }
.step { margin-bottom: .1rem; }
.step .label { color: #555; }
.step .val { font-family: ui-monospace, monospace; color: #1d3557; font-weight: 600; }
/* Collision chips listing matching names */
.collisions-section { margin-bottom: .7rem; }
.collisions-label { font-size: .82rem; font-weight: 600; color: #555; margin-bottom: .35rem; }
.collisions-list { display: flex; flex-wrap: wrap; gap: .35rem; min-height: 2rem; }
.collision-chip { background: #e8eef3; border: 1px solid #cdd9e3; border-radius: 20px;
padding: .18rem .65rem; font-size: .82rem; color: #1d3557; }
.collision-chip.self { background: #1d3557; color: #fff; border-color: #1d3557; }
.collision-chip.empty { color: #888; background: transparent; border: none; font-style: italic; }
button.ghost { font: 600 13px system-ui, sans-serif; padding: .35rem .8rem;
border: 1px solid #1d3557; background: #fff; color: #1d3557;
border-radius: 8px; cursor: pointer; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Soundex algorithm — returns a 4-char code: 1 letter + 3 digits
function soundex(name) {
if (!name) return '----';
// Step 1: keep first letter, strip non-alpha
const upper = name.toUpperCase().replace(/[^A-Z]/g, '');
if (!upper) return '----';
const first = upper[0];
// Consonant-to-digit mapping table (standard Soundex)
const MAP = { B:1,F:1,P:1,V:1, C:2,G:2,J:2,K:2,Q:2,S:2,X:2,Z:2,
D:3,T:3, L:4, M:5,N:5, R:6 };
// Step 2: encode remaining consonants
const digits = [];
let prev = MAP[first] ?? 0;
for (let i = 1; i < upper.length; i++) {
const ch = upper[i];
// H, W, Y are ignored (they do not change the phonetic code)
if ('HWY'.includes(ch)) continue;
const code = MAP[ch] ?? 0;
// Vowels (code 0) reset the dedup guard but are not appended
if (code === 0) { prev = 0; continue; }
// Adjacent same-code consonants are merged into one digit
if (code !== prev) { digits.push(code); prev = code; }
if (digits.length === 3) break;
}
// Pad with zeros to always produce exactly 3 digits after the letter
while (digits.length < 3) digits.push(0);
return first + digits.join('');
}
// Built-in list of names used to show phonetic collisions
const NAMES = [
'Smith','Smyth','Schmidt','Smithe',
'Johnson','Jonson','Johnsen','Jensen',
'Robert','Rupert','Roberta',
'Catherine','Katherine','Kathryn','Katarina',
'Thompson','Thomson','Tomson',
'Williams','Williamson',
'Anderson','Andersen',
'Jackson','Jaxon','Jessen',
'Garcia','Garza','Garsia',
'Rodriguez','Rodrigues',
'Martinez','Martines',
'Brown','Braun','Brawn',
'Taylor','Tailor','Tayler',
'Wilson','Willson',
'Lee','Leigh','Lea',
'Clark','Clarke','Clerk',
'Walker','Walcker',
'White','Wight','Whyte',
'Hall','Haul','Halle',
'Allen','Alan','Allan'
];
// Render the step-by-step breakdown inside the steps box
function renderSteps(name, code) {
const box = document.getElementById('steps-box');
if (!name) { box.innerHTML = ''; return; }
const upper = name.toUpperCase().replace(/[^A-Z]/g, '') || '?';
const labels = [
{ label: 'Input:', val: name },
{ label: 'Uppercase, letters only:', val: upper },
{ label: 'Soundex code:', val: code }
];
box.innerHTML = labels.map(l =>
`<div class="step"><span class="label">${l.label}</span> <span class="val">${l.val}</span></div>`
).join('');
}
// Render the list of known names that share the computed code
function renderCollisions(code, inputName) {
const label = document.getElementById('collisions-label');
const list = document.getElementById('collisions-list');
if (!inputName || code === '----') {
label.textContent = 'Known names with this code:';
list.innerHTML = '';
return;
}
const matches = NAMES.filter(n => soundex(n) === code);
label.textContent = i18nFmt('Known names sharing code %code%:', { code: code });
if (matches.length === 0) {
list.innerHTML = `<span class="collision-chip empty">No known names share this code.</span>`;
return;
}
const norm = inputName.trim().toLowerCase();
list.innerHTML = matches.map(n => {
const isSelf = n.toLowerCase() === norm;
return `<span class="collision-chip${isSelf ? ' self' : ''}">${n}</span>`;
}).join('');
}
function update() {
const raw = document.getElementById('name-input').value;
const code = soundex(raw.trim());
const badge = document.getElementById('code-badge');
badge.textContent = code;
badge.className = 'code-badge' + (code !== '----' ? ' match' : '');
renderSteps(raw.trim(), code);
renderCollisions(code, raw.trim());
}
document.getElementById('name-input').addEventListener('input', update);
document.getElementById('reset-btn').addEventListener('click', () => {
document.getElementById('name-input').value = '';
document.getElementById('code-badge').textContent = '----';
document.getElementById('code-badge').className = 'code-badge';
document.getElementById('steps-box').innerHTML = '';
document.getElementById('collisions-label').textContent = 'Known names with this code:';
document.getElementById('collisions-list').innerHTML = '';
});
// Pre-fill with 'Smith' to show a result on load
document.getElementById('name-input').value = 'Smith';
update();
The demo computes the code in real time and shows you a handful of well-known names that share the same code, so you can see the collision in action. Notice how Robert and Rupert land on the same code, or how Katherine, Kathryn, and Katarina all collapse together — but Catherine gets a different code because Soundex always keeps the first letter.
Comments
Loading comments...