Kamerafenster: Groesse und Ecke lassen sich stellen

Filipe: "und danach perfektionierst du auch die verschiedenen
groessen von kamera und so, perfektionier das alles bitte."

Sechs Groessen (0,6x bis 2x) und vier Ecken, beide an der SENDUNG
und nicht am Browser: Was Filipe einstellt, sehen alle. Waere es
eine Einstellung je Geraet, redete er ueber ein Bild, das bei den
Zusehenden anders aussieht -- und im Stream stuende ein drittes.

Anordnung, Groesse und Ecke gehen jetzt EINEN Weg (/layout), weil
sie zusammen eine einzige Frage beantworten: Wie sieht das Bild aus?
Drei getrennte Aufrufe waeren drei Rundrufe, und dazwischen saehen
die Zusehenden eine Mischung -- neue Anordnung, alte Ecke. Eine
falsche Angabe laesst auch das Gute stehen, statt halb umzustellen.

Die Ecke gibt es, weil unten rechts bei TikTok die Knopfreihe liegt,
bei YouTube die Fortschrittsleiste, und Untertitel fast immer unten
stehen. Eine feste Ecke ist eine, die bei jeder zweiten Plattform
im Weg ist.

Die OBS-Videoquelle kennt jetzt die Anordnung und blendet sich bei
"Nur Kamera" aus -- sonst laege im Stream ein stehengebliebenes
YouTube-Bild unter den Kameras, und Filipe saehe es nicht, weil er
auf seine Szene schaut und nicht auf die Quelle.

VIER BEFUNDE, ALLE VON DEN PRUEFUNGEN UND KEINER VOM AUGE:

1. Die Knoepfe fuer Groesse und Ecke gab es gar nicht. HTML und
   Stilblatt waren da, das Programm nicht. Gemessen: "0 von 0
   Eckknoepfen gesperrt", und der Hinweistext stand noch wortgleich
   wie im HTML -- zwei leere Kaesten, die aussahen, als sei alles
   in Ordnung.

2. Bei "Nur Kamera" war der Stapel 493 px breit statt 1072. Der
   neue Deckel max-width: 46% -- richtig gegen ein zu grosses
   Fenster bei "Video gross" -- galt still auch dort, wo die
   Kameras die ganze Flaeche fuellen sollen.

3. Am Handy haette die Groesseneinstellung ueberhaupt nichts
   bewirkt: Dort stand weiterhin width: clamp(88px, 26vw, 140px)
   am Fenster selbst. Das ist die dritte Wiederholung derselben
   Sache an einem Tag (die Saalzeilen, die Tafeln, jetzt die
   Kamerabreite): Zwei Regeln fuer dieselbe Frage sind die
   Garantie, dass eine davon irgendwo falsch gewinnt. Breite und
   Rand gehen jetzt ueber --kam-grund/--kam-rand, --kam wird an
   genau EINER Stelle gerechnet, und eine Pruefung zaehlt das nach.

4. Die Pruefung schlug auf ihren EIGENEN Kommentar an, der die
   entfernte Zeile zitiert. Ein Werkzeug, das bei jedem Lauf
   meckert, wird nach dem zweiten Mal weggeklickt -- samt dem
   echten Befund darin. Gezaehlt werden jetzt Regeln, nicht Prosa.

Gemessen beim Zuschauer und nicht in der Datenbank: 1x -> 208 px,
2x -> 416 px, alle Fenster innerhalb der Leinwand, und jede der
vier Ecken am Abstand zu den Kanten nachgewiesen statt an ihrem
eigenen Namen. Ein Knopf, der gerade nichts bewirken kann, ist
grau, und der Satz darunter sagt warum.

