Die Regieleiste steht jetzt über dem Saal

Filipe: „ich hätte das gerne oben also nicht in dieser kachel sondern
über der kachel wo die videos laufen werden und das soll extrem
perfektioniert werden."

WARUM DAS MEHR IST ALS EIN UMHÄNGEN
Die Register standen INNERHALB des einklappbaren Teils. Wer die Regie
zuklappte, um mehr Bild zu haben, hatte sie nicht mehr — und genau
dann braucht man sie. Oben ändert sich ihre Aufgabe: aus dem
Inhaltsverzeichnis einer Schublade wird eine Steuerleiste über der
Sendung. Daraus folgen vier Dinge, die vorher nicht nötig waren:

1. EIN REGISTER MACHT DIE ZUGEKLAPPTE REGIE AUF. Vorher wechselte nur
   der Reiter, die Tafel lag im zugeklappten Teil — sichtbar passierte
   nichts. Nur auf, nie zu: Zugemacht wird mit dem Griff. Ein Knopf,
   der beim zweiten Druck das Gegenteil tut, versteckt irgendwann
   etwas, das jemand gerade liest.

2. SIE BRICHT NICHT UM, SIE ROLLT. Sieben Register in zwei Zeilen
   kosten am oberen Rand rund 90 px — und zwar dem Video. Gemessen:
   eine Zeile auf 1440 px wie auf 390 px, dort mit Einrasten und
   weichen Rändern als Auskunft, dass es weitergeht.

3. SIE LÖST EIN, WAS `role="tablist"` VERSPRICHT. Pfeiltasten wandern,
   Pos1/Ende springen, genau ein Register liegt in der Tabulatorfolge.
   Steht die Rolle im Markup und tut das Programm es nicht, sagt ein
   Vorleser etwas an, was nicht stimmt — schlimmer als keine Rolle.
   Gemessen wird das gedrückt, nicht gelesen: fünf Tastendrücke, und
   Fokus, Auswahl und offene Tafel müssen jedes Mal dasselbe sagen.

4. EINE GLEITENDE MARKE zeigt, wo man steht — Glas mit Lichtkante,
   darunter ein roter Streifen zur offenen Tafel. Ist die Regie
   zugeklappt, wird er leise: Der Streifen führt dann nirgendwohin.
   Dazu ein Schild „Regie" links (am Handy weg), damit die sieben
   Wörter am oberen Rand nicht wie eine zweite Navigation aussehen.

DER FEHLER, DER MICH ZWEI ANLÄUFE GEKOSTET HAT
Die Leiste war im Browser 1 px hoch, ihre Register standen 55 px hoch
daneben und ragten über das Video. Ursache: 900 Zeilen weiter unten
stand eine ZWEITE `grid-template-rows` für `.saal` (aus der Zeit, als
der Saal zwei Zeilen hatte). Sie stand später und gewann — die Leiste
landete in der `1fr`-Zeile und bekam null Pixel.

Und fast hätte ich das Falsche repariert: Beim Durchprobieren habe ich
`style.gridTemplateRows` gesetzt — ein Stilattribut schlägt jede Regel
im Stilblatt. Damit „funktionierten" `auto` und `min-content` gleich
gut, weil beide die zweite Regel aushebelten, und es sah nach einem
Unterschied zwischen den beiden aus. Erst nach dem Entfernen der
Doppelung hat die Gegenprobe gezeigt: `auto` tut es genauso. Eine
Probe, die mehr ändert als das, wonach man sucht, beantwortet eine
andere Frage. Es gibt jetzt eine Prüfung, dass genau EINE Regel die
Zeilen des Saals bestimmt.

ZWEI WEITERE FUNDE AUS DER MESSUNG
Am Handy blieben bei offener Regie 131 px für das Bild — ein Streifen.
Mein erster Versuch (62dvh → 52dvh) machte es auf 75 px SCHLECHTER:
Die Ursache lag woanders, die Leiste des Pults bricht dort in vier
Zeilen um und ist rund 200 px hoch, wovon ein Anteil der Fensterhöhe
nichts wissen kann. Mit `min(52dvh, 19rem)` sind es 210 px.

Und die Spendenkarte wurde als „steht aus dem Bild heraus" gemeldet:
413 statt 430 px, links −1. 413 ist genau das 0,96-fache — die Messung
hatte die Karte im Ausblenden erwischt. Sie wartet jetzt auf
`data-da="ja"` und misst nur, was ganz da ist.

GEPRUEFT
pruef-reaktion 280 (vorher 260), 0 Fehler — Abschnitt 18 neu.
mess-reaktion: Rückgabewert 0, kein ACHTUNG; misst die Leiste jetzt
auf 1440 und auf 390 px, samt Tastatur und Aufklappen.
Dazu grün: handy, buehne, struktur, css-klassen, tippziele.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-28 14:13:07 +02:00
co-authored by Claude Opus 5
parent d2f02ba84e
commit 2b02d88767
49 changed files with 1917 additions and 1255 deletions
+233 -3
View File
@@ -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);
+100
View File
@@ -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");