/* Per-device program I/O + "Show info" toggle — assembled into each form-factor page. Uses the engine codec (patchToSetup / setupToPatch / codeToSetlist) for decode + lint. Host page provides: window.currentProgramString() and window.loadProgramString(plain). Exposes window.progRefresh() — call it after the device's program changes. When the page is embedded it posts {type:'varasys-prog'} to the parent instead of touching the box. Defers to DOM-ready so the box/toggle/techinfo are found wherever they sit on the page. */ (function () { var embedded = document.documentElement.dataset.embed === "1"; var box, msg, editing = false; function setMsg(t, ok) { if (!msg) return; msg.textContent = t || ""; msg.classList.toggle("ok", !!ok && !!t); msg.classList.toggle("bad", !ok && !!t); } function lint(text) { text = (text || "").trim(); if (!text) return { ok: false, msg: "empty" }; var m = text.match(/[#?&](p|sl)=([^&\s]+)/), kind = null, payload = text; if (m) { kind = m[1]; try { payload = decodeURIComponent(m[2]); } catch (e) { payload = m[2]; } } var b64 = /^[A-Za-z0-9_-]{12,}$/.test(payload) && !/[;:]/.test(payload); try { if (kind === "sl" || (kind !== "p" && b64)) { var sl = codeToSetlist(payload); if (!sl.items || !sl.items.length) throw new Error("set-list code has no items"); return { ok: true, plain: setupToPatch(sl.items[0]), msg: "decoded set list “" + sl.title + "” — item 1" }; } var s = patchToSetup(payload); if (!s.lanes.length) throw new Error("no lanes — try e.g. kick:4"); return { ok: true, plain: setupToPatch(s), msg: s.lanes.length + " lane" + (s.lanes.length > 1 ? "s" : "") + " · " + s.bpm + " BPM" }; } catch (e) { return { ok: false, msg: "✗ " + e.message }; } } function doLoad() { var r = lint(box.value); if (!r.ok) { box.classList.add("err"); setMsg(r.msg, false); return; } box.classList.remove("err"); box.value = r.plain; setMsg("✓ " + r.msg, true); try { if (window.loadProgramString) window.loadProgramString(r.plain); } catch (e) { setMsg("✗ " + e.message, false); } } // report the current program: to the parent when embedded, else into the box. // No-op on pages without a program hook (e.g. panel-based Teacher/Player). window.progRefresh = function () { if (!window.currentProgramString) return; var p = ""; try { p = window.currentProgramString(); } catch (e) {} if (embedded) { try { parent.postMessage({ type: "varasys-prog", patch: p }, "*"); } catch (e) {} return; } if (box && !editing) { box.value = p; setMsg("", true); } }; function init() { box = document.getElementById("dProg"); msg = document.getElementById("dProgMsg"); if (box) { box.addEventListener("focus", function () { editing = true; }); box.addEventListener("blur", function () { editing = false; }); box.addEventListener("input", function () { box.classList.remove("err"); }); box.addEventListener("keydown", function (e) { if (e.key === "Enter") { e.preventDefault(); doLoad(); } }); var loadBtn = document.getElementById("dProgLoad"); if (loadBtn) loadBtn.addEventListener("click", doLoad); var copyBtn = document.getElementById("dProgCopy"); if (copyBtn) copyBtn.addEventListener("click", function () { try { navigator.clipboard.writeText(box.value); copyBtn.textContent = "Copied!"; setTimeout(function () { copyBtn.textContent = "Copy"; }, 1200); } catch (e) { box.select(); } }); } // "Show info" toggle — reveals the technical section (#techinfo + any .tech); ?info=1 opens it checked var tog = document.getElementById("infoToggle"), tech = document.getElementById("techinfo"); if (tog) { var extra = document.querySelectorAll(".tech"); var apply = function (on) { if (tech) tech.hidden = !on; for (var i = 0; i < extra.length; i++) extra[i].hidden = !on; }; var open = /[?&]info=1/.test(location.search); tog.checked = open; apply(open); tog.addEventListener("change", function () { apply(tog.checked); }); } window.progRefresh(); } if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init); else init(); })();