diff --git a/server/mess-reaktion.mjs b/server/mess-reaktion.mjs
index 367cc7e2..ceec9ca6 100644
--- a/server/mess-reaktion.mjs
+++ b/server/mess-reaktion.mjs
@@ -685,6 +685,172 @@ if (zuschauer.konsole.length) console.log(" KONSOLE:", zuschauer.konsole.slice
Abzeichen UND ein zweiter Knopf dazugekommen. Genau so ist am
28.09. die Regie 197 px uebergelaufen: Die Rechnung ging auf, die
Wirklichkeit nicht. */
+/* ---- Die Regieleiste ueber dem Saal -----------------------------
+
+ Filipe, 28.09.2026: „ich haette das gerne oben also nicht in dieser
+ kachel sondern ueber der kachel wo die videos laufen werden."
+
+ GEMESSEN WIRD, WAS SIE KOSTET UND WAS SIE KANN. Eine Leiste am
+ oberen Rand nimmt Hoehe -- und zwar dem Video, um das es geht.
+ Zwei Zeilen statt einer waeren rund 90 Pixel, und das sieht man
+ auf dem Bildschirmfoto nicht, weil beides „eine Leiste" ist. */
+console.log("");
+console.log(" --- Die Regieleiste ---");
+{
+ const leiste = await host.seite.evaluate(() => {
+ const l = document.getElementById("regieleiste");
+ const r = document.getElementById("reiter");
+ const kino = document.getElementById("teil-live");
+ if (!l || !r || !kino) return null;
+ const lr = l.getBoundingClientRect();
+ const kr = kino.getBoundingClientRect();
+ const knoepfe = [...r.querySelectorAll(".reiter__knopf")];
+ return {
+ hoch: Math.round(lr.height),
+ oben: Math.round(lr.top),
+ /* STEHT SIE WIRKLICH UEBER DEM KINO? Das ist die Frage, nicht
+ ob sie irgendwo existiert. */
+ ueberDemKino: lr.bottom <= kr.top + 1,
+ knoepfe: knoepfe.length,
+ /* EINE ZEILE ODER ZWEI? Gemessen an den Oberkanten -- wenn
+ zwei Knoepfe verschieden hoch sitzen, bricht sie um. */
+ zeilen: new Set(knoepfe.map((k) => Math.round(k.getBoundingClientRect().top))).size,
+ kleinster: Math.min(...knoepfe.map((k) => Math.round(k.getBoundingClientRect().height))),
+ rollt: r.scrollWidth > r.clientWidth,
+ /* Genau EIN Register in der Tabulatorfolge. */
+ imTabulator: knoepfe.filter((k) => k.tabIndex === 0).length,
+ marke: (() => {
+ const m = document.getElementById("reiter-marke");
+ if (!m || m.hidden) return null;
+ const mr = m.getBoundingClientRect();
+ const a = knoepfe.find((k) => k.getAttribute("aria-selected") === "true");
+ const ar = a.getBoundingClientRect();
+ return {
+ breit: Math.round(mr.width),
+ /* Sitzt sie unter dem gewaehlten Register? */
+ versatz: Math.round(Math.abs(mr.left - ar.left)),
+ gleichBreit: Math.abs(mr.width - ar.width) < 2,
+ };
+ })(),
+ };
+ });
+
+ if (!leiste) {
+ console.log(" ACHTUNG: Die Regieleiste gibt es nicht.");
+ ausgang = 1;
+ } else {
+ console.log(` ${leiste.knoepfe} Register in ${leiste.zeilen} Zeile(n), `
+ + `Leiste ${leiste.hoch} px hoch bei y=${leiste.oben}, `
+ + `kleinstes Ziel ${leiste.kleinster} px, rollt=${leiste.rollt}`);
+ console.log(` Ueber dem Kino: ${leiste.ueberDemKino} · `
+ + `im Tabulator: ${leiste.imTabulator} · `
+ + `Marke ${leiste.marke ? `${leiste.marke.breit} px, Versatz ${leiste.marke.versatz} px` : "(keine)"}`);
+
+ if (!leiste.ueberDemKino) {
+ console.log(" ACHTUNG: Die Leiste steht nicht ueber dem Kino.");
+ ausgang = 1;
+ }
+ if (leiste.zeilen !== 1) {
+ console.log(" ACHTUNG: Die Register brechen um -- das kostet das Video Hoehe.");
+ ausgang = 1;
+ }
+ if (leiste.kleinster < 44) {
+ console.log(" ACHTUNG: Ein Register ist kleiner als 44 px.");
+ ausgang = 1;
+ }
+ if (leiste.imTabulator !== 1) {
+ console.log(" ACHTUNG: Der Tabulator wandert nicht -- sieben Schritte bis zum Inhalt.");
+ ausgang = 1;
+ }
+ if (!leiste.marke || leiste.marke.versatz > 2 || !leiste.marke.gleichBreit) {
+ console.log(" ACHTUNG: Die Marke sitzt nicht unter dem gewaehlten Register.");
+ ausgang = 1;
+ }
+ }
+
+ /* ---- DIE TASTATUR MUSS TUN, WAS `role=tablist` VERSPRICHT ----
+ Ein Versprechen im Markup, das das Programm nicht einloest, ist
+ schlimmer als gar keines: Der Vorleser sagt etwas an, was nicht
+ stimmt. Also wird es gedrueckt, nicht gelesen. */
+ await host.seite.evaluate(() => {
+ document.querySelector('#reiter .reiter__knopf[data-tafel="sendung"]').focus();
+ });
+ const tasten = [];
+ for (const [taste, wohin] of [["ArrowRight", "rechts"], ["ArrowRight", "rechts"],
+ ["ArrowLeft", "links"], ["End", "ans Ende"], ["Home", "an den Anfang"]]) {
+ await host.seite.keyboard.press(taste);
+ await host.seite.waitForTimeout(220);
+ const wo = await host.seite.evaluate(() => {
+ const a = document.activeElement;
+ const g = [...document.querySelectorAll("#reiter .reiter__knopf")]
+ .find((k) => k.getAttribute("aria-selected") === "true");
+ const t = [...document.querySelectorAll(".pult-tafel")].find((x) => !x.hidden);
+ return {
+ fokus: a?.dataset?.tafel || "(?)",
+ gewaehlt: g?.dataset?.tafel || "(?)",
+ tafel: t?.dataset?.tafel || "(keine)",
+ };
+ });
+ tasten.push(`${wohin}->${wo.fokus}`);
+ /* FOKUS, AUSWAHL UND OFFENE TAFEL MUESSEN DASSELBE SAGEN.
+ Laufen sie auseinander, zeigt die Regie eine andere Tafel als
+ das hervorgehobene Register -- und man stellt etwas ein, von
+ dem man glaubt, es gehoere woandershin. */
+ if (wo.fokus !== wo.gewaehlt || wo.gewaehlt !== wo.tafel) {
+ console.log(` ACHTUNG: Fokus ${wo.fokus}, gewaehlt ${wo.gewaehlt}, `
+ + `offen ${wo.tafel} -- das passt nicht zusammen.`);
+ ausgang = 1;
+ }
+ }
+ console.log(` Tastatur: ${tasten.join(" · ")}`);
+
+ /* ---- EIN REGISTER MACHT DIE ZUGEKLAPPTE REGIE AUF ----
+ Seit die Leiste oben steht, kann das Pult zu sein, waehrend man
+ ein Register waehlt. Ohne das waere der Knopf einer, bei dem
+ sichtbar nichts passiert. */
+ await host.seite.click("#pult-griff").catch(() => { });
+ await host.seite.waitForTimeout(400);
+ const zu = await host.seite.evaluate(() =>
+ document.getElementById("pult").dataset.auf);
+ await host.seite.click('#reiter .reiter__knopf[data-tafel="ton"]').catch(() => { });
+ await host.seite.waitForTimeout(500);
+ const danach = await host.seite.evaluate(() => ({
+ auf: document.getElementById("pult").dataset.auf,
+ tafel: [...document.querySelectorAll(".pult-tafel")].find((x) => !x.hidden)?.dataset.tafel,
+ sichtbar: (() => {
+ const t = document.getElementById("tafel-ton");
+ const r = t.getBoundingClientRect();
+ return r.height > 0 && r.top < window.innerHeight;
+ })(),
+ }));
+ console.log(` Regie zugeklappt (auf=${zu}), Register „Ton" gedrueckt `
+ + `-> auf=${danach.auf}, offen=${danach.tafel}, sichtbar=${danach.sichtbar}`);
+ if (zu !== "nein" || danach.auf !== "ja" || !danach.sichtbar) {
+ console.log(" ACHTUNG: Ein Register macht die zugeklappte Regie nicht auf.");
+ ausgang = 1;
+ }
+ await bild(host.seite, "20-regieleiste");
+
+ /* ---- WAS SIE DEM VIDEO KOSTET ----
+ Die eigentliche Frage bei einer Leiste am oberen Rand. */
+ const platz = await host.seite.evaluate(() => {
+ const l = document.getElementById("regieleiste").getBoundingClientRect();
+ const k = document.getElementById("teil-live").getBoundingClientRect();
+ const s2 = document.getElementById("saal").getBoundingClientRect();
+ return {
+ leiste: Math.round(l.height), kino: Math.round(k.height), saal: Math.round(s2.height),
+ ueber: Math.max(0, document.documentElement.scrollHeight
+ - document.documentElement.clientHeight),
+ };
+ });
+ console.log(` Saal ${platz.saal} px = Leiste ${platz.leiste} + Kino ${platz.kino} `
+ + `+ Regie, senkrecht ueber ${platz.ueber} px`);
+ if (platz.ueber > 6 || platz.kino < 200) {
+ console.log(" ACHTUNG: Die Leiste nimmt dem Bild zu viel oder die Seite laeuft ueber.");
+ ausgang = 1;
+ }
+}
+
/* ---- Die Gestaltung der Spendenkarten ---------------------------
Filipe: „mach paar fertige und so dass ich auch hochladen kann.
@@ -785,7 +951,7 @@ const messeKarte = async (erwartet) => {
Messung bestaetigte eine Aenderung, die gar nicht
angekommen ist. */
await host.seite.waitForFunction(
- (soll) => [...document.querySelectorAll(".spendenkarte")].some((k2) =>
+ (soll) => [...document.querySelectorAll('.spendenkarte[data-da="ja"]')].some((k2) =>
k2.querySelector(".spendenkarte__wer")?.textContent === "Probe"
&& Math.abs(parseFloat(getComputedStyle(k2).fontSize) - soll) < 0.5),
erwartet, { timeout: 16000 },
@@ -798,7 +964,12 @@ const messeKarte = async (erwartet) => {
die Irre gefuehrt. */
await host.seite.waitForTimeout(700);
return host.seite.evaluate(() => {
- const k2 = [...document.querySelectorAll(".spendenkarte")]
+ /* `data-da="ja"` HEISST: GANZ DA. Beim Hereinkommen UND beim
+ Verschwinden steht die Karte auf `translateX(-28px) scale(.96)`
+ -- gemessen wurden dadurch 413 statt 430 Pixel (genau das
+ 0,96-fache) und ein linker Rand von -1. Beides war kein
+ Fehler der Karte, sondern ein Blick zur falschen Zeit. */
+ const k2 = [...document.querySelectorAll('.spendenkarte[data-da="ja"]')]
.find((x) => x.querySelector(".spendenkarte__wer")?.textContent === "Probe");
if (!k2) return null;
const r = k2.getBoundingClientRect();
@@ -886,7 +1057,7 @@ if (!kGross || !kKlein) {
/* ---- Und die Farbe? ---- */
const farbe = await host.seite.evaluate(() => {
- const k2 = [...document.querySelectorAll(".spendenkarte")]
+ const k2 = [...document.querySelectorAll('.spendenkarte[data-da="ja"]')]
.find((x) => x.querySelector(".spendenkarte__wer")?.textContent === "Probe");
return k2 ? getComputedStyle(k2).getPropertyValue("--sp-farbe").trim() : "";
});
@@ -1396,6 +1567,65 @@ await gast2.kontext.close();
await zuschauer.kontext.close();
{
+ /* ==== DIE REGIELEISTE AM HANDY ==================================
+ Sieben Register auf 390 Pixel passen nicht nebeneinander -- sie
+ muss rollen. Gemessen wird beides: dass sie rollt, und dass sie
+ trotzdem nur EINE Zeile hoch ist. Umbrechen waere die stille
+ Alternative, und die kostet das Video 44 Pixel. */
+ {
+ const chef = await alsWer("admin", 390, 844);
+ await chef.seite.goto(`${BASIS}/workspace/reaktion.html`, { waitUntil: "domcontentloaded" });
+ await chef.seite.waitForTimeout(2600);
+ const l = await chef.seite.evaluate(() => {
+ const r = document.getElementById("reiter");
+ const le = document.getElementById("regieleiste");
+ if (!r || le.hidden) return null;
+ const k = [...r.querySelectorAll(".reiter__knopf")];
+ const rr = r.getBoundingClientRect();
+ return {
+ hoch: Math.round(le.getBoundingClientRect().height),
+ zeilen: new Set(k.map((x) => Math.round(x.getBoundingClientRect().top))).size,
+ rollt: r.scrollWidth > r.clientWidth + 1,
+ sichtbar: k.filter((x) => {
+ const q = x.getBoundingClientRect();
+ return q.left >= rr.left - 1 && q.right <= rr.right + 1;
+ }).length,
+ schild: !!document.querySelector(".regieleiste__schild")?.offsetParent,
+ ueber: Math.max(0, document.documentElement.scrollWidth
+ - document.documentElement.clientWidth),
+ };
+ });
+ if (!l) {
+ console.log(" ACHTUNG: Am Handy gibt es die Regieleiste nicht.");
+ ausgang = 1;
+ } else {
+ console.log(` Handy-Regie: ${l.hoch} px, ${l.zeilen} Zeile(n), rollt=${l.rollt}, `
+ + `${l.sichtbar} von 7 im Bild, Schild=${l.schild}, seitlich ${l.ueber} px`);
+ if (l.zeilen !== 1 || !l.rollt || l.ueber > 0 || l.schild) {
+ console.log(" ACHTUNG: Die Regieleiste am Handy bricht um, rollt nicht "
+ + "oder schiebt die Seite breiter.");
+ ausgang = 1;
+ }
+ }
+ /* WAS BLEIBT VOM BILD, WENN DIE REGIE OFFEN IST?
+ Am Handy ist das die eigentliche Frage. Ein Kinosaal von 131
+ Pixeln ist kein Bild mehr, sondern ein Streifen -- und man
+ merkt es erst, wenn man mitten in der Sendung etwas einstellen
+ will und nicht mehr sieht, worueber man redet. */
+ const platzHandy = await chef.seite.evaluate(() => {
+ const g = (id) => Math.round(document.getElementById(id).getBoundingClientRect().height);
+ return { saal: g("saal"), leiste: g("regieleiste"), kino: g("teil-live"), pult: g("pult") };
+ });
+ console.log(` Handy offen: Saal ${platzHandy.saal} = Leiste ${platzHandy.leiste} `
+ + `+ Kino ${platzHandy.kino} + Regie ${platzHandy.pult}`);
+ if (platzHandy.kino < 190) {
+ console.log(" ACHTUNG: Bei offener Regie bleibt vom Bild fast nichts.");
+ ausgang = 1;
+ }
+ await bild(chef.seite, "21-regieleiste-handy");
+ await chef.kontext.close();
+ }
+
const handy = await alsWer("gast2", 390, 844);
await handy.seite.goto(`${BASIS}/workspace/reaktion.html`, { waitUntil: "networkidle" });
await handy.seite.waitForTimeout(4000);
diff --git a/server/pruef-reaktion.mjs b/server/pruef-reaktion.mjs
index 7d958a58..89a89176 100644
--- a/server/pruef-reaktion.mjs
+++ b/server/pruef-reaktion.mjs
@@ -1552,6 +1552,106 @@ melde("=== 17. Tempo, Anordnung und das zweite Video ===");
}
}
+
+/* =======================================================================
+ 18. DIE REGIELEISTE UEBER DEM SAAL
+
+ Filipe, 28.09.2026: „ich haette das gerne oben also nicht in dieser
+ kachel sondern ueber der kachel wo die videos laufen werden."
+
+ Geprueft wird hier, was sich aus dem Quelltext beweisen laesst --
+ Lage, Verdrahtung, Versprechen. Wie hoch sie ist und ob die Marke
+ sitzt, misst `mess-reaktion` im Browser; das kann kein Quelltext.
+ ======================================================================= */
+melde("");
+melde("=== 18. Die Regieleiste ===");
+{
+ const html = readFileSync(join(HIER, "..", "workspace", "reaktion.html"), "utf8");
+ const css = readFileSync(join(HIER, "..", "workspace", "assets", "css", "reaktion.css"), "utf8");
+ const js = readFileSync(join(HIER, "..", "workspace", "assets", "js", "reaktion.js"), "utf8");
+
+ /* ---- SIE STEHT UEBER DEM SAAL, NICHT DARIN ----
+ Die Reihenfolge im Dokument ist die Aussage. Waere sie wieder im
+ Pult, stuende sie weiter unten -- und niemandem fiele es auf,
+ solange sie „irgendwo" da ist. */
+ const iLeiste = html.indexOf('id="regieleiste"');
+ const iKino = html.indexOf('id="teil-live"');
+ const iPult = html.indexOf('id="pult"');
+ ok(iLeiste > 0, "es gibt die Regieleiste");
+ ok(iLeiste < iKino, `sie steht vor dem Kino (${iLeiste} < ${iKino})`);
+ ok(iLeiste < iPult, "und vor dem Pult");
+ ok(html.indexOf('id="reiter"') < iPult,
+ "die Register stehen nicht mehr im Pult");
+
+ /* ---- JEDES REGISTER ZEIGT AUF EINE TAFEL, DIE ES GIBT ----
+ `aria-controls` ist ein Versprechen an Hilfsmittel. Zeigt es ins
+ Leere, sagt der Vorleser eine Tafel an, die niemand oeffnen
+ kann -- und gemerkt haette es nie jemand. */
+ const reiter = [...html.matchAll(/data-tafel="([a-z]+)"[^>]*aria-controls="([^"]+)"/g)]
+ .concat([...html.matchAll(/aria-controls="([^"]+)"[^>]*data-tafel="([a-z]+)"/g)]
+ .map((m) => [m[0], m[2], m[1]]));
+ ok(reiter.length >= 7, `${reiter.length} Register gefunden`);
+ const ohneTafel = reiter.filter(([, , ziel]) => !html.includes(`id="${ziel}"`));
+ ok(ohneTafel.length === 0,
+ ohneTafel.length
+ ? `ZEIGT INS LEERE: ${ohneTafel.map((x) => x[2]).join(", ")}`
+ : "jedes Register zeigt auf eine Tafel, die es gibt");
+ ok(!html.includes('id="tafel-gibtesnicht"'),
+ "Gegenprobe: eine erfundene Tafel gibt es nicht");
+
+ /* ---- DAS VERSPRECHEN VON `role=tablist` ----
+ Steht die Rolle im Markup und loest das Programm sie nicht ein,
+ sagt ein Vorleser etwas an, was nicht stimmt. Das ist schlimmer
+ als gar keine Rolle. */
+ ok(/id="reiter"[^>]*role="tablist"/.test(html), "die Leiste ist eine Registerleiste");
+ /* Die Anfuehrungszeichen bleiben draussen -- im Quelltext steht
+ `'Home'`, in dieser Pruefung stand zuerst `"Home"`, und daran
+ ist sie gescheitert. Eine Pruefung, die die Schreibweise der
+ Anfuehrungszeichen mitprueft, prueft die falsche Sache. */
+ for (const taste of ["ArrowRight", "ArrowLeft", "Home", "End"]) {
+ ok(js.includes(taste), `das Programm kennt „${taste}"`);
+ }
+ ok(/tabIndex\s*=\s*gewaehlt \? 0 : -1/.test(js),
+ "genau ein Register liegt in der Tabulatorfolge");
+
+ /* ---- EIN REGISTER MACHT DIE ZUGEKLAPPTE REGIE AUF ----
+ Seit die Leiste oben steht, kann das Pult zu sein. Ohne diese
+ Zeile waere jedes Register bei zugeklappter Regie ein Knopf,
+ bei dem sichtbar nichts passiert. */
+ ok(/pult\.dataset\.auf = 'ja'/.test(js) && /reiterWechseln/.test(js),
+ "ein Register klappt die Regie auf");
+
+ /* ---- DIE MARKE UND IHRE ZWEITE ANZEIGE ----
+ Was gerechnet wird, braucht etwas daneben, das nicht rechnet. */
+ ok(html.includes('id="reiter-marke"'), "es gibt die gleitende Marke");
+ ok(/offsetLeft/.test(js) && /offsetWidth/.test(js) && /offsetHeight/.test(js),
+ "sie wird aus gemessenen Werten gesetzt, nicht gerechnet");
+ const knopfRegel = css.match(/\.reiter__knopf\[aria-selected="true"\] \{[^}]*\}/);
+ ok(!!knopfRegel && /color:/.test(knopfRegel[0]),
+ "das gewaehlte Register traegt ausserdem eine eigene Farbe");
+
+ /* ---- SIE GEHOERT NICHT IN DEN STREAM ----
+ Der Buehnenmodus zeigt nur, was Zuschauer sehen sollen. Eine
+ Regieleiste im Stream waere die Bedienung im Bild. */
+ ok(/body\[data-nur="buehne"\][^{]*\.regieleiste/.test(css.replace(/\s+/g, " ")),
+ "im Buehnenmodus ist sie weg");
+
+ /* ---- NUR EINE STELLE SAGT, WIE HOCH DIE ZEILEN SIND ----
+ Bis zum 28.09. standen zwei Angaben in derselben Datei; die
+ spaetere gewann und gab der Leiste null Pixel. Zwei Regeln fuer
+ dieselbe Frage sind keine Sicherheit. */
+ const zeilen = [...css.matchAll(/^\s*\.saal[^{]*\{[^}]*grid-template-rows/gm)]
+ .concat([...css.matchAll(/^\.saal \{ grid-template-rows/gm)]);
+ ok(zeilen.length === 1,
+ zeilen.length === 1
+ ? "genau eine Regel bestimmt die Zeilen des Saals"
+ : `${zeilen.length} Regeln bestimmen die Zeilen -- eine davon wird ignoriert`);
+
+ /* ---- UND SIE GEHOERT NUR DENEN, DIE SENDEN ---- */
+ ok(/leiste\.hidden = !lage\.ich\.host/.test(js),
+ "nur wer die Sendung fuehrt, sieht sie");
+}
+
melde("");
melde(`${geprueft} Pruefungen, ${fehler} Fehler`);
melde(fehler === 0 ? "ALLES IN ORDNUNG" : "NICHT IN ORDNUNG");
diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html
index 23f4c42f..f5a5d74f 100644
--- a/workspace/anruf-probe.html
+++ b/workspace/anruf-probe.html
@@ -16,8 +16,8 @@
verdeckt. Gefunden hat es pruef-struktur.
Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. -->
-
-
+
+
-
+