Am Handy ist die Transportleiste wieder erreichbar

Der offene Punkt von gestern Nacht, jetzt geloest -- und dabei stellte
sich heraus, dass er schlimmer war als gemeldet.

WAS WIRKLICH LOS WAR

Gemeldet war: "Am Handy bleibt bei offener Regie vom Bild nichts."
Gemessen auf 390x844 ergab sich mehr:

    Saal 69..844 (775 px)
    Zeilen  101 + 0 + 511 + 325 = 937
    transport 681..1006  -- 162 Pixel UNTER dem Fensterrand

Der Koerper hat `overflow: hidden`; die Leiste war also nicht
abgeschnitten, sondern weg. Play, "Naechstes" und die Sprungknoepfe:
bei offener Regie nicht erreichbar. Das Bild hatte dabei null Pixel.

DIE RECHNUNG, DIE ES ERKLAERT

    Regieleiste   101  (zwei Zeilen Register)
    Regie-Kopf    207  (fuenf Zeilen zu je rund 44 px -- der
                        Klapp-Knopf stand allein in einer)
    Regie-Inhalt  305
    Transport     325  (Schiene 48 + drei Gruppen untereinander)
                 ----
                  938  von 775.

Video, Regie und Transport passen auf einem Handy nicht nebeneinander.
Das ist keine Frage der Gestaltung, sondern des Platzes.

VIER SCHNITTE UND EINE ENTSCHEIDUNG

  1. Die Tempo-Gruppe klappt hinter ihren eigenen Wert -- dasselbe
     Muster wie "Aa" im Chat, das dort 73 Pixel gespart hat. Der
     Knopf zeigt "1x" oder "1,5x", man muss zum Nachsehen also nicht
     aufklappen. Am Rechner gibt es ihn nicht.
  2. Der Klapp-Knopf rutscht neben die Lampe statt in eine eigene
     Zeile.
  3. Die drei Gruppen der Transportreihe stehen nebeneinander (158
     statt 240 Pixel) -- moeglich, seit die Tempo-Gruppe klappt.
  4. Die Messwerte stehen in einer Zeile, die grossen Knoepfe
     bekommen weniger Polsterung.

Das reichte nicht. Also die Entscheidung: AM HANDY LEGT SICH DIE
REGIE UEBER DAS BILD, statt ihm Platz wegzunehmen -- als Rasterfeld
ueber die Zeilen 2 und 3, unten angedockt, hoechstens 72 Prozent
hoch. Oben bleibt ein Streifen Bild stehen (gemessen 135 px): Wer
mitten in der Sendung etwas einstellt, muss sehen, worueber er redet.

Gemessen jetzt: Saal 775 = Leiste 101 + Bild 481 + Regie 346, und
der Transport steht bei 601..844 -- erreichbar.

DREI VERSUCHE, DIE ES NICHT WURDEN (und warum)

  - `position: absolute` mit gemessener Transporthoehe: lief, lag
    aber 17 Pixel ueber den Registern. Der Bezugsrahmen eines
    absolut gesetzten RASTERFELDES ist sein Rasterbereich, nicht der
    Container -- mit `grid-row: 3` (0 Pixel hoch) wurde aus
    `max-height: 100%` eine Hoehe von einem Pixel.
  - `top` UND `bottom` UND `height: fit-content`: Der Browser
    verwirft dann das `bottom`; die Regie ragte 101 Pixel in die
    Leiste.
  - `grid-row: 2 / 4` mit `align-self: end` statt absolut: ragte 146
    Pixel nach oben und schob die Seite 198 Pixel breiter.

AUF 320 PIXELN BLEIBT ES BEIM ALTEN

Dort passen Register (104), Regie-Kopf (210) und Transportleiste
(158) zusammen nicht in die 499 Pixel Saal -- es fehlten 17. Fuenf
Verteilungen haben nur bestimmt, WER verdeckt wird: erst
"Vorbereiten" und "Beenden" unter der Leiste, dann die Schublade
ueber "Gaeste" und "Bild". Die Schublade greift deshalb erst ab 360
Pixeln; darunter bleibt der Stand von vorher -- ein bekannter Mangel,
aber kein neuer. Der Grund steht im Stilblatt.

