Nach Filipes sieben Bildschirmfotos vom 07.10.2026. Sieben Wuensche,
und einer davon ("alles viel hochwertiger") betrifft alle anderen --
deshalb steht das Fundament zuerst und nicht am Ende.
DAS AUSSEHEN HAT EINEN WERKSTOFF. Jede Flaeche sagt jetzt, ob sie oben
oder unten liegt: Was obenauf liegt, bekommt oben eine helle Kante und
darunter einen Schatten; was vertieft ist, genau umgekehrt. Dazwischen
gibt es nichts mehr. Vorher stand im ganzen Pult zweimal eine
Lichtkante und sonst nirgends -- genau deshalb sahen diese zwei Stellen
gut aus und alles daneben danebengestellt. Dazu: jeder Knopf senkt sich
beim Druecken, jedes Feld ist ein Loch und kein Kasten, und wer mit der
Tastatur bedient, wird zum ersten Mal ueberhaupt gesehen.
UND EIN FUND DABEI: Die drei Kacheln des Regieplatzes hiessen in der
HTML anders als im Stilblatt (kachel--laeuft gegen regiekachel--laeuft,
regieregiekachel__kopf gegen regiekachel__kopf). Die Aufteilung "links
was laeuft, rechts was man eintraegt" hat damit NIE gegriffen -- und
mess-reaktion hat den falschen Zustand gemessen und gruen gemeldet,
weil sie gegen die HTML geschrieben war.
DIE SPENDENKARTE STEHT IN DER MITTE und kommt aus der Tiefe nach vorn.
Das dreht eine Entscheidung vom 28.09. um ("sie deckt nie die Mitte
zu"); die Begruendung war richtig und ist es noch -- sie schuetzt das
Video, und Filipe sieht die Spende als das, was in dem Augenblick
zaehlt. Der alte Platz bleibt einen Knopf weit weg und gilt fuer Saal
UND OBS-Tafel. Die Karte besteht dafuer aus zwei Teilen: `opacity`
unter 1 zwingt ein Element auf `transform-style: flat`, eine Karte mit
Deckkraft-Uebergang waere also genau waehrend des Hereinkommens flach
und spraenge am Ende in einem Bild in die Tiefe.
FERTIGE SAETZE GEHEN SOFORT INS BILD. Der Browser schickt nur die
Kennung, den Text holt der Server -- deshalb darf hier die Bestaetigung
entfallen und beim freien Text nicht. Zwei Schranken bleiben: sofort
nur mit einem der vier Knopfbetraege (Filipes Ansage galt dem SATZ,
nicht der Summe), und GEZEIGT IST NICHT GEBUCHT -- die Dogen gibt es
erst nach dem Tipp der Leitung, sonst stellt sich jeder seine eigene
Waehrung aus.
WER GESPENDET HAT, GLAENZT IM CHAT -- je nach Hoehe. Die Dauer haengt
an der Stufe und nicht an einer zweiten Leiter; 0 heisst "gar nicht".
GAESTE WERDEN GEFRAGT, NICHT GEHOLT. Vorher sprang die Kamera eines
Zuschauers an, sobald jemand "Dazuholen" drueckte -- er hat es daran
gemerkt, dass er sich selbst sah. Jetzt gibt es einen Vorraum: eigenes
Bild, Pegel, Kamera und Mikro vorwaehlen, Geraete waehlen, dann "Dabei
sein" oder "Lieber nicht". Zwei Minuten Frist, und sie verfaellt
SICHTBAR -- eine verschwundene Zeile sieht fuer den Host aus wie ein
Verklicker.
DAS MISCHPULT. Hier stand fest `echoCancellation: true,
noiseSuppression: true`. Fuer ein Laptopmikrofon richtig, ueber einem
RODECaster falsch: Das Pult hat Gate, Kompressor und EQ schon gemacht,
und die Echo-Unterdrueckung legt sich danach als zweite Automatik
darueber und duckt, sobald von der Gegenseite Ton kommt -- genau das
Pumpen vom 14.09. Jetzt: Geraetewahl fuer Mikro, Kamera und Ausgabe
(auch fuer Gaeste), ein Pultmodus, und darunter GEMESSEN, was wirklich
ankommt. Mit und ohne Pult.
DAS ZWEITE VIDEO war nie gesperrt -- es war nur unsichtbar. Jetzt
stehen Vorschaubild, Titel und die gemerkte Stelle da, "Jetzt wechseln"
auch von hier aus, und aus der Warteschlange legt ein Knopf eines
bereit.
UND EIN FUND AUF DEM BILDSCHIRMFOTO: Das Band "Ton einschalten" lag auf
derselben Hoehe wie die Senderleiste. Bei vier Knoepfen reichte die
Leiste bis zur Mitte -- vom Mikro-Knopf war nur noch ein "t" zu sehen.
Das war schon vorher so; der fuenfte Knopf hat es sichtbar gemacht.
Dieselbe Lehre wie am 06.09.: keine neue Zahl suchen, sondern die
Begegnung unmoeglich machen. mess-reaktion misst das jetzt als
UEBERLAPPUNG.
GEPRUEFT: pruef-reaktion 423 -> 478 (0 Fehler), pruef-spenden 172 -> 215
(0), pruef-reaktion-schmal 44 (0), pruef-tippziele 13 (0), dazu gruen:
pruef-css-klassen, pruef-alle-wege, pruef-haus-trennung, pruef-agentur,
pruef-deutsche-texte. Keine Zahl ist gefallen.
Drei Doppelungen hat erst das Pruefen gefunden: `satz_unbekannt` gab es
zweimal (Muenzsatz), `saetze` im Spendenstand war belegt (ebenfalls
Muenzsaetze), und `glanz_minuten` waere in einem frischen Haus immer 0
geblieben -- die Spalte kam per ALTER TABLE, und das laeuft VOR dem
Anlegen der Werksstufen.
Co-Authored-By: Claude Opus 5 <[email protected]>
192 lines
7.6 KiB
JavaScript
192 lines
7.6 KiB
JavaScript
/* =====================================================================
|
|
DIE SPENDENTAFEL ALS BROWSER-QUELLE (28.09.2026)
|
|
|
|
Hört am Bühnenstrom und lässt jede bestätigte Spende als Karte
|
|
laufen. Sonst tut diese Seite nichts.
|
|
|
|
---------------------------------------------------------------------
|
|
SIE MUSS TAGE LAUFEN, OHNE DASS JEMAND HINSIEHT
|
|
|
|
Das ist der ganze Unterschied zu einer Seite im Browser: Wer eine
|
|
Seite offen hat, merkt, wenn sie hängt, und lädt neu. Eine Quelle
|
|
in OBS läuft im Hintergrund, und wenn sie einmal stehenbleibt,
|
|
fällt es erst auf, wenn die erste Spende nicht erscheint — mitten
|
|
in der Sendung, vor Publikum.
|
|
|
|
Deshalb drei Dinge:
|
|
|
|
Der Browser verbindet einen abgerissenen Ereignisstrom von
|
|
selbst neu (`retry:` kommt vom Server). Darauf allein zu bauen
|
|
reicht aber nicht: Schläft der Rechner, kann der Strom in einem
|
|
Zustand stehenbleiben, aus dem der Browser nicht von selbst
|
|
herausfindet.
|
|
|
|
Also eine eigene WACHE: Kommt 70 Sekunden lang gar nichts — auch
|
|
kein Lebenszeichen, das alle 25 Sekunden gesendet wird —, wird
|
|
die Verbindung weggeworfen und neu aufgebaut.
|
|
|
|
Und ein FEHLER WIRD ANGEZEIGT, aber nur der, der etwas bedeutet:
|
|
ein falscher Schlüssel. Alles andere bleibt still, weil jede
|
|
Fläche hier im Stream zu sehen wäre.
|
|
===================================================================== */
|
|
(() => {
|
|
'use strict';
|
|
|
|
const tafel = document.getElementById('tafel');
|
|
|
|
/* ==== GROESSE UND LAGE STEHEN IN DER ADRESSE ====================
|
|
|
|
Wer in OBS eine Browser-Quelle einrichtet, hat die Adresse
|
|
ohnehin vor sich. Ein Wert, den man stattdessen im Regiepult
|
|
suchen muesste, waere ein Fensterwechsel mitten im Einrichten --
|
|
und beim Probieren aendert man zehnmal hintereinander etwas.
|
|
|
|
DIE VORGABE IST 1,4 UND NICHT 1. Im Saal steht die Karte neben
|
|
einem Chat und darf nichts zudecken; auf der Buehne deckt sie
|
|
nichts zu, was man lesen muesste, und darf deutlich groesser
|
|
sein. Eine Vorgabe, bei der man immer nachstellen muss, ist die
|
|
falsche. */
|
|
{
|
|
const p = new URLSearchParams(location.search);
|
|
const g = Number(String(p.get('g') || '').replace(',', '.'));
|
|
/* Zwischen 0,5 und 4. Darunter ist nichts mehr zu lesen, darueber
|
|
passt keine Karte mehr ins Bild -- beides waere eine Quelle,
|
|
die aussieht, als sei sie kaputt. */
|
|
const faktor = Number.isFinite(g) && g >= 0.5 && g <= 4 ? g : 1.4;
|
|
document.documentElement.style.fontSize = `${Math.round(16 * faktor)}px`;
|
|
|
|
/* ==== WO DIE KARTE STEHT — EINE ANTWORT, EINE AUSNAHME =======
|
|
(geändert 08.10.2026)
|
|
|
|
Seit heute entscheidet das Regiepult, ob die Karte in der
|
|
MITTE des Bildes steht oder am RAND; die Karte bringt den
|
|
Platz mit („Gestaltung → Wo die Karte steht"), und derselbe
|
|
Wert gilt im Saal und hier. Das ist die eine Antwort — wer
|
|
umstellt, stellt beide Orte zugleich um und muss nicht daran
|
|
denken, in OBS eine Adresse nachzuziehen.
|
|
|
|
`pos` IN DER ADRESSE BLEIBT TROTZDEM, UND ZWAR ALS AUSNAHME.
|
|
Im Stream liegt oft noch etwas anderes im Bild — ein
|
|
Kamerakasten, ein Alert, ein Rahmen. Dafür braucht es eine
|
|
Stelle, an der man die Karte für DIESE Szene wegschiebt, ohne
|
|
den Saal mitzuverstellen.
|
|
|
|
UND SIE GILT NUR, WENN SIE AUSDRÜCKLICH DASTEHT. Vorher stand
|
|
hier `p.get('pos') || 'ul'` — damit hatte JEDE Adresse eine
|
|
Lage, auch die, in der nie jemand eine angegeben hat, und die
|
|
Hauseinstellung wäre an dieser Seite wirkungslos verpufft.
|
|
Das ist der Unterschied zwischen einer Ausnahme und einer
|
|
zweiten Regel.
|
|
|
|
Die Werte heißen `align-content`/`justify-items` und nicht
|
|
`justify-content`/`align-items`: Die Tafel ist seit dem
|
|
08.10. ein Raster mit einer Spalte, und dort sind das die
|
|
Eigenschaften, die wirken. */
|
|
const lagen = {
|
|
ul: ['end', 'start'], ur: ['end', 'end'],
|
|
ol: ['start', 'start'], or: ['start', 'end'],
|
|
mitte: ['center', 'center'],
|
|
};
|
|
const gewuenscht = String(p.get('pos') || '');
|
|
if (gewuenscht && lagen[gewuenscht]) {
|
|
const [senkrecht, waagerecht] = lagen[gewuenscht];
|
|
tafel.style.alignContent = senkrecht;
|
|
tafel.style.justifyItems = waagerecht;
|
|
/* Die FORM muss zur Lage passen: In der Mitte steht die Karte
|
|
als Aufsteller (Münze oben, Betrag darunter), an jeder
|
|
anderen Lage als Zeile. Ohne diese Zeile stünde mitten im
|
|
Bild die flache Randfassung. */
|
|
tafel.dataset.platz = gewuenscht === 'mitte' ? 'mitte' : 'rand';
|
|
tafel.dataset.posFest = 'ja';
|
|
}
|
|
}
|
|
const schluessel = new URLSearchParams(location.search).get('s') || '';
|
|
|
|
function sagFehler(text) {
|
|
let k = document.querySelector('.obs-fehler');
|
|
if (!k) {
|
|
k = document.createElement('div');
|
|
k.className = 'obs-fehler';
|
|
document.body.append(k);
|
|
}
|
|
k.textContent = text;
|
|
}
|
|
|
|
if (!schluessel) {
|
|
sagFehler('Diese Adresse ist unvollständig — der Schlüssel fehlt. '
|
|
+ 'Hol sie dir im Regiepult unter „Bühne“.');
|
|
return;
|
|
}
|
|
|
|
let strom = null;
|
|
let letztes = Date.now();
|
|
|
|
function verbinden() {
|
|
try { if (strom) strom.close(); } catch { /* war schon zu */ }
|
|
strom = new EventSource(
|
|
`/workspace/api/buehne/strom?s=${encodeURIComponent(schluessel)}`);
|
|
|
|
strom.addEventListener('open', () => {
|
|
letztes = Date.now();
|
|
const k = document.querySelector('.obs-fehler');
|
|
if (k) k.remove();
|
|
});
|
|
|
|
/* JEDE NACHRICHT ZAEHLT ALS LEBENSZEICHEN -- auch die Doppelpunkt-
|
|
Zeile, die der Server alle 25 Sekunden schickt. Sie kommt hier
|
|
als „message" ohne Inhalt an. */
|
|
strom.addEventListener('message', () => { letztes = Date.now(); });
|
|
|
|
strom.addEventListener('spende', (e) => {
|
|
letztes = Date.now();
|
|
try {
|
|
const d = JSON.parse(e.data);
|
|
if (d.was === 'zeigen' && window.Spendenkarte) {
|
|
window.Spendenkarte.zeigen(tafel, d.karte);
|
|
}
|
|
} catch { /* eine kaputte Meldung darf die Quelle nicht reissen */ }
|
|
});
|
|
|
|
strom.addEventListener('error', () => {
|
|
/* KEINE MELDUNG INS BILD. Ein kurzer Abriss ist normal, und der
|
|
Browser verbindet von selbst neu. Eine rote Fläche dafür
|
|
stünde im Stream. */
|
|
if (strom.readyState === EventSource.CLOSED) {
|
|
setTimeout(verbinden, 3000);
|
|
}
|
|
});
|
|
}
|
|
|
|
/* Einmal fragen, ob der Schlüssel überhaupt stimmt. Eine Quelle,
|
|
die still bleibt, weil die Adresse falsch ist, sieht genauso aus
|
|
wie eine, bei der nur niemand gespendet hat -- und das ist die
|
|
eine Verwechslung, die hier wirklich schadet. */
|
|
fetch(`/workspace/api/buehne/lage?s=${encodeURIComponent(schluessel)}`)
|
|
.then((a) => {
|
|
if (a.status === 404) {
|
|
sagFehler('Der Schlüssel stimmt nicht mehr. '
|
|
+ 'Hol dir die Adresse neu im Regiepult unter „Bühne“.');
|
|
return;
|
|
}
|
|
verbinden();
|
|
})
|
|
.catch(() => verbinden());
|
|
|
|
/* DIE WACHE. Kommt 70 Sekunden nichts -- nicht einmal ein
|
|
Lebenszeichen --, ist die Verbindung tot, auch wenn der Browser
|
|
sie fuer offen haelt. */
|
|
setInterval(() => {
|
|
if (Date.now() - letztes > 70_000) {
|
|
letztes = Date.now();
|
|
verbinden();
|
|
}
|
|
}, 10_000);
|
|
|
|
/* WACHT DER RECHNER AUF, WIRD SOFORT NEU VERBUNDEN. Nach dem
|
|
Ruhezustand ist ein Ereignisstrom fast immer tot, und bis die
|
|
Wache greift, vergeht eine Minute. */
|
|
document.addEventListener('visibilitychange', () => {
|
|
if (!document.hidden && Date.now() - letztes > 30_000) verbinden();
|
|
});
|
|
})();
|