Files
dogfather-universe/server/pruef-kachel-universum.mjs
T
DogFatherGitandClaude Opus 5 d7bb0f7e60 45 Kachelfarben: acht Paare waren dieselbe Farbe -- und die Pruefung
konnte es nicht sagen

Filipe am 17.09.: „ich will das jede kachel eine andere farbe hat, es
soll keine die gleiche farben haben bitte und auch keine die sich
irgendwie aehnlich sind ... die farben sollen auch richtig geil und
speziell sein."

Gemessen am 25.09.: #ff8fb4 gegen #fe8ebe, Abstand 0,0154 in OKLab.
Das ist mit blossem Auge DIESELBE Farbe. Acht solche Paare gab es.

=== WARUM ES NIEMAND GEMERKT HAT ===

pruef-kachel-universum hat es gesagt. Jede Nacht. Die Zeile stand da:
„kleinster Abstand 0,0154". Gelesen hat sie niemand -- weil direkt
daneben eine zweite Zeile stand, die NIEMALS gruen werden konnte:
„kein Paar unter 0,10".

Nachgerechnet (tools/_toene-packen.mjs, eine echte Kugelpackung ueber
alle sRGB-Farben, die 4,8:1 gegen den Grund halten, nicht blenden und
bunt genug sind):

  30 Farben  ->  0,115        48 Farben  ->  0,0925
  40 Farben  ->  0,103        52 Farben  ->  0,0840
  45 Farben  ->  0,0974       60 Farben  ->  0,0805

Die Forderung war fuer 21 Farben geschrieben. Bei den heutigen 45
liegt die DECKE bei 0,0974 -- „kein Paar unter 0,10" konnte niemand
erfuellen, mit keiner Palette der Welt.

DAS IST DER EIGENTLICHE BEFUND. Eine Bedingung, die niemand erfuellen
kann, macht nicht nur sich selbst wertlos. Sie faerbt die ganze Datei
rot, und ab da liest man die Zeile darueber nicht mehr. Der echte
Mangel lag acht Tage offen da, versteckt hinter einem Fehlalarm.

=== DIE NEUE PALETTE WURDE GERECHNET, NICHT NACHGEBESSERT ===

Von Hand nachbessern hat sie erst dahin gebracht: Am 08.09. waren es
21 Farben, danach kamen sechzehn dazu, jede einzeln gewaehlt, keine
gegen die anderen geprueft. In drei Schritten:

  1. Aus allen erlaubten sRGB-Farben 45 so waehlen, dass der kleinste
     Abstand so gross wie moeglich wird.
  2. Sie den 45 Kachelnummern so zuordnen, dass jede moeglichst nah an
     ihrer bisherigen Farbe bleibt -- eine Kachel soll wiedererkennbar
     sein, sie rueckt, sie wechselt nicht.
  3. Nachziehen: Jeder Ton darf zurueck in Richtung seiner alten Farbe
     wandern, solange der Mindestabstand haelt.

Ergebnis: kleinster Abstand 0,0154 -> 0,0931, kein Paar mehr unter
0,09. Dreissig der 45 Kacheln haben sich um weniger als 0,02 bewegt --
das sieht man nicht. Nur sieben sind sichtbar gewandert, und alle
sieben lagen in dem Gedraenge aus neun fast gleichen Rot- und
Rosatoenen, das den Ausschlag gegeben hat.

„Richtig geil und speziell" bleibt messbar erhalten: Die Buntheit hat
eine Untergrenze von 0,10 in der Rechnung, damit keine Farbe ins Graue
rutscht. Gemessen kostet das nichts -- mit dieser Grenze ist die Decke
sogar minimal hoeher als ohne (0,0974 gegen 0,0973).

=== UND DIE PRUEFUNG SAGT JETZT ETWAS ERFUELLBARES ===

  kleinster Abstand >= 0,09          (Decke fuer 45 Farben: 0,097)
  hoechstens 48 Farben               (darueber ist 0,09 nicht mehr
                                      erreichbar -- gemessen, nicht
                                      gesetzt)
  Gegenprobe: #ff8fb4 gegen #fe8ebe  muss durchfallen

