observer effect content
This commit is contained in:
parent
625a847a81
commit
e4ee25295c
23 changed files with 2982 additions and 0 deletions
120
services/observer-effect/site/control.js
Normal file
120
services/observer-effect/site/control.js
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
/* Handler's control panel. Sends authenticated commands to the relay and
|
||||
mirrors the live clock so the Handler sees exactly what the players see. */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
|
||||
// --- control key (persisted locally) --------------------------------------
|
||||
const tokenInput = $("token");
|
||||
tokenInput.value = localStorage.getItem("oe-token") || "";
|
||||
function saveToken() {
|
||||
localStorage.setItem("oe-token", tokenInput.value.trim());
|
||||
flashHint("key set");
|
||||
}
|
||||
$("saveToken").addEventListener("click", saveToken);
|
||||
tokenInput.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") saveToken();
|
||||
});
|
||||
let hintTimer = null;
|
||||
function flashHint(text) {
|
||||
const el = $("tokenHint");
|
||||
el.textContent = text;
|
||||
clearTimeout(hintTimer);
|
||||
hintTimer = setTimeout(() => (el.textContent = ""), 1500);
|
||||
}
|
||||
|
||||
// --- connection -----------------------------------------------------------
|
||||
const model = OE.ClockModel();
|
||||
const conn = OE.connect(OE.defaultWsUrl(), {
|
||||
onstatus(s) {
|
||||
$("lampLink").classList.toggle("lamp--on", s === "open");
|
||||
},
|
||||
onstate(state) {
|
||||
model.apply(state);
|
||||
syncRateButtons(state.rate);
|
||||
},
|
||||
});
|
||||
window.addEventListener("beforeunload", () => conn.close());
|
||||
|
||||
function send(obj) {
|
||||
const token = (localStorage.getItem("oe-token") || "").trim();
|
||||
if (!token) {
|
||||
flashHint("no control key set");
|
||||
tokenInput.focus();
|
||||
return;
|
||||
}
|
||||
conn.send(Object.assign({ token }, obj));
|
||||
}
|
||||
|
||||
// --- wire fixed-command buttons (play/pause/pulse/communion) ---------------
|
||||
document.querySelectorAll("[data-cmd]").forEach((btn) => {
|
||||
btn.addEventListener("click", () => send({ cmd: btn.dataset.cmd }));
|
||||
});
|
||||
|
||||
// --- rate -----------------------------------------------------------------
|
||||
document.querySelectorAll(".rate").forEach((btn) => {
|
||||
btn.addEventListener("click", () => send({ cmd: "rate", rate: Number(btn.dataset.rate) }));
|
||||
});
|
||||
function syncRateButtons(rate) {
|
||||
document.querySelectorAll(".rate").forEach((b) => {
|
||||
b.classList.toggle("active", Number(b.dataset.rate) === rate);
|
||||
});
|
||||
}
|
||||
|
||||
// --- iterations -----------------------------------------------------------
|
||||
document.querySelectorAll(".iter").forEach((btn) => {
|
||||
btn.addEventListener("click", () => send({ cmd: "iteration", n: Number(btn.dataset.iter) }));
|
||||
});
|
||||
|
||||
// --- timeline jumps -------------------------------------------------------
|
||||
const jumps = $("jumps");
|
||||
OE.EVENTS.forEach((ev) => {
|
||||
const b = document.createElement("button");
|
||||
b.type = "button";
|
||||
b.className = "btn jump" + (ev.communion ? " btn--red" : ev.pulse ? " btn--amber" : "");
|
||||
b.innerHTML = "<b>" + ev.time + "</b><span>" + ev.label + "</span>";
|
||||
b.addEventListener("click", () => send({ cmd: "set", inworld: ev.t }));
|
||||
jumps.appendChild(b);
|
||||
});
|
||||
|
||||
// manual time -> seconds-of-day
|
||||
$("manualJump").addEventListener("click", () => {
|
||||
const parts = $("manualTime").value.split(":").map(Number);
|
||||
if (parts.length >= 2 && parts.every((n) => !Number.isNaN(n))) {
|
||||
const sec = (parts[0] || 0) * 3600 + (parts[1] || 0) * 60 + (parts[2] || 0);
|
||||
send({ cmd: "set", inworld: sec });
|
||||
}
|
||||
});
|
||||
|
||||
// --- broadcast message ----------------------------------------------------
|
||||
$("sendMsg").addEventListener("click", () => {
|
||||
const text = $("msg").value.trim();
|
||||
if (text) send({ cmd: "msg", text });
|
||||
});
|
||||
$("clearMsg").addEventListener("click", () => send({ cmd: "msg", text: null }));
|
||||
$("msg").addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") $("sendMsg").click();
|
||||
});
|
||||
|
||||
// player-screen link points at clock.html next to this page
|
||||
$("clockLink").href = new URL("clock.html", location.href).href;
|
||||
|
||||
// --- mirror loop ----------------------------------------------------------
|
||||
function nextBeat(inworld) {
|
||||
const upcoming = OE.EVENTS.filter((e) => e.t > inworld + 0.5);
|
||||
return upcoming.length ? upcoming[0] : null;
|
||||
}
|
||||
function frame() {
|
||||
const s = model.now();
|
||||
$("mClock").textContent = OE.clock(s.inworld);
|
||||
$("mIter").textContent = OE.ROMAN[s.iteration] || s.iteration;
|
||||
$("mCountdown").textContent = s.remaining <= 0 ? "00:00:00" : OE.duration(s.remaining);
|
||||
$("mRun").textContent = s.running ? "RUNNING" : "PAUSED";
|
||||
$("mRun").classList.toggle("live", s.running);
|
||||
const nb = nextBeat(s.inworld);
|
||||
$("mNext").textContent = nb ? nb.time + " " + nb.label : "—";
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
requestAnimationFrame(frame);
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue