147 Zeichen je Zeile -- am Kontrast lag es nicht

Filipe: „nur die kacheln noch viel geiler. so dass ich die texte auch
besser erkenne."

NAHELIEGEND WAERE GEWESEN, die Farben aufzuhellen. Erst gemessen,
dann gebaut -- und die Vermutung war falsch:

    Kontrast .s-karte__text        15,7 : 1   (noetig 4,5)
    Kontrast aller zehn Textarten   7,5 bis 15,7 : 1
    ZEICHEN JE ZEILE                        147   (angenehm 45-75)

Am Kontrast lag es nicht; der ist ueberall ueppig. Es lag an der
ZEILENLAENGE. Auf 1280 px lief der Meldungstext ueber die ganze
Kartenbreite: 1120 px, knapp 150 Zeichen. Beim Zeilenwechsel findet
das Auge die naechste Zeile nicht mehr sicher -- man liest dieselbe
zweimal oder ueberspringt eine. Das ist der groesste Hebel beim
Lesen, und er kostet nichts.

GEAENDERT

    Meldungstext   15,2 -> 17 px,  max-width 72ch,  Zeilenhoehe 1.6
    Verlaufstext   14,4 -> 15,2 px, max-width 70ch
    Kartenrand     16/18/15/22 -> 18/20/16/24 px
    Trennlinie     zwischen Meldung und Verlauf

    147 -> 78 Zeichen je Zeile (PC), 39 auf dem Handy

`ch` UND NICHT PIXEL: `ch` ist die Breite der Null und waechst mit
der Schrift mit. Eine Angabe in Pixeln waere bei der naechsten
Schriftgroesse wieder falsch -- dieselbe Falle wie jede feste Zahl.

MEHR RAND, WEIL DIE SCHRIFT GEWACHSEN IST. Bei unveraendertem Rand
haette sie die Kante beruehrt, und die Karte waere voller gewirkt
statt lesbarer. Links bleibt es breiter: dort laeuft die
Leuchtschiene in der Farbe des Stands.

DIE TRENNLINIE kostet einen Pixel und beantwortet „wo hoert die
Meldung auf und wo faengt die Vorgeschichte an?". Vorher lief beides
ohne Zaesur ineinander.

EIN FEHLER IN MEINER EIGENEN MESSUNG -- und er haette Schaden
angerichtet

Die erste Fassung der Kontrastrechnung meldete fuer drei gut lesbare
Texte einen Kontrast von 1,04. Ich war nah dran, Farben zu
„reparieren", die in Ordnung sind.

Der Grund: `rgb()` zaehlt 0..255, `color(srgb …)` zaehlt 0..1 -- und
genau das liefert Chrome fuer jedes `color-mix()`. 0,77 als 0,77/255
gelesen ist fast Schwarz. Aufgefallen ist es nur, weil die Zahl
nicht zum Bildschirmfoto passte: Dort waren die Texte deutlich
lesbar.

DESHALB STEHT IN DER PRUEFUNG EINE GEGENPROBE IM BROWSER: Ein
absichtlich zu blasser Absatz wird eingehaengt und MUSS auffallen
(gemessen 1,23 : 1). Ohne sie waere „alle ueber 4,5" auch dann
gruen, wenn die Rechnung kaputt ist -- und genau das war sie.

GEPRUEFT -- pruef-support-bilder 56 -> 64 ok

    9 Textarten in der Karte gemessen
    der Meldungstext ist 16.96 px gross (mindestens 16)
    und bricht nach etwa 78 Zeichen um (hoechstens 85, vorher 147)
    der Verlaufstext: 15.2 px, etwa 75 Zeichen
    jeder Text haelt die Kontrastschwelle (0 darunter)
      — schwaechster 7.55 : 1
    Gegenprobe: ein absichtlich blasser Text faellt auf (1.23 : 1)
    auf 390 px ragt mit der groesseren Schrift nichts heraus (0 px)
    und die Zeile bleibt lesbar lang (etwa 39 Zeichen)