Die zweite Zeile ist die wichtige: Sie bewacht den GRUND. Wer die
46., 47., 48. Kachel anlegt, kommt noch durch; wer die 52. anlegt,
bekommt gesagt, dass jetzt ueber die Palette geredet werden muss,
statt still wieder in zwei gleiche Farben zu rutschen. Genau das ist
zwischen dem 08.09. und dem 17.09. passiert.

GEMESSEN: pruef-kachel-universum 13 Pruefungen, 0 Fehler (vorher 12
mit 2 Fehlern). Schlechtester Textkontrast auf der fertigen Kachel
7,02:1, am Bildschirmfoto gemessen.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-25 23:19:29 +02:00

399 lines
18 KiB
JavaScript

/* =====================================================================
DIE KACHELN: FARBEN UND UNIVERSUM (17.09.2026)
Filipe, zwei Wünsche in einem Satz:
„ich will das jede kachel eine andere farbe hat, es soll keine die
gleiche farben haben bitte und auch keine die sich irgendwie
ähnlich sind ... die farben sollen auch richtig geil und speziell
sein."
„der hintergrund von allen kacheln haben wir richtung universum
mäßig gemacht, aber bin noch nicht ganz zufrieden ... nicht
einfach so paar weiße punkte sondern richtig geil hochwertiger
geiler universum als hintergrund in den kacheln überall."
BEIDES WAR BERECHTIGT, und beides ließ sich messen:
kleinster Farbabstand 0,0529 (Rückmeldung / Steckbrief)
Paare unter 0,10 26 von 666
schlechtester Kontrast 1,64:1 (Ton 35 — unlesbar)
Universum auf GENAU EINER Kachel
Der Grund für das erste: Ein Werkzeug rechnete die Farben, kannte
aber nur 21 — seit dem 08.09. waren sechzehn von Hand dazugekommen.
Es meldete weiter „alle Bedingungen erfüllt" und meinte einen Stand,
den es nicht mehr gab.
---------------------------------------------------------------------
WAS HIER GEPRÜFT WIRD — und warum es drei Abschnitte braucht:
1. Die Farben stehen weit genug auseinander (OKLab, alle Paare).
2. Jede hält 4,5:1 gegen den dunklen Grund — sie trägt auch Text.
3. DER TEXT AUF DER FERTIGEN KACHEL ist lesbar.
Der dritte ist der, den sonst niemand macht, und der einzige, der
die Frage wirklich beantwortet: Zwischen Textfarbe und `--flaeche`
liegen inzwischen SIEBEN Ebenen (drei Nebel, Sterne, Milchstraße,
Vignette, Glanz) plus das durchscheinende Bühnenbild. Aus dem CSS
lässt sich das nicht mehr ausrechnen — gemessen wird deshalb am
Bildschirmfoto, an den echten Bildpunkten.
Aufrufen mit: node server/pruef-kachel-universum.mjs
===================================================================== */
import { mkdtempSync, rmSync, readFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { eigenerPort } from "./helfer-port.mjs";
import { pngLesen, pixel, kontrast } from "./helfer-png.mjs";
const HIER = dirname(fileURLToPath(import.meta.url));
const CSS = join(HIER, "..", "workspace", "assets", "css", "start.css");
const GRUND = [10, 18, 30]; // #0a121e
let fehler = 0, geprueft = 0, offen = 0;
const melde = (t) => console.log(t);
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
const unklar = (t) => { offen++; console.log(" -- " + t); };
/* ---------- OKLab ---------------------------------------------------- */
const linear = (c) => (c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
const srgb = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255);
function oklab(hex) {
const [r, g, b] = srgb(hex).map(linear);
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
return [
0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s,
1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s,
0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s,
];
}
const abstand = (a, b) => {
const x = oklab(a), y = oklab(b);
return Math.hypot(x[0] - y[0], x[1] - y[1], x[2] - y[2]);
};
const alsRgb = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));
/* =======================================================================
1. DIE FARBEN STEHEN AUSEINANDER
======================================================================= */
melde("=== 1. Die Farben ===");
const cssText = readFileSync(CSS, "utf8");
const TOENE = new Map(
[...cssText.matchAll(/\[data-ton="(\d+)"\]\s*\{\s*--ton:\s*(#[0-9a-fA-F]{6})/g)]
.map((m) => [Number(m[1]), m[2].toLowerCase()]));
/* DIE ANZAHL KOMMT AUS DER DATEI. Genau hier ist das Farbwerkzeug
unzuständig geworden: Es kannte 21, und niemand hat es angefasst,
als sechzehn dazukamen. Eine Zahl, die niemand pflegt, kann nicht
veralten. */
ok(TOENE.size >= 20, `${TOENE.size} Farben in start.css gefunden`);
/* Werden alle auch benutzt? Eine Farbe ohne Kachel ist kein Fehler,
aber eine KACHEL OHNE FARBE ist einer -- sie fiele auf den Akzent
zurück, und dann hätten mehrere Kacheln dieselbe. */
{
const quellen = ["workspace.js", "../workspace/assets/js/bereiche.js",
"../workspace/assets/js/start.js", "../workspace/assets/js/kopf.js",
"../workspace/assets/js/befinden.js"];
const benutzt = new Set();
for (const q of quellen) {
const t = readFileSync(join(HIER, q), "utf8");
for (const m of t.matchAll(/ton:\s*(\d+)/g)) benutzt.add(Number(m[1]));
}
const ohne = [...benutzt].filter((n) => !TOENE.has(n));
ok(ohne.length === 0,
ohne.length ? `Kacheln ohne Farbe: Ton ${ohne.join(", ")}`
: `alle ${benutzt.size} benutzten Tonnummern haben eine Farbe`);
}
const liste = [...TOENE.entries()].map(([n, hex]) => ({ n, hex }));
const paare = [];
for (let i = 0; i < liste.length; i++) {
for (let j = i + 1; j < liste.length; j++) {
paare.push({ a: liste[i], b: liste[j], d: abstand(liste[i].hex, liste[j].hex) });
}
}
paare.sort((x, y) => x.d - y.d);
/* ==== DIE GRENZE WAR FUER EINUNDZWANZIG FARBEN GERECHNET (25.09.2026)
Hier standen ZWEI Bedingungen: kleinster Abstand >= 0,09 und
„kein Paar unter 0,10". Die zweite war seit dem 17.09. nicht mehr
erfuellbar -- nicht schwer, sondern UNMOEGLICH.
Nachgerechnet (tools/_toene-packen.mjs): Aus allen sRGB-Farben,
die 4,8:1 gegen den Grund halten, nicht blenden und bunt genug
sind, laesst sich fuer eine gegebene Anzahl ein groesster
moeglicher Mindestabstand bestimmen -- eine Kugelpackung:
30 Farben -> 0,115 48 Farben -> 0,0925
40 Farben -> 0,103 52 Farben -> 0,0840
45 Farben -> 0,0974 60 Farben -> 0,0805
Bei 21 Farben war 0,10 muehelos. Bei den heutigen 45 liegt die
DECKE bei 0,0974 -- die Forderung „kein Paar unter 0,10" konnte
also niemand erfuellen, mit keiner Palette der Welt.
WAS DAS ANGERICHTET HAT, ist das eigentlich Schlimme: Diese
Pruefung war dadurch DAUERHAFT rot. Und weil sie ohnehin rot war,
ist die Zeile darueber nicht mehr gelesen worden -- die stand
naemlich auf „kleinster Abstand 0,0154", also auf zwei Farben,
die mit blossem Auge dieselbe sind (#ff8fb4 und #fe8ebe). Acht
solche Paare gab es. Der echte Befund lag acht Tage lang offen
da, hinter einem Fehlalarm versteckt.
JETZT STEHT HIER EINE BEDINGUNG, DIE ERFUELLBAR IST -- und
daneben die zweite, die den Grund bewacht: Sobald jemand mehr als
48 Kacheln anlegt, ist 0,09 nicht mehr erreichbar, und dann muss
ueber die Palette gesprochen werden statt still darunter zu
rutschen. Die Zahl ist gemessen, nicht gesetzt. */
const GRENZE = 0.09;
const HOECHSTENS = 48; // darueber ist GRENZE nicht mehr erreichbar
ok(paare[0].d >= GRENZE,
`kleinster Abstand ${paare[0].d.toFixed(4)} (Grenze ${GRENZE}, `
+ `Decke fuer ${liste.length} Farben rund 0,097) `
+ `-- ${paare[0].a.hex}/${paare[0].b.hex}`);
ok(liste.length <= HOECHSTENS,
liste.length <= HOECHSTENS
? `${liste.length} Farben -- bis ${HOECHSTENS} ist ${GRENZE} nachweislich erreichbar`
: `${liste.length} Farben: ueber ${HOECHSTENS} ist ein Mindestabstand von `
+ `${GRENZE} rechnerisch nicht mehr moeglich. Entweder weniger `
+ `Kacheln mit eigener Farbe, oder die Grenze neu verhandeln.`);
/* GEGENPROBE. Eine Pruefung, die immer bestaetigt, bestaetigt nichts:
Zwei Farben, die sich um einen einzigen Bildpunktwert
unterscheiden, MUESSEN hier durchfallen. */
{
const nah = abstand("#ff8fb4", "#fe8ebe"); // das alte Paar
ok(nah < GRENZE,
`Gegenprobe: #ff8fb4 gegen #fe8ebe misst ${nah.toFixed(4)} und faellt durch`);
}
{
/* KEINE ZWEI GLEICHEN. Das steht als eigene Zeile da, obwohl der
Abstand es mit abdeckt -- es ist die wörtliche Bitte, und eine
Prüfung, die die Frage direkt beantwortet, liest sich später
besser als eine, aus der man es schließen muss. */
const doppelt = liste.length - new Set(liste.map((x) => x.hex)).size;
ok(doppelt === 0, doppelt ? `${doppelt} Farben kommen doppelt vor` : "keine Farbe kommt zweimal vor");
}
/* Jede Farbe trägt Text -- sie muss gegen den dunklen Grund halten. */
{
const schlecht = liste.filter((x) => kontrast(alsRgb(x.hex), GRUND) < 4.5);
ok(schlecht.length === 0,
schlecht.length
? `unter 4,5:1 -> ${schlecht.map((x) => `Ton ${x.n} ${x.hex} `
+ `${kontrast(alsRgb(x.hex), GRUND).toFixed(2)}:1`).join(", ")}`
: `alle halten 4,5:1 gegen den Grund (schlechteste `
+ `${Math.min(...liste.map((x) => kontrast(alsRgb(x.hex), GRUND))).toFixed(2)}:1)`);
}
/* =======================================================================
2. DAS UNIVERSUM IST DA -- UND ÜBERALL
Gezählt wird im CSS: Wie viele Ebenen hat der Kachelhintergrund? Ein
Universum aus „ein paar weißen Punkten" hat drei, das gebaute hat
siebzehn. Die Zahl allein sagt nichts über Schönheit -- sie sagt,
dass die Ebenen NICHT VERSEHENTLICH verschwinden, und genau das ist
schon einmal passiert (die Leuchtschiene, 07.09.: Regeln blieben,
Inhalt weg).
======================================================================= */
melde("");
melde("=== 2. Das Universum im Hintergrund ===");
{
const block = cssText.slice(cssText.indexOf(".kachel {"),
cssText.indexOf("background-size:", cssText.indexOf(".kachel {")));
/* DIE GROESSEN, NICHT NUR DIE ANZAHL.
Der erste Anlauf zaehlte `radial-gradient(...px` -- und `0px` ist
auch eine Zahl. Setzt man alle Sterne auf null, blieb die Zaehlung
bei zwoelf und die Pruefung gruen, waehrend das Universum leer war.
UND WARUM NICHT AM BILD GEMESSEN WIRD: Der zweite Anlauf zaehlte
Lichtpunkte im Bildschirmfoto. Auch das trug nicht -- die Kachel
ist halbdurchsichtig, darunter liegt das Buehnenbild, und ein Foto
hat selbst Punktstruktur. Gemessen: 459 Punkte mit Sternen, 441
ohne. Vier Prozent Unterschied sind kein Nachweis, sondern
Rauschen. Eine Pruefung, die das als "bestanden" wertet, prueft
nichts -- lieber eine ehrliche schwache als eine starke, die luegt. */
const groessen = [...block.matchAll(/radial-gradient\(\s*([\d.]+)px/g)]
.map((m) => Number(m[1]));
const sterne = groessen.filter((g) => g >= 0.7).length;
const nebel = (block.match(/radial-gradient\(\s*\d+%/g) || []).length;
ok(sterne >= 10,
`${sterne} Sternebenen mit sichtbarer Groesse `
+ `(${groessen.length} Punktebenen insgesamt, kleinste `
+ `${Math.min(...groessen).toFixed(1)}px)`);
ok(nebel >= 3, `${nebel} Nebel- und Tiefenebenen`);
{
/* GEZAEHLT, NICHT NUR GESUCHT. Der erste Anlauf fragte, ob
`var(--ton)` im Block VORKOMMT -- das tut es auch im
Grundverlauf, der seit jeher da ist. Die Gegenprobe (einem Nebel
die Kachelfarbe wegnehmen) blieb deshalb gruen. Gezaehlt werden
jetzt die FLAECHIGEN Verlaeufe, die den Ton wirklich tragen. */
const nebelMitTon = (block.match(
/radial-gradient\(\s*\d+%[^)]*\)[^,]*var\(--ton\)|radial-gradient\(\s*\d+%[^;]*?color-mix\(in srgb, var\(--ton\)/g) || []).length;
ok(nebelMitTon >= 2,
`${nebelMitTon} Nebel tragen die Farbe der Kachel `
+ "-- jede Kachel ihr eigenes Universum");
}
ok(/118deg|milchstra/i.test(block), "und es gibt eine Schräge (die Milchstraße)");
}
/* =======================================================================
3. DER TEXT DARAUF IST LESBAR
Der Abschnitt, den sonst niemand macht. Gemessen am Bildschirmfoto:
Zwischen Textfarbe und Fläche liegen sieben Ebenen plus das
durchscheinende Bühnenbild -- aus dem CSS ist das nicht mehr
auszurechnen.
======================================================================= */
melde("");
melde("=== 3. Der Text auf der fertigen Kachel ===");
const PORT = await eigenerPort(import.meta, "pruef-kachel-universum");
const ordner = mkdtempSync(join(tmpdir(), "ws-kachel-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
process.env.PORT = String(PORT);
process.env.SITE_ACCESS_SECRET = "lokaler-test";
process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
const { notbremse } = await import("./helfer-notbremse.mjs");
await import("./index.js");
notbremse(180_000, "pruef-kachel-universum");
await new Promise((r) => setTimeout(r, 800));
{
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes } = await import("node:crypto");
const d = new DatabaseSync(process.env.WORKSPACE_DB);
const salt = randomBytes(16).toString("hex");
const hash = scryptSync("CODE-DOGI-0001", salt, 64,
{ N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, aktiv, erstellt) VALUES (?,?,?,?,?,1,?)")
.run("Filipe", "admin", hash, salt, 32768, new Date().toISOString());
d.close();
}
let chromium;
try {
({ chromium } = await import(
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"));
} catch { chromium = null; }
if (!chromium) {
unklar("kein Playwright gefunden -- die Kacheln wurden nicht angesehen");
} else {
const browser = await chromium.launch();
try {
const kontext = await browser.newContext({
viewport: { width: 1440, height: 900 }, deviceScaleFactor: 2,
});
const seite = await kontext.newPage();
await seite.goto(`http://127.0.0.1:${PORT}/workspace/`, { waitUntil: "domcontentloaded" });
await seite.click('.rolle[data-rolle="admin"]');
await seite.fill("#code", "CODE-DOGI-0001");
await seite.click("button[type=submit]");
await seite.waitForURL("**/start.html", { timeout: 15000 });
await seite.waitForTimeout(2500);
/* ERST ZU DEN KACHELN SCROLLEN. Beim ersten Lauf wurden NULL
gemessen: Der Filter verlangt, dass die Kachel ganz im Bild
liegt, und oben steht die Zentrale mit Uhr und Zahlen. Eine
Messung, die nichts findet, ist keine Messung -- sie sah nur
aus wie eine. */
/* In die Mitte des Kachelfeldes, nicht an seinen Anfang: Dort
stehen mehrere Reihen gleichzeitig im Bild, und je mehr Kacheln
gemessen werden, desto eher ist die schlechteste dabei. */
const anzahl = await seite.locator(".kachel").count();
await seite.locator(".kachel").nth(Math.min(6, anzahl - 1)).scrollIntoViewIfNeeded();
await seite.waitForTimeout(700);
const stellen = await seite.evaluate(() =>
[...document.querySelectorAll(".kachel")].map((k) => {
const name = k.querySelector(".kachel__name");
if (!name) return null;
const r = name.getBoundingClientRect();
/* SICHTBAR HEISST: die Zeile liegt im Bild -- nicht die ganze
Kachel. Der erste Filter verlangte letzteres und liess genau
EINE uebrig; gemessen wird aber der Punkt unter dem TEXT,
und der braucht nur sich selbst. */
if (r.width < 10 || r.top < 8 || r.bottom > window.innerHeight - 8) return null;
const [tr, tg, tb] = getComputedStyle(name).color.match(/\d+/g).map(Number);
/* DIE KACHELGRENZEN KOMMEN MIT. Ohne sie wurde der Messbereich
aus der Textposition geschaetzt (`y - 70`) -- und lag damit
OBERHALB der Kachel, im Seitenhintergrund. Die Gegenprobe
lieferte zweimal exakt 153 Punkte; genau diese Gleichheit hat
den Fehler verraten. Eine Messung, deren Ergebnis sich nicht
aendert, wenn man das Gemessene entfernt, misst etwas
anderes. */
const kb = k.getBoundingClientRect();
return {
name: name.textContent.trim(), text: [tr, tg, tb],
x: Math.round(r.left + r.width / 2), y: Math.round(r.top + r.height / 2),
kachel: {
links: Math.round(kb.left), oben: Math.round(kb.top),
breite: Math.round(kb.width), hoehe: Math.round(kb.height),
},
};
}).filter(Boolean));
/* DIE ANZAHL GEHÖRT IN DIE BEDINGUNG: Ohne sichtbare Kacheln wäre
„alle lesbar" auch dann grün, wenn keine einzige gemessen wurde. */
ok(stellen.length >= 4, `${stellen.length} Kacheln im Bild gemessen`);
/* DER DRITTE AUSGANG. Ohne diesen Sprung stuerzte der Abschnitt ab
(`werte[0].k` auf einem leeren Feld) -- und ein Absturz sagt
weniger als ein Fehlschlag: Er verschluckt die Zeilen danach und
sieht aus wie ein Problem der Pruefung statt der Sache. */
if (!stellen.length) {
ok(false, "ohne sichtbare Kachel laesst sich kein Kontrast messen");
} else {
const bild = pngLesen(await seite.screenshot({ type: "png" }));
const werte = stellen.map((s) => {
/* Mehrere Punkte je Kachel -- der Untergrund ist nicht
gleichmäßig (Nebel, Vignette, Sterne). Der SCHLECHTESTE zählt:
An der hellsten Stelle steht der Text am schwersten. */
let schlecht = Infinity;
for (const dx of [-30, -15, 0, 15, 30]) {
for (const dy of [-4, 0, 4]) {
const k = kontrast(s.text, pixel(bild, (s.x + dx) * 2, (s.y + dy) * 2));
if (k < schlecht) schlecht = k;
}
}
return { name: s.name, k: schlecht };
}).sort((a, b) => a.k - b.k);
ok(stellen.length > 0 && werte[0].k >= 4.5,
`schlechtester Textkontrast ${werte[0].k.toFixed(2)}:1 auf „${werte[0].name}" (Grenze 4,5)`);
melde(` die drei schwächsten: ${werte.slice(0, 3)
.map((w) => `${w.name} ${w.k.toFixed(1)}:1`).join(" · ")}`);
}
await seite.screenshot({ path: "server/pruef-kachel-universum.png", fullPage: false });
melde(" -- Bildschirmfoto: server/pruef-kachel-universum.png");
} finally {
await browser.close();
}
}
melde("");
melde(`${geprueft} Pruefungen, ${fehler} Fehler`
+ (offen ? `, ${offen} nicht nachsehbar` : ""));
melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest */ }
process.exit(fehler ? 1 : 0);