kicks
@@ -677,6 +792,51 @@ document.querySelectorAll('canvas.hw-knob').forEach((canvas) => {
});
});
+// ===========================================================================
+// faders — drag the cap or click anywhere on the track to jump
+// ===========================================================================
+document.querySelectorAll('.fader-cell').forEach((cell) => {
+ const track = cell.querySelector('.fd-track');
+ const cap = cell.querySelector('.fd-cap');
+ const valueEl = cell.querySelector('.value');
+ let value = parseFloat(track.dataset.value || '0.5');
+
+ function paint() {
+ const trackH = track.clientHeight;
+ cap.style.top = ((1 - value) * trackH) + 'px';
+ if (valueEl) valueEl.textContent = value.toFixed(2);
+ }
+ // wait for layout
+ requestAnimationFrame(paint);
+
+ function valueAt(clientY) {
+ const rect = track.getBoundingClientRect();
+ const norm = 1 - (clientY - rect.top) / rect.height;
+ return Math.max(0, Math.min(1, norm));
+ }
+
+ function startDrag(e, jumpToPointer) {
+ e.preventDefault();
+ cap.classList.add('dragging');
+ track.setPointerCapture(e.pointerId);
+ document.body.style.cursor = 'grabbing';
+ if (jumpToPointer) { value = valueAt(e.clientY); paint(); }
+ const onMove = (ev) => { value = valueAt(ev.clientY); paint(); };
+ const onUp = () => {
+ cap.classList.remove('dragging');
+ document.body.style.cursor = '';
+ track.removeEventListener('pointermove', onMove);
+ track.removeEventListener('pointerup', onUp);
+ track.removeEventListener('pointercancel', onUp);
+ };
+ track.addEventListener('pointermove', onMove);
+ track.addEventListener('pointerup', onUp);
+ track.addEventListener('pointercancel', onUp);
+ }
+ // click on track jumps to that position; drag continues from there
+ track.addEventListener('pointerdown', (e) => startDrag(e, true));
+});
+
// ===========================================================================
// step LEDs
// ===========================================================================