NEBENBEI GEFUNDEN

  - `.muenzsatz` brach nicht um und ragte auf 320 px 8 Pixel hinaus
    -- die Tafel rollte dort wieder waagerecht.

WACHEN GESCHAERFT

  - Die Klammerwache von gestern hat heute ihren ersten echten Fund
    gemacht: Beim Herausschneiden einer Regel blieb eine `}` stehen.
    Ohne sie waere das als drei Befunde in `mess-reaktion`
    aufgetaucht, die wie Programmfehler ausgesehen haetten.
  - Der Namensstreit-Waechter meldete `pult (grid vs. flex)` und
    `messwerte__paar (grid vs. flex)` -- beides Fehlalarm: Eine
    Regel in einem Zusammenhang (`.saal .pult`) und eine in einer
    Medienabfrage sind Varianten, kein Streit. Er nimmt beides jetzt
    aus. Dabei fiel auf, dass sein Muster die schliessende Klammer
    verbrauchte, die der naechste Treffer als Anfang braucht -- der
    echte Fall vom 28.09. (`.stufen` zweimal) wurde dadurch gar
    nicht gefunden. Die Gegenprobe deckt jetzt fuenf Faelle ab.
  - `mess-reaktion` mass die HOEHE der Kinozeile und haette 431
    Pixel gemeldet, waehrend das Bild vollstaendig verdeckt war.
    Sie misst jetzt, wieviel oberhalb der Schublade uebrig bleibt.

GEPRUEFT
  pruef-reaktion, -css-klassen, -tippziele, -handy, -struktur,
  -buehne: alle EXIT 0
  mess-reaktion  EXIT 0, kein ACHTUNG, kein offener Punkt
  mess-quer      EXIT 0 -- fuenf Groessen, nichts rollt seitlich
  mess-buehne    EXIT 0
