1<!doctype html>
2<html lang="en">
3<head>
4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <title>Unit Converter</title>
7 <link rel="stylesheet" href="style.css">
8</head>
9<body>
10 <main class="app">
11 <header>
12 <h1>Unit Converter</h1>
13 <p>Fast, keyboard-friendly conversions.</p>
14 </header>
15
16 <section class="panel" aria-label="converter">
17 <label>Category
18 <select id="category" accesskey="c"></select>
19 </label>
20
21 <div class="grid">
22 <label>From value
23 <input id="fromValue" type="number" inputmode="decimal" step="any" autocomplete="off" autofocus>
24 </label>
25 <label>From unit
26 <select id="fromUnit"></select>
27 </label>
28 <button id="swap" title="Swap units (Ctrl+/)" aria-label="Swap units">⇄ Swap</button>
29 <label>To value
30 <input id="toValue" type="number" inputmode="decimal" step="any" autocomplete="off">
31 </label>
32 <label>To unit
33 <select id="toUnit"></select>
34 </label>
35 </div>
36
37 <label>Precision
38 <select id="precision">
39 <option>0</option><option>1</option><option>2</option><option>3</option>
40 <option selected>4</option><option>5</option><option>6</option><option>8</option><option>10</option>
41 </select>
42 </label>
43 </section>
44
45 <section class="panel">
46 <div class="row"><h2>Recent conversions</h2><button id="clearRecent">Clear</button></div>
47 <ol id="recent" aria-live="polite"></ol>
48 </section>
49 </main>
50 <script src="convert.js"></script>
51 <script>
52 const $ = id => document.getElementById(id);
53 const els = ['category','fromValue','toValue','fromUnit','toUnit','precision','swap','recent','clearRecent'].reduce((o,id)=>(o[id]=$(id),o),{});
54 let active = 'from';
55 const recentKey = 'unit-converter-recent';
56 const names = { length:'Length', mass:'Mass', temperature:'Temperature', data:'Data size', time:'Time' };
57
58 function fillCategories(){
59 els.category.innerHTML = Object.keys(UnitConverter.CATEGORIES).map(k=>`<option value="${k}">${names[k]}</option>`).join('');
60 }
61 function fillUnits(){
62 const units = UnitConverter.getUnits(els.category.value);
63 els.fromUnit.innerHTML = units.map(u=>`<option>${u}</option>`).join('');
64 els.toUnit.innerHTML = units.map(u=>`<option>${u}</option>`).join('');
65 els.toUnit.selectedIndex = Math.min(1, units.length-1);
66 update('from');
67 }
68 function update(source){
69 active = source || active;
70 const input = active === 'from' ? els.fromValue : els.toValue;
71 const output = active === 'from' ? els.toValue : els.fromValue;
72 const from = active === 'from' ? els.fromUnit.value : els.toUnit.value;
73 const to = active === 'from' ? els.toUnit.value : els.fromUnit.value;
74 if (input.value === '') { output.value = ''; return; }
75 output.value = UnitConverter.formatNumber(UnitConverter.convert(input.value, from, to, els.category.value), els.precision.value);
76 saveRecent(input.value, from, output.value, to);
77 }
78 function saveRecent(a, au, b, bu){
79 const item = `${a} ${au} = ${b} ${bu}`;
80 let list = JSON.parse(localStorage.getItem(recentKey) || '[]').filter(x => x !== item);
81 list.unshift(item); list = list.slice(0, 8);
82 localStorage.setItem(recentKey, JSON.stringify(list)); renderRecent();
83 }
84 function renderRecent(){
85 const list = JSON.parse(localStorage.getItem(recentKey) || '[]');
86 els.recent.innerHTML = list.map(x=>`<li>${x}</li>`).join('') || '<li class="muted">No conversions yet</li>';
87 }
88 els.fromValue.addEventListener('input',()=>update('from'));
89 els.toValue.addEventListener('input',()=>update('to'));
90 ['fromUnit','toUnit','precision'].forEach(id=>els[id].addEventListener('change',()=>update(active)));
91 els.category.addEventListener('change', fillUnits);
92 els.swap.addEventListener('click',()=>{ [els.fromUnit.value, els.toUnit.value] = [els.toUnit.value, els.fromUnit.value]; update(active); els.fromValue.focus(); });
93 els.clearRecent.addEventListener('click',()=>{ localStorage.removeItem(recentKey); renderRecent(); });
94 document.addEventListener('keydown', e => { if (e.ctrlKey && e.key === '/') { e.preventDefault(); els.swap.click(); } });
95 fillCategories(); fillUnits(); renderRecent();
96 </script>
97</body>
98</html>
99
Discussion
No comments yet. Start the discussion. Recorded by @patrick-toulme.