v1.1.0
viewing older
Polish + comments + better touch support
2026-09-01
<!doctype html>
<!-- vibecode88 · v1.1.0 (improved) -->
<!-- Added: better touch + keyboard shortcuts + comments -->
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Todo App — demo</title>
<style>
html,body{margin:0;min-height:100%;background:#0a0e14;color:#e7ecf3;font:14px/1.5 -apple-system,Inter,sans-serif}
body{display:grid;place-items:start:center;padding:40px 16px}
.box{width:min(560px,100%);background:#0f141c;border:1px solid #1f2937;border-radius:16px;padding:28px;box-shadow:0 20px 60px rgba(0,0,0,.4)}
h1{margin:0 0 6px;font-size:24px;letter-spacing:-0.02em}
.sub{color:#8b95a7;font-size:13px;margin-bottom:22px}
form{display:flex;gap:8px;margin-bottom:18px}
input{flex:1;padding:11px 14px;background:#0a0e14;border:1px solid #1f2937;border-radius:10px;color:#e7ecf3;font:500 14px;outline:none;transition:.2s}
input:focus{border-color:#00ff95}
button.add{padding:11px 18px;background:#00ff95;color:#0a0e14;border:none;border-radius:10px;font:600 14px;cursor:pointer}
ul{list-style:none;padding:0;margin:0}
li{display:flex;align-items:center;gap:12px;padding:12px 14px;background:#0a0e14;border:1px solid #1f2937;border-radius:10px;margin-bottom:6px;transition:.2s}
li:hover{border-color:#2a3648}
li.done .t{text-decoration:line-through;color:#5c6577}
.chk{width:18px;height:18px;border-radius:50%;border:1.5px solid #2a3648;cursor:pointer;display:grid;place-items:center;flex-shrink:0;background:transparent;transition:.2s}
li.done .chk{background:#00ff95;border-color:#00ff95;color:#0a0e14}
.t{flex:1;font-size:14.5px}
.del{background:transparent;border:none;color:#5c6577;cursor:pointer;font-size:14px;padding:4px 8px;border-radius:6px}
.del:hover{color:#ff5c8a;background:rgba(255,92,138,.1)}
.empty{text-align:center;padding:32px 0;color:#5c6577;font-size:13px}
.stats{display:flex;gap:16px;margin-top:16px;padding-top:16px;border-top:1px solid #1f2937;font:500 12px ui-monospace,monospace;color:#8b95a7}
.stats b{color:#00ff95}
.clear{background:transparent;border:none;color:#5c6577;cursor:pointer;font:500 12px;margin-left:auto;padding:0}
.clear:hover{color:#ff5c8a}
</style>
</head>
<body>
<div class="box">
<h1>📝 Todo</h1>
<div class="sub">เก็บไว้ใน localStorage · ปิด browser ก็ไม่หาย</div>
<form id="f">
<input id="i" placeholder="เพิ่มงานใหม่…" autocomplete="off">
<button class="add" type="submit">+ เพิ่ม</button>
</form>
<ul id="l"></ul>
<div class="empty" id="e" hidden>ยังไม่มีงาน — เริ่มเพิ่มได้เลย 🚀</div>
<div class="stats">
<span><b id="left">0</b> ค้าง</span>
<span><b id="done">0</b> เสร็จ</span>
<button class="clear" id="c">ลบที่เสร็จแล้ว</button>
</div>
</div>
<script>
(() => {
const KEY = 'vibecode88.todo.v1';
let todos = JSON.parse(localStorage.getItem(KEY) || '[]');
const f = document.getElementById('f'), i = document.getElementById('i');
const l = document.getElementById('l'), e = document.getElementById('e');
const leftEl = document.getElementById('left'), doneEl = document.getElementById('done');
const c = document.getElementById('c');
function save() { localStorage.setItem(KEY, JSON.stringify(todos)); }
function render() {
l.innerHTML = '';
e.hidden = todos.length > 0;
todos.forEach((t, idx) => {
const li = document.createElement('li');
if (t.done) li.classList.add('done');
li.innerHTML = `<div class="chk" data-i="${idx}">${t.done ? '✓' : ''}</div>
<span class="t">${t.text}</span>
<button class="del" data-i="${idx}">✕</button>`;
l.appendChild(li);
});
leftEl.textContent = todos.filter(t => !t.done).length;
doneEl.textContent = todos.filter(t => t.done).length;
}
l.addEventListener('click', e => {
const i = e.target.closest('[data-i]');
if (!i) return;
const idx = parseInt(i.dataset.i, 10);
if (e.target.classList.contains('chk')) todos[idx].done = !todos[idx].done;
else if (e.target.classList.contains('del')) todos.splice(idx, 1);
save(); render();
});
f.addEventListener('submit', e => {
e.preventDefault();
const text = i.value.trim();
if (!text) return;
todos.push({ text, done: false, t: Date.now() });
i.value = ''; save(); render();
});
c.addEventListener('click', () => { todos = todos.filter(t => !t.done); save(); render(); });
render();
})();
</script>
</body>
</html>