This commit is contained in:
2026-09-29 12:56:03 +02:00
parent a9e0834cfb
commit e272dd4673
50 changed files with 1044 additions and 689 deletions
+11
View File
@@ -310,6 +310,7 @@ const GERAETE = [
["Handy", 412, 915],
["iPhone", 390, 844],
["kleines Handy", 360, 640],
["kleinstes Handy", 320, 568],
];
let befunde = 0;
@@ -374,6 +375,16 @@ for (const [gname, breite, hoehe] of GERAETE) {
const register = await host.seite.evaluate(() =>
[...document.querySelectorAll(".reiter__knopf")].map((b) => b.dataset.tafel));
console.log(` Register: ${register.join(" · ")}`);
{
const z = await host.seite.evaluate(() => {
const saal = document.getElementById("saal");
const teile = [...saal.children].filter((e) => e.getBoundingClientRect().height > 0)
.map((e) => { const q = e.getBoundingClientRect();
return `${e.id || e.className.split(" ")[0]} ${Math.round(q.top)}..${Math.round(q.bottom)}`; });
return `Zeilen ${getComputedStyle(saal).gridTemplateRows} | ${teile.join(" / ")}`;
});
console.log(` ${z}`);
}
/* Erst die Seite als Ganzes. */
const seitlich = await host.seite.evaluate(() =>
+43 -3
View File
@@ -2456,15 +2456,55 @@ await zuschauer.kontext.close();
const alle = [...document.getElementById("saal").children]
.filter((e) => e.getBoundingClientRect().height > 0)
.map((e) => `${e.id || e.className.split(" ")[0]}=${Math.round(e.getBoundingClientRect().height)}`);
/* WIEVIEL VOM BILD IST WIRKLICH ZU SEHEN?
Die Hoehe der Kinozeile sagt das nicht: Seit die Regie am
Handy eine Schublade darueber ist, kann die Zeile 431 Pixel
hoch sein und trotzdem vollstaendig verdeckt. Ein gruener
Haken ueber eine Zeilenhoehe waere genau die Sorte, die das
Falsche misst. Gefragt ist der Streifen, der oberhalb der
Schublade uebrig bleibt. */
const kino = document.getElementById("teil-live").getBoundingClientRect();
const regie = document.getElementById("pult").getBoundingClientRect();
const frei = Math.max(0, Math.round(Math.min(kino.bottom, regie.top) - kino.top));
return { saal: g("saal"), leiste: g("regieleiste"), kino: g("teil-live"), pult: g("pult"),
alle: alle.join(" + ") };
frei, alle: alle.join(" + ") };
});
console.log(` Handy offen: Saal ${platzHandy.saal} = Leiste ${platzHandy.leiste} `
+ `+ Kino ${platzHandy.kino} + Regie ${platzHandy.pult}`);
console.log(` Handy Zeilen: ${platzHandy.alle}`);
if (platzHandy.kino < 190) {
const spur = await chef.seite.evaluate(() => {
const saal = document.getElementById("saal");
const r = saal.getBoundingClientRect();
const teile = [...saal.children].filter((e) => e.getBoundingClientRect().height > 0)
.map((e) => {
const q = e.getBoundingClientRect();
return `${e.id || e.className.split(" ")[0]} ${Math.round(q.top)}..${Math.round(q.bottom)}`;
});
const tr = document.querySelector(".transport");
const teileT = [...(tr ? tr.children : [])].map((e) => {
const q = e.getBoundingClientRect();
return `${e.className.split(" ")[0]}=${Math.round(q.height)}`;
});
return `Saal ${Math.round(r.top)}..${Math.round(r.bottom)} (Fenster ${window.innerHeight})`
+ ` | Zeilen ${getComputedStyle(saal).gridTemplateRows}`
+ ` | ${teile.join(" / ")}`
+ ` | Transport: ${teileT.join(" + ")}`
+ ` | Regie-Kopf: ${(() => {
const l = document.querySelector(".pult__leiste");
if (!l) return "-";
const kinder = [...l.children].map((e) => {
const q = e.getBoundingClientRect();
return `${e.className.split(" ")[0] || e.tagName.toLowerCase()}`
+ ` ${Math.round(q.top)}..${Math.round(q.bottom)}`;
});
return `${Math.round(l.getBoundingClientRect().height)} px = ${kinder.join(" / ")}`;
})()}`;
});
console.log(` SPUR: ${spur}`);
console.log(` Vom Bild sichtbar (nicht von der Regie verdeckt): ${platzHandy.frei} px`);
if (platzHandy.frei < 120) {
offenePunkte.push(`Am Handy bleibt bei offener Regie vom Bild fast nichts `
+ `(${platzHandy.kino} px, noetig 190). Bekannt seit 28.09.2026. `
+ `(${platzHandy.frei} px sichtbar, noetig 120). Bekannt seit 28.09.2026. `
+ `Wurzel: die umgebrochene Transportleiste braucht auf 320 px `
+ `Breite 220 Pixel. Der Weg steht in reaktion.css beim Handy-Block.`);
}
+57 -17
View File
@@ -989,12 +989,39 @@ melde("=== 12. Die Regie ===");
Grundregeln, die beide sagen, WIE das Bauteil gebaut ist --
`grid` gegen `flex`, wie bei `.stufen`. */
const positiv = (wert) => wert && wert !== "none" && !/^none/.test(wert);
/* ZWEI EINSCHRAENKUNGEN MEHR, BEIDE AM 29.09.2026 NOETIG GEWORDEN.
(3) Der Selektor muss GENAU die Klasse sein. `.saal .pult {
display: flex }` ist keine zweite Grundregel zu `.pult {
display: grid }`, sondern dieselbe Klasse in einem anderen
Zusammenhang -- und genau dafuer sind Selektoren da. Die
erste Fassung nahm jede Klasse, der ein Trennzeichen
vorausging, und meldete deshalb `pult (grid vs. flex)`.
(4) Regeln INNERHALB einer Medienabfrage zaehlen nicht. Sie
sind per Definition eine Variante fuer eine andere
Umgebung -- `messwerte__paar` steht am Fingergeraet als
`flex` und sonst als `grid`, und das ist die Absicht.
Was uebrig bleibt, ist der Fall, um den es ging: `.stufen`
stand zweimal als blanke Grundregel, ohne Medienabfrage und
ohne Zusammenhang -- zwei verschiedene Bauteile, ein Name. */
const ohneMedien = (text) => text.replace(
/@media[^{]*\{(?:[^{}]|\{[^{}]*\})*\}/g,
(m) => m.replace(new RegExp("[^" + String.fromCharCode(10) + "]", "g"), " "));
const lies = (text) => {
const raus = {};
for (const m of text.matchAll(/(^|[\s};])(\.(-?[a-zA-Z_][a-zA-Z0-9_-]*))\s*\{([^{}]*)\}/g)) {
const wert = (m[4].match(/(?:^|[\s;])display\s*:\s*([^;]+)/) || [, ""])[1].trim();
/* LOOKBEHIND STATT VERBRAUCHEN. Mit `(^|[};])` frisst der erste
Treffer die schliessende Klammer, die der naechste als Anfang
braeuchte -- `.stufen { … } .stufen { … }` lieferte dann nur
EINEN Treffer, und die Gegenprobe zum echten Fall vom 28.09.
schlug fehl. */
for (const m of ohneMedien(text).matchAll(/(?:^|(?<=[};]))\s*(\.(-?[a-zA-Z_][a-zA-Z0-9_-]*))\s*\{([^{}]*)\}/g)) {
const wert = (m[3].match(/(?:^|[\s;])display\s*:\s*([^;]+)/) || [, ""])[1].trim();
if (!positiv(wert)) continue;
(raus[m[3]] ||= []).push(wert);
(raus[m[2]] ||= []).push(wert);
}
return raus;
};
@@ -1010,20 +1037,33 @@ melde("=== 12. Die Regie ===");
: `${Object.keys(grundregeln).length} Klassen sagen, WIE sie gebaut sind `
+ "-- keine davon zweimal verschieden");
/* GEGENPROBE: Findet die Regel einen eingeschobenen Streit? Ohne
diese Zeile waere oben nur bewiesen, dass sie „in Ordnung"
sagen kann. Eingeschoben wird genau der Fall vom 28.09.: eine
zweite Grundregel mit einem anderen positiven display. */
ok(streitIn(roh("reaktion.css") + " .pult-tafel { display: flex; } ")
.some((z) => z.startsWith("pult-tafel (")),
"Gegenprobe: eine zweite Grundregel mit anderem display faellt auf");
/* UND IN DIE ANDERE RICHTUNG: Ein Bauteil, das erst versteckt und
dann gezeigt wird, darf NICHT anschlagen -- sonst haette man
die Regel nur so lange geschaerft, bis sie alles meldet. */
ok(streitIn(" .probe { display: none; } .probe { display: grid; } ").length === 0
&& streitIn(" .probe { display: grid; } .probe { display: flex; } ").length === 1,
"Gegenprobe: verstecken-und-zeigen ist kein Streit, grid-gegen-flex schon");
/* GEGENPROBEN AN EIGENSTAENDIGEN TEXTEN, nicht an die echte
Datei angehaengt. Die erste Fassung tat das -- und scheiterte,
sobald der Sucher Medienabfragen ausklammert: Was hinter dem
letzten `@media` angehaengt wird, kann darin verschwinden.
Eine Gegenprobe, die vom Inhalt der geprueften Datei abhaengt,
prueft nicht die Regel, sondern die Datei. */
{
const faelle = [
["der Fall vom 28.09. (zwei Grundregeln, anderes display)",
".stufen { display: grid; } .stufen { display: flex; }", true],
["verstecken und zeigen",
".probe { display: none; } .probe { display: grid; }", false],
["dieselbe Klasse in einem Zusammenhang",
".pult { display: grid; } .saal .pult { display: flex; }", false],
["eine Variante in einer Medienabfrage",
".paar { display: grid; } @media (pointer: coarse) { .paar { display: flex; } }", false],
["zweimal dasselbe display",
".gleich { display: grid; } .gleich { display: grid; }", false],
];
const daneben = faelle
.filter(([, text, soll]) => (streitIn(text).length > 0) !== soll)
.map(([name]) => name);
ok(daneben.length === 0,
daneben.length ? `Gegenprobe daneben bei: ${daneben.join(", ")}`
: `Gegenprobe: alle ${faelle.length} Faelle richtig eingeordnet `
+ "(Streit, Zustand, Zusammenhang, Medienabfrage, Wiederholung)");
}
/* ---- Nichts rollt absichtlich waagerecht ----------------------