unitbox: One Page, Twelve Unit Categories, Zero React
Travel blog needed km/mi, °C/°F, kg/lb in one static URL. Tabbed converter with shared engine and localStorage last category.
Problem: Twelve separate calculator pages killed SEO. One page, category tabs, shared conversion logic.
Full multi-converter (excerpt — length + temperature; pattern repeats)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Unit Converter</title>
<style>
body { font-family: system-ui; max-width: 560px; margin: 2rem auto; padding: 0 1rem; }
.tabs { display: flex; gap: .25rem; flex-wrap: wrap; margin-bottom: 1rem; }
.tabs button { padding: .35rem .7rem; cursor: pointer; }
.tabs button.active { background: #0ea5e9; color: #fff; border-color: #0ea5e9; }
.row { display: flex; gap: .5rem; align-items: center; margin: .5rem 0; }
input { flex: 1; padding: .5rem; }
select { padding: .5rem; }
</style>
</head>
<body>
<h1>Unit Converter</h1>
<div class="tabs" id="tabs"></div>
<div class="row">
<input type="number" id="val" value="1" step="any">
<select id="from"></select>
<span>=</span>
<input type="number" id="result" readonly>
<select id="to"></select>
</div>
<script>
const CATEGORIES = {
length: { m: 1, km: 1000, mi: 1609.344, ft: 0.3048, in: 0.0254 },
mass: { kg: 1, g: 0.001, lb: 0.453592, oz: 0.0283495 },
temp: { C: 'temp', F: 'temp', K: 'temp' },
data: { B: 1, KB: 1024, MB: 1048576, GB: 1073741824 },
};
let cat = localStorage.getItem('unitbox-cat') || 'length';
const tabs = document.getElementById('tabs');
const from = document.getElementById('from');
const to = document.getElementById('to');
const val = document.getElementById('val');
const result = document.getElementById('result');
Object.keys(CATEGORIES).forEach(key => {
const b = document.createElement('button');
b.textContent = key;
b.className = key === cat ? 'active' : '';
b.onclick = () => { cat = key; localStorage.setItem('unitbox-cat', cat); render(); document.querySelectorAll('.tabs button').forEach(x => x.classList.remove('active')); b.classList.add('active'); };
tabs.appendChild(b);
});
function convertTemp(v, fromU, toU) {
let c = fromU === 'C' ? v : fromU === 'F' ? (v - 32) * 5/9 : v - 273.15;
if (toU === 'C') return c;
if (toU === 'F') return c * 9/5 + 32;
return c + 273.15;
}
function convert() {
const v = Number(val.value);
const f = from.value, t = to.value;
if (cat === 'temp') {
result.value = convertTemp(v, f, t).toFixed(4);
return;
}
const base = CATEGORIES[cat];
const meters = v * base[f];
result.value = (meters / base[t]).toFixed(6);
}
function render() {
const units = Object.keys(CATEGORIES[cat]);
from.innerHTML = to.innerHTML = units.map(u => '<option>' + u + '</option>').join('');
to.selectedIndex = Math.min(1, units.length - 1);
convert();
}
val.oninput = from.onchange = to.onchange = convert;
render();
</script>
</body>
</html>
Temperature is special
Can't multiply by factors — needs offset formulas. Separate branch avoids wrong answers like 20°C = 20°F.
Extend categories
Add volume, speed same pattern — base unit in object, ratio conversion.
Verify
1 km → mi ≈ 0.621371 0°C → 32°F Tab switch remembers via localStorage