pruef-reaktion 320/0 - pruef-buehne 36/0 - pruef-css-klassen ok -
pruef-tippziele 11/0 - mess-reaktion ohne Beanstandung

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-28 14:35:02 +02:00
co-authored by Claude Opus 5
parent 2b02d88767
commit 3fedeea716
55 changed files with 1537 additions and 693 deletions
+167
View File
@@ -685,6 +685,173 @@ 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. */
/* ---- Groesse und Ecke der Kamerafenster -------------------------
Filipe: „und danach perfektionierst du auch die verschiedenen
groessen von kamera und so."
GEMESSEN WIRD BEIM ZUSCHAUER. Dass in der Datenbank 1,5 steht,
sagt nichts darueber, ob das Fenster bei den anderen groesser
wird -- dazwischen liegen ein Rundruf, eine Variable im Stilblatt
und eine Rechnung mit `clamp`. */
console.log("");
console.log(" --- Kameragroesse und Ecke ---");
{
/* Sauberer Anfang: „Video gross", damit die Ecke ueberhaupt gilt. */
await roh("/workspace/api/reaktion/layout", {
method: "POST", keks: k.admin, rumpf: { layout: "kino" },
});
await zuschauer.seite.waitForTimeout(900);
const messen = () => zuschauer.seite.evaluate(() => {
const kino = document.getElementById("teil-live");
const kk = document.querySelector(".kameras");
const ein = document.querySelector(".kamera");
const lein = document.querySelector(".leinwand");
if (!kk || !ein || !lein) return null;
const r = ein.getBoundingClientRect();
const lr = lein.getBoundingClientRect();
const fenster = [...document.querySelectorAll(".kamera")];
return {
gross: kino.style.getPropertyValue("--kam-gross") || "(?)",
ecke: kino.dataset.ecke || "(?)",
breit: Math.round(r.width),
/* Wo sitzt der Stapel? Als Abstand zu den vier Kanten der
Leinwand -- daran erkennt man die Ecke ohne Vorwissen. */
links: Math.round(kk.getBoundingClientRect().left - lr.left),
rechts: Math.round(lr.right - kk.getBoundingClientRect().right),
oben: Math.round(kk.getBoundingClientRect().top - lr.top),
unten: Math.round(lr.bottom - kk.getBoundingClientRect().bottom),
/* Und liegt noch alles IN der Leinwand? Sie hat
`overflow: hidden` -- was daneben liegt, ist unsichtbar,
nicht abgeschnitten sichtbar. */
drin: fenster.filter((f) => {
const q = f.getBoundingClientRect();
return q.left >= lr.left - 1 && q.right <= lr.right + 1
&& q.top >= lr.top - 1 && q.bottom <= lr.bottom + 1;
}).length,
fenster: fenster.length,
leinwand: Math.round(lr.width),
};
});
const normal = await messen();
console.log(` 1×: Fenster ${normal?.breit} px, Ecke ${normal?.ecke}, `
+ `Abstände l/r/o/u ${normal?.links}/${normal?.rechts}/${normal?.oben}/${normal?.unten}, `
+ `${normal?.drin}/${normal?.fenster} im Bild`);
/* ---- DIE GROESSE MUSS WIRKEN ---- */
const { KAMERA_GROESSEN } = await import("./reaktion-tabellen.js");
const gross = KAMERA_GROESSEN[KAMERA_GROESSEN.length - 1];
await roh("/workspace/api/reaktion/layout", {
method: "POST", keks: k.admin, rumpf: { kamera_gross: gross },
});
await zuschauer.seite.waitForTimeout(900);
const dick = await messen();
console.log(` ${gross}×: Fenster ${dick?.breit} px, `
+ `${dick?.drin}/${dick?.fenster} im Bild (Leinwand ${dick?.leinwand} px)`);
if (!normal || !dick) {
console.log(" ACHTUNG: Die Kamerafenster liessen sich nicht messen.");
ausgang = 1;
} else {
/* NICHT AUF DEN FAKTOR GENAU. `clamp` deckelt die Grundbreite,
und `max-width: 46%` deckelt den Stapel -- beides ist Absicht.
Geprueft wird, dass es SPUERBAR waechst und im Bild bleibt. */
const wuchs = dick.breit / normal.breit;
console.log(` Das Fenster wuchs um das ${wuchs.toFixed(2)}-fache`);
if (wuchs < 1.3) {
console.log(" ACHTUNG: Die Groesse wirkt nicht.");
ausgang = 1;
}
if (dick.drin !== dick.fenster) {
console.log(" ACHTUNG: Ein Fenster liegt ausserhalb der Leinwand.");
ausgang = 1;
}
if (dick.breit > dick.leinwand * 0.5) {
console.log(" ACHTUNG: Ein Fenster deckt mehr als die halbe Leinwand zu.");
ausgang = 1;
}
}
/* ---- DIE VIER ECKEN ----
Geprueft wird der ABSTAND zu den Kanten, nicht der Name der
Ecke. Eine Einstellung, die nur ihren eigenen Namen bestaetigt,
bestaetigt nichts. */
await roh("/workspace/api/reaktion/layout", {
method: "POST", keks: k.admin, rumpf: { kamera_gross: 1 },
});
const erwartet = {
ol: ["links", "oben"], or: ["rechts", "oben"],
ul: ["links", "unten"], ur: ["rechts", "unten"],
};
for (const [ecke, [waag, senk]] of Object.entries(erwartet)) {
await roh("/workspace/api/reaktion/layout", {
method: "POST", keks: k.admin, rumpf: { kamera_ecke: ecke },
});
await zuschauer.seite.waitForTimeout(800);
const m = await messen();
const nahWaag = m[waag] < (waag === "links" ? m.rechts : m.links);
const nahSenk = m[senk] < (senk === "oben" ? m.unten : m.oben);
console.log(` Ecke ${ecke}: l/r/o/u ${m.links}/${m.rechts}/${m.oben}/${m.unten} `
+ `-> ${waag}+${senk} ${nahWaag && nahSenk ? "stimmt" : "STIMMT NICHT"}`);
if (!nahWaag || !nahSenk || m.ecke !== ecke) {
console.log(` ACHTUNG: Die Ecke „${ecke}" kommt nicht an.`);
ausgang = 1;
}
}
await bild(zuschauer.seite, "22-kamera-ecke");
/* ---- EIN KNOPF, DER NICHTS TUN KANN, IST GRAU ----
Die Ecke wirkt nur bei „Video gross"; die Groesse wirkt ueberall,
wo es Kamerafenster GIBT -- bei „Nur Video" gibt es keine.
GEMESSEN WIRD DER ZUSTAND DER KNOEPFE, nicht der Satz darunter.
Ein Hinweis, der die Wahrheit sagt, waehrend der Knopf daneben
bedienbar bleibt, ist die schlechtere Haelfte: Man liest ihn
nicht und drueckt trotzdem. */
const knoepfe = () => host.seite.evaluate(() => {
const e = [...document.querySelectorAll("#kamecke .kamecke__knopf")];
const g = [...document.querySelectorAll("#kamgroesse .kamgroesse__knopf")];
return {
ecken: e.length, eckenGrau: e.filter((x) => x.disabled).length,
groessen: g.length, groessenGrau: g.filter((x) => x.disabled).length,
hinweis: document.getElementById("kamecke-hinweis")?.textContent || "",
};
});
for (const [anordnung, sollEcken, sollGroessen, wort] of [
["kamera", "alle", "keine", "Mitte"],
["nur_video", "alle", "alle", "Nur Video"],
["kino", "keine", "keine", "Ecke"],
]) {
await roh("/workspace/api/reaktion/layout", {
method: "POST", keks: k.admin, rumpf: { layout: anordnung },
});
await host.seite.waitForTimeout(800);
const m = await knoepfe();
console.log(` „${anordnung}": ${m.eckenGrau}/${m.ecken} Ecken grau, `
+ `${m.groessenGrau}/${m.groessen} Größen grau`);
console.log(` Hinweis: „${m.hinweis.replace(/\s+/g, " ").trim()}"`);
const soll = (art, wieviel, da) => (wieviel === "alle" ? art === da : art === 0);
if (m.ecken !== 4 || m.groessen < 4) {
console.log(" ACHTUNG: Die Knoepfe fuer Groesse oder Ecke fehlen.");
ausgang = 1;
} else if (!soll(m.eckenGrau, sollEcken, m.ecken)
|| !soll(m.groessenGrau, sollGroessen, m.groessen)) {
console.log(` ACHTUNG: Bei „${anordnung}" stimmt das Grauwerden nicht.`);
ausgang = 1;
} else if (!m.hinweis.includes(wort)) {
console.log(` ACHTUNG: Der Hinweis sagt nichts von „${wort}".`);
ausgang = 1;
}
}
await roh("/workspace/api/reaktion/layout", {
method: "POST", keks: k.admin, rumpf: { layout: "kino", kamera_ecke: "ur" },
});
await host.seite.waitForTimeout(700);
}
/* ---- Die Regieleiste ueber dem Saal -----------------------------
Filipe, 28.09.2026: „ich haette das gerne oben also nicht in dieser