/** * Bluetooth / USB HID numpad shortcuts (browser focus required). * * Numpad1–9,0 → zone 1–10 (visible zone list order) * NumpadEnter → sequence beat sync (step), same as S * NumpadDecimal → sequence beat sync (pass), same as Shift+S * NumpadMultiply → reset audio detector * NumpadAdd → brightness +16 * NumpadSubtract → brightness −16 * NumpadDivide → stop zone sequence playback */ (() => { const BRIGHTNESS_STEP = 16; function isTypingTarget(target) { if (!target || typeof target !== "object") return false; const tag = String(target.tagName || "").toLowerCase(); return tag === "input" || tag === "textarea" || tag === "select" || target.isContentEditable; } function zoneIdsInListOrder() { return [...document.querySelectorAll("#zones-list .zone-button[data-zone-id]")] .map((el) => el.getAttribute("data-zone-id")) .filter((id) => id != null && id !== ""); } async function selectZoneByListIndex(oneBased) { const order = zoneIdsInListOrder(); if (oneBased < 1 || oneBased > order.length) return; const zoneId = order[oneBased - 1]; if (window.tabsManager && typeof window.tabsManager.selectZone === "function") { await window.tabsManager.selectZone(zoneId); } else if (typeof selectZone === "function") { await selectZone(zoneId); } } async function syncSequenceBeatPhase(mode) { const res = await fetch("/sequences/sync-phase", { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json" }, body: JSON.stringify({ mode: mode || "step" }), }); if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error(data.error || `Sync failed (${res.status})`); } } async function resetAudioTracking() { const res = await fetch("/api/audio/reset", { method: "POST", headers: { Accept: "application/json" }, }); if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error(data.error || `Reset failed (${res.status})`); } } function adjustZoneBrightness(delta) { const zoneId = (window.tabsManager && typeof window.tabsManager.getCurrentTabId === "function" ? window.tabsManager.getCurrentTabId() : null) || (window.tabsManager && typeof window.tabsManager.getCurrentZoneId === "function" ? window.tabsManager.getCurrentZoneId() : null); if (!zoneId) return; const slider = document.getElementById("header-brightness-slider") || document.getElementById("menu-brightness-slider"); if (!slider) return; const cur = parseInt(slider.value, 10); const base = Number.isFinite(cur) ? cur : 127; const next = Math.max(0, Math.min(255, base + delta)); if (String(slider.value) === String(next)) return; slider.value = String(next); slider.dispatchEvent(new Event("input", { bubbles: true })); } async function stopSequencePlayback() { if (typeof window.stopZoneSequencePlayback === "function") { await window.stopZoneSequencePlayback(true); } } /** @type {Record void | Promise>} */ const actions = { NumpadEnter: () => syncSequenceBeatPhase("step"), NumpadDecimal: () => syncSequenceBeatPhase("pass"), NumpadMultiply: () => resetAudioTracking(), NumpadAdd: () => adjustZoneBrightness(BRIGHTNESS_STEP), NumpadSubtract: () => adjustZoneBrightness(-BRIGHTNESS_STEP), NumpadDivide: () => stopSequencePlayback(), Numpad1: () => selectZoneByListIndex(1), Numpad2: () => selectZoneByListIndex(2), Numpad3: () => selectZoneByListIndex(3), Numpad4: () => selectZoneByListIndex(4), Numpad5: () => selectZoneByListIndex(5), Numpad6: () => selectZoneByListIndex(6), Numpad7: () => selectZoneByListIndex(7), Numpad8: () => selectZoneByListIndex(8), Numpad9: () => selectZoneByListIndex(9), Numpad0: () => selectZoneByListIndex(10), }; document.addEventListener("keydown", (ev) => { if (ev.defaultPrevented || ev.repeat || isTypingTarget(ev.target)) return; const code = ev.code; if (!code || !code.startsWith("Numpad")) return; const action = actions[code]; if (!action) return; ev.preventDefault(); Promise.resolve(action()).catch((e) => console.warn("numpad shortcut failed:", e)); }); })();