← back to Dot Palette
public/index.html
92 lines
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>dot-palette</title>
<style>
:root { color-scheme: dark; }
* { box-sizing: border-box; -webkit-user-select: none; user-select: none; }
html, body { margin: 0; height: 100%; background: #14161a; overflow: hidden;
font: 12px -apple-system, system-ui, sans-serif; color: #cbd2da; }
#bar { display: flex; align-items: center; gap: 6px; height: 100%; padding: 0 8px; }
/* Left grip: the drag handle (the window is non-activating, so we move it via IPC deltas). */
#grip { cursor: grab; color: #4a525c; font-size: 13px; letter-spacing: -2px; padding: 0 2px; }
#grip:active { cursor: grabbing; }
.chip { width: 30px; height: 30px; border-radius: 50%; border: 2px solid rgba(255,255,255,.14);
display: flex; align-items: center; justify-content: center; font-size: 15px; cursor: pointer;
transition: transform .08s ease, box-shadow .12s ease; }
.chip:hover { transform: scale(1.12); }
.chip:active { transform: scale(.92); }
.chip.ok { box-shadow: 0 0 0 3px rgba(255,255,255,.85); }
.chip.bad { box-shadow: 0 0 0 3px #ff3b30; }
#x { margin-left: 2px; color: #565e69; cursor: pointer; font-size: 14px; padding: 0 3px; }
#x:hover { color: #ff5b52; }
</style>
</head>
<body>
<div id="bar">
<div id="grip" title="drag to move">⠿</div>
<div id="chips"></div>
<div id="x" title="close palette">×</div>
</div>
<script>
const CHIPS = [
{ c: 'green', bg: '#00c853', em: '🟢', name: 'WORKING' },
{ c: 'yellow', bg: '#ffcc00', em: '🟡', name: 'DIRECTION?' },
{ c: 'orange', bg: '#ff8c00', em: '🟠', name: 'PASTE waiting' },
{ c: 'purple', bg: '#9400d3', em: '🟣', name: 'GATED' },
{ c: 'lightblue', bg: '#00b0f0', em: '🔵', name: 'NEEDS STEVE' },
{ c: 'pink', bg: '#ff69b4', em: '🩷', name: 'PARKED' },
];
const chipsEl = document.getElementById('chips');
chipsEl.style.display = 'flex';
chipsEl.style.gap = '6px';
for (const k of CHIPS) {
const el = document.createElement('div');
el.className = 'chip';
el.textContent = k.em;
el.style.background = k.bg + '22'; // faint fill; the ring + emoji carry the colour
el.style.borderColor = k.bg;
el.title = k.c + ' — ' + k.name;
el.addEventListener('click', async () => {
try {
const r = await fetch('/api/setdot', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ color: k.c }),
});
const j = await r.json();
el.classList.remove('ok', 'bad');
void el.offsetWidth; // restart the CSS transition
el.classList.add(j.ok ? 'ok' : 'bad');
el.title = j.ok ? (k.c + ' — set on ' + j.tty) : (k.c + ' — ' + (j.error || 'failed'));
setTimeout(() => el.classList.remove('ok', 'bad'), 650);
} catch (e) {
el.classList.add('bad');
setTimeout(() => el.classList.remove('bad'), 650);
}
});
chipsEl.appendChild(el);
}
// Drag-to-move: the panel is focusable:false so native title-bar drag won't fire;
// track pointer deltas on the grip and nudge the window via the main process.
const grip = document.getElementById('grip');
let dragging = false, lastX = 0, lastY = 0;
grip.addEventListener('pointerdown', (e) => {
dragging = true; lastX = e.screenX; lastY = e.screenY;
grip.setPointerCapture(e.pointerId); e.preventDefault();
});
grip.addEventListener('pointermove', (e) => {
if (!dragging) return;
const dx = e.screenX - lastX, dy = e.screenY - lastY;
lastX = e.screenX; lastY = e.screenY;
if (dx || dy) window.palette?.move?.(dx, dy); // no-op if the IPC bridge is absent (e.g. opened in a plain browser)
});
grip.addEventListener('pointerup', (e) => { dragging = false; try { grip.releasePointerCapture(e.pointerId); } catch {} });
document.getElementById('x').addEventListener('click', () => window.palette?.quit?.()); // guarded: harmless if no IPC bridge
</script>
</body>
</html>