Files
dogfather-universe/pruef-system.mjs
T
DogFatherGitandClaude Opus 5 10a075cc33 Gestaltung: Hoehensystem -- mehrschichtige Schatten und Lichtkante
Bis hierher trug jede Karte EINEN Schatten. Das ist solide und genau der
Grund, warum eine Oberflaeche "ordentlich" statt "teuer" wirkt.

Echtes Licht erzeugt zwei Dinge gleichzeitig: einen engen, dunklen
Kontaktschatten an der Kante -- er sagt dem Auge, dass etwas AUFLIEGT --
und einen weiten, weichen Umgebungsschatten, der sagt, wie HOCH es
darueber schwebt. Nur einen von beiden zu setzen sieht aus wie ein
Aufkleber; beide zusammen ergeben einen Gegenstand.

Dazu die LICHTKANTE: eine 1 Pixel hohe, sehr schwache weisse Linie an der
Oberkante. Sie tut so, als kaeme das Licht von oben und braeche sich an
der Kante. Das ist das meistuebersehene Detail in dunklen Oberflaechen
und dasjenige mit der groessten Wirkung -- ohne sie wirkt eine Karte wie
ein Loch im Hintergrund, mit ihr wie ein Stueck Material.

Drei Stufen, mehr braucht es nicht: ruhende Flaechen, hervorgehobene
Flaechen, Schwebendes. Dazu eine vierte, umgekehrte fuer Eingabefelder:
Die liegen nicht AUF der Flaeche, sie sind hineingeschnitten -- oben
dunkel, unten hell.

Weil es die gemeinsamen Bausteine sind, wirkt es sofort auf allen 13
Seiten: Hauptseite, Portal und Verwaltung.

Dazu Feinheiten, die einzeln niemand bemerkt und in Summe den
Unterschied machen: Uebergaenge auf 160-180 ms verkuerzt (alles darueber
wirkt beim Ueberfahren traege), eigene Rueckmeldung beim Druecken,
optische Laufweitenkorrektur fuer grosse Ueberschriften (-.028em bei h1;
das Auge sieht bei 48px mehr Weissraum zwischen Buchstaben als bei 16px),
text-wrap: balance gegen einzelne Woerter in der letzten Zeile.

EIN FEHLER BEIM EINBAU, GEMESSEN STATT UEBERSEHEN

Nach dem ersten Durchgang hatten die Karten ihre Lichtkante, der
Hauptknopf nicht. Grund: Die Grundregel lautet
".wd .wd-btn--haupt, .wd-btn--haupt" -- der erste Teil zaehlt ZWEI
Klassen. Mein Nachtrag zaehlte eine und verlor, obwohl er spaeter steht.
Dieselbe Falle wie am 22.08.2026 bei ".wd a" gegen ".wd-btn--haupt".
Aufgefallen nur, weil die Pruefung die Lichtkanten ZAEHLT.

PRUEFWERKZEUGE ANGEPASST

diagnose.html ist jetzt ausgenommen. Sie traegt bewusst alles fest in
sich -- eigene Farben, keine Uebersetzung -- weil sie funktionieren muss,
wenn genau das kaputt ist, was sie untersucht. Sie an den Regeln der
eigentlichen Seite zu messen erzeugte zwei Meldungen, die man auf Dauer
wegsieht. Und irgendwann sieht man dann auch eine echte weg.

ALLES GRUEN: Gestaltung 0 Befunde (12 Seiten x 5 Sprachen gegen
WCAG 2.2 AA), Designsystem 5, Verwaltung 69, Portal 32.

Versionsstempel und Cache auf v17.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-23 18:37:17 +02:00

178 lines
7.7 KiB
JavaScript

/* =====================================================================
pruef-system.mjs — Beweist, dass das Farbsystem eines IST
Ein Designsystem ist erst dann eines, wenn eine Änderung an EINER
Stelle überall ankommt. Steht die Markenfarbe an 155 Stellen
ausgeschrieben, hat man kein System, sondern eine Sammlung von
Zufällen, die sich zufällig ähneln.
WIE gemessen wird — und warum nicht anders.
Der naheliegende Weg wäre, die Variable im laufenden Browser
umzustellen und zu schauen, was mitgeht. Er liefert FALSCHE
Ergebnisse: Chromium erneuert Teilbäume, die per innerHTML eingefügt
wurden — Kopf- und Fusszeile stammen aus wd-core.js — nicht
zuverlässig, wenn man eine Variable nachträglich ändert. Auch ein
erzwungener Neuberechnungsstoss half nicht.
Nachgewiesen am 23.08.2026: Bei Laufzeitänderung blieben 110 Elemente
scheinbar "hängen". Lädt man dieselbe Seite dagegen mit bereits
geänderter Variable, sind sie orange. Der Fehler lag im
Messverfahren, nicht im Farbsystem.
Deshalb wird die Variable VOR dem Laden überschrieben. Das entspricht
genau dem, was eine Änderung in der CSS-Datei bewirkt — also dem
Fall, um den es wirklich geht.
===================================================================== */
import { chromium } from "playwright";
import { createServer } from "node:http";
import { readFileSync, existsSync, readdirSync } from "node:fs";
import { join, extname } from "node:path";
const W = process.cwd();
const T = { ".html": "text/html", ".css": "text/css", ".js": "text/javascript",
".png": "image/png", ".jpg": "image/jpeg", ".svg": "image/svg+xml",
".webp": "image/webp", ".ico": "image/x-icon", ".json": "application/json" };
const srv = createServer((q, r) => {
const p = join(W, decodeURIComponent(q.url.split("?")[0]));
if (existsSync(p) && !p.endsWith("/")) {
r.writeHead(200, { "content-type": T[extname(p)] || "text/plain" });
return r.end(readFileSync(p));
}
r.writeHead(404).end("");
});
await new Promise((r) => srv.listen(4332, r));
/* diagnose.html ist bewusst ausgenommen.
Sie ist das Werkzeug fuer den Fall, dass die Seite NICHT laeuft, und
traegt deshalb alles fest in sich: eigene Farben, eigenes Layout,
keine externe CSS- oder JS-Datei, keine Uebersetzung. Genau das ist
ihr Zweck -- eine Diagnoseseite, die an derselben kaputten
Stilvorlage scheitert wie das Problem, das sie untersuchen soll,
waere wertlos.
Sie deshalb an den Regeln fuer die eigentliche Seite zu messen, wuerde
zwei Meldungen erzeugen, die man auf Dauer wegsieht -- und irgendwann
sieht man dann auch eine echte weg. */
const SEITEN = readdirSync(join(W, "webdesign"))
.filter((f) => f.endsWith(".html") && f !== "diagnose.html");
const ALT = "127, 208, 232"; // der bisherige Markenton
const NEU = "255 120 40"; // drastisch anders, damit nichts untergeht
const ergebnisse = [];
function pruefe(name, ok, zusatz) {
ergebnisse.push({ name, ok });
console.log((ok ? " ok " : " FEHL ") + name + (zusatz ? " -> " + zusatz : ""));
}
const browser = await chromium.launch();
async function seiteMitFarbe(datei, umstellen) {
const s = await browser.newPage({ viewport: { width: 1280, height: 900 } });
await s.route("**/webdesign/admin/**", (r) => r.fulfill({ json: { ok: true, anfragen: [], kunden: [], projekte: [], verlaeufe: [] } }));
await s.route("**/webdesign/portal/**", (r) => r.fulfill({ json: { ok: true } }));
await s.route("**/webdesign/api-ausweis", (r) => r.fulfill({ json: {} }));
await s.addInitScript(({ um, neu }) => {
sessionStorage.setItem("wd-sitzung-aktiv", "1");
if (!um) return;
const st = document.createElement("style");
st.textContent = ":root{--wd-blau-rgb: " + neu + " !important;}";
document.addEventListener("DOMContentLoaded", () => document.head.appendChild(st));
}, { um: umstellen, neu: NEU });
await s.goto("http://127.0.0.1:4332/webdesign/" + datei);
await s.waitForTimeout(420);
return s;
}
console.log("=== Kommt eine Farbänderung überall an? ===\n");
let haengen = 0;
let betroffen = 0;
let sichtbarGesamt = 0;
for (const datei of SEITEN) {
/* Erst zählen, wie viele Elemente den Markenton ÜBERHAUPT tragen —
sonst wäre "0 hängen geblieben" auch dann erfüllt, wenn die Farbe
auf der Seite gar nicht vorkommt. */
const vorher = await seiteMitFarbe(datei, false);
const wieviele = await vorher.evaluate((alt) => {
let n = 0;
for (const el of document.querySelectorAll("*")) {
const r = el.getBoundingClientRect();
if (r.width < 1 || r.height < 1) continue;
const st = getComputedStyle(el);
if ([st.color, st.backgroundColor, st.borderTopColor, st.boxShadow, st.backgroundImage]
.join("|").includes(alt)) n++;
}
return n;
}, ALT);
await vorher.close();
betroffen += wieviele;
const nachher = await seiteMitFarbe(datei, true);
const rest = await nachher.evaluate((alt) => {
const raus = [];
let sichtbar = 0;
for (const el of document.querySelectorAll("*")) {
const r = el.getBoundingClientRect();
if (r.width < 1 || r.height < 1) continue;
sichtbar++;
const st = getComputedStyle(el);
if ([st.color, st.backgroundColor, st.borderTopColor, st.boxShadow, st.backgroundImage]
.join("|").includes(alt)) {
raus.push(el.tagName.toLowerCase() + "." + (el.className || "").toString().slice(0, 34));
}
}
return { raus, sichtbar };
}, ALT);
await nachher.close();
sichtbarGesamt += rest.sichtbar;
haengen += rest.raus.length;
if (rest.raus.length) {
console.log(` FEHL ${datei}: ${rest.raus.length} Elemente zeigen weiter den alten Ton`);
for (const h of [...new Set(rest.raus)].slice(0, 6)) console.log(" " + h);
}
}
pruefe("die Markenfarbe kommt auf vielen Elementen vor", betroffen > 200,
betroffen + " von " + sichtbarGesamt + " sichtbaren Elementen");
pruefe("nach der Umstellung bleibt KEINE Stelle beim alten Ton", haengen === 0,
haengen + " hängen geblieben");
/* ---- Die vier Prozessfarben müssen an den Markenfarben hängen ---- */
const s3 = await seiteMitFarbe("ablauf.html", false);
const prozess = await s3.evaluate(() => {
const w = getComputedStyle(document.documentElement);
const vor = ["--wd-p1", "--wd-p2", "--wd-p3", "--wd-p4"].map((v) => w.getPropertyValue(v).trim());
document.documentElement.style.setProperty("--wd-blau-rgb", "255 120 40");
document.documentElement.style.setProperty("--wd-gold-rgb", "40 255 120");
const w2 = getComputedStyle(document.documentElement);
const nach = ["--wd-p1", "--wd-p2", "--wd-p3", "--wd-p4"].map((v) => w2.getPropertyValue(v).trim());
return { vor, nach };
});
pruefe("Prozessfarbe 1 (Start) hängt an der Markenfarbe Blau",
prozess.vor[0] !== prozess.nach[0], prozess.vor[0] + " -> " + prozess.nach[0]);
pruefe("Prozessfarbe 4 (Abschluss) hängt an der Markenfarbe Gold",
prozess.vor[3] !== prozess.nach[3], prozess.vor[3] + " -> " + prozess.nach[3]);
/* ---- Radien: eine knappe Skala statt Zufallswerten ---- */
const radien = await s3.evaluate(() => {
const werte = new Set();
for (const el of document.querySelectorAll("*")) {
if (el.getBoundingClientRect().width < 1) continue;
const v = getComputedStyle(el).borderTopLeftRadius;
if (v && v !== "0px" && v.endsWith("px") && parseFloat(v) < 100) werte.add(v);
}
return Array.from(werte).sort((a, b) => parseFloat(a) - parseFloat(b));
});
pruefe("Eckenradien folgen einer knappen Skala", radien.length <= 5, radien.join(", "));
await s3.close();
await browser.close();
srv.close();
const f = ergebnisse.filter((e) => !e.ok).length;
console.log(`\n===== ${ergebnisse.length - f} bestanden, ${f} fehlgeschlagen =====`);
process.exitCode = f ? 1 : 0;