Dazu gruen: pruef-css-klassen · pruef-fingermass.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-10-03 12:37:05 +02:00
co-authored by Claude Opus 5
parent a1c2d49260
commit db8dd2fbea
48 changed files with 885 additions and 688 deletions
+152
View File
@@ -656,6 +656,158 @@ melde("\n=== Eine Farbe je Runde ===");
await h.kontext.close();
}
/* =====================================================================
7. SIND DIE TEXTE IN DER KARTE ZU LESEN? (03.10.2026)
=====================================================================
Filipe: „nur die kacheln noch viel geiler. so dass ich die texte
auch besser erkenne."
GEMESSEN, BEVOR ETWAS ANGEFASST WURDE -- und die naheliegende
Vermutung war falsch:
Kontrast aller zehn Textarten 7,5 bis 15,7 : 1
ZEICHEN JE ZEILE 147
Am Kontrast lag es nicht. Es lag an der Zeilenlaenge: Auf 1280 px
lief der Meldungstext ueber 1120 px, also knapp 150 Zeichen.
Angenehm sind 45 bis 75; darueber findet das Auge beim
Zeilenwechsel die naechste Zeile nicht mehr sicher.
ZWEI ZAHLEN WERDEN DESHALB GEPRUEFT, und beide mussten vorher
gemessen werden, um sie ueberhaupt zu kennen. Eine Regel, die aus
einer Messung kommt, ist etwas anderes als eine, die man sich
ausdenkt.
===================================================================== */
melde("\n=== Sind die Texte zu lesen? ===");
{
const { kontext, seite } = await neueSeite(1280, keksModi);
await seite.locator(".s-karte__text").first().waitFor({ timeout: 8000 });
const lesbar = await seite.evaluate(() => {
/* ==== ZWEI SCHREIBWEISEN, EINE EINHEIT ======================
`rgb()` zaehlt 0..255, `color(srgb …)` zaehlt 0..1 -- und
genau das liefert Chrome fuer jedes `color-mix()`. Beim Bauen
dieser Messung hat mich das getaeuscht: 0,77 als 0,77/255
gelesen ergibt fast Schwarz, und drei gut lesbare Texte
standen mit Kontrast 1,04 da. Fast haette ich Farben
repariert, die in Ordnung sind. */
const zahl = (t) => {
const n = (t.match(/[\d.]+/g) || []).map(Number);
if (/^color\(/.test(t)) {
return [n[0] * 255, n[1] * 255, n[2] * 255, n.length > 3 ? n[3] : 1];
}
return n;
};
const kanal = (c) => {
const x = c / 255;
return x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
};
const hell = ([r, g, b]) => 0.2126 * kanal(r) + 0.7152 * kanal(g) + 0.0722 * kanal(b);
const mischen = (v, h, a) => v.map((x, i) => x * a + h[i] * (1 - a));
/* DER GRUND WIRD NACH OBEN GESUCHT, bis eine undurchsichtige
Farbe kommt -- halbdurchsichtige Kaesten uebereinander sind
sonst nicht zu rechnen. */
const grundVon = (e) => {
let n = e; const stapel = [];
while (n && n !== document.documentElement) {
const bg = zahl(getComputedStyle(n).backgroundColor);
const a = bg.length === 4 ? bg[3] : 1;
if (a > 0) stapel.push([bg.slice(0, 3), a]);
if (a === 1) break;
n = n.parentElement;
}
let farbe = [11, 15, 22];
for (let i = stapel.length - 1; i >= 0; i--) {
farbe = mischen(stapel[i][0], farbe, stapel[i][1]);
}
return farbe;
};
const kontrast = (e) => {
const vg = zahl(getComputedStyle(e).color);
const a = vg.length === 4 ? vg[3] : 1;
const grund = grundVon(e);
const vorne = mischen(vg.slice(0, 3), grund, a);
const l1 = hell(vorne), l2 = hell(grund);
const [h, d] = l1 > l2 ? [l1, l2] : [l2, l1];
return (h + 0.05) / (d + 0.05);
};
const arten = [".s-karte__text", ".s-marke", ".s-karte__zeit", ".s-runde__nr",
".s-runde__von", ".s-runde__zeit", ".s-schritt__schild", ".s-schritt__text",
".s-rundenleiste__punkt"];
const werte = [];
for (const w of arten) {
const e = document.querySelector(w);
if (!e) continue;
const st = getComputedStyle(e);
const px = parseFloat(st.fontSize);
werte.push({
w, px: Math.round(px * 100) / 100,
k: Math.round(kontrast(e) * 100) / 100,
zeichen: Math.round(e.getBoundingClientRect().width / (px * 0.5)),
gross: px >= 24 || (px >= 18.66 && Number(st.fontWeight) >= 700),
});
}
/* GEGENPROBE IM BROWSER: Ein absichtlich zu blasser Text MUSS
auffallen. Ohne sie waere „alle ueber 4,5" auch dann gruen,
wenn die Rechnung kaputt ist -- und genau das war sie. */
const probe = document.createElement("p");
probe.textContent = "Probe";
probe.style.color = "#17202c";
document.querySelector(".s-karte").append(probe);
const blass = Math.round(kontrast(probe) * 100) / 100;
probe.remove();
return { werte, blass };
});
ok(lesbar.werte.length >= 8,
`${lesbar.werte.length} Textarten in der Karte gemessen`);
const text = lesbar.werte.find((x) => x.w === ".s-karte__text");
/* DER MELDUNGSTEXT IST DIE HAUPTSACHE DER SEITE. */
ok(text && text.px >= 16,
`der Meldungstext ist ${text?.px} px gross (mindestens 16)`);
ok(text && text.zeichen <= 85,
`und bricht nach etwa ${text?.zeichen} Zeichen um (hoechstens 85, `
+ "vorher waren es 147)");
const verlauf = lesbar.werte.find((x) => x.w === ".s-schritt__text");
ok(verlauf && verlauf.px >= 15 && verlauf.zeichen <= 85,
`der Verlaufstext: ${verlauf?.px} px, etwa ${verlauf?.zeichen} Zeichen`);
/* UND KEIN TEXT UNTER DER KONTRASTSCHWELLE. */
const schwach = lesbar.werte.filter((x) => x.k < (x.gross ? 3 : 4.5));
ok(schwach.length === 0,
`jeder Text haelt die Kontrastschwelle (${schwach.length} darunter`
+ (schwach.length ? `: ${schwach.map((x) => `${x.w} ${x.k}`).join(", ")}` : "")
+ `) — schwaechster ${Math.min(...lesbar.werte.map((x) => x.k))} : 1`);
ok(lesbar.blass < 4.5,
`Gegenprobe: ein absichtlich blasser Text faellt auf (${lesbar.blass} : 1)`);
/* UND AUF DEM HANDY: kurze Zeilen sind dort von selbst kurz -- die
Frage ist, ob die groessere Schrift die Seite sprengt. */
await kontext.close();
const h = await neueSeite(390, keksModi);
await h.seite.locator(".s-karte__text").first().waitFor({ timeout: 8000 });
const handy = await h.seite.evaluate(() => {
const e = document.querySelector(".s-karte__text");
const px = parseFloat(getComputedStyle(e).fontSize);
return {
px: Math.round(px * 100) / 100,
zeichen: Math.round(e.getBoundingClientRect().width / (px * 0.5)),
ueber: document.documentElement.scrollWidth - document.documentElement.clientWidth,
};
});
ok(handy.ueber <= 0,
`auf 390 px ragt mit der groesseren Schrift nichts heraus (${handy.ueber} px)`);
ok(handy.zeichen >= 25 && handy.zeichen <= 60,
`und die Zeile bleibt lesbar lang (etwa ${handy.zeichen} Zeichen)`);
await h.kontext.close();
}
await browser.close();
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }