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]>
178 lines
7.7 KiB
JavaScript
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;
|