CRYONOVA Schritt 2: Bildwelt, Materialien, Rangsystem, Status

Setzt die restlichen Kapitel des Design-Systems um (Seiten 7, 14, 16,
19, 24-29). Wie Schritt 1 ausschliesslich Farbe, Material und Licht --
Aufbau, Navigation, Texte und Funktionen bleiben unangetastet.

BILDWELT (Seiten 24-29)
Die Motive sind jetzt echte Seitenhintergruende: Ring auf Startseite
und Portal, Kristallwelt an der Zugangswand. Ueber jedem Motiv liegt
eine Navy-Glasflaeche -- das System fordert auf Seite 31 ausdruecklich,
dass bei Bedarf "eine Navy- oder Frost-Glasflaeche zwischen Bild und
Inhalt" liegt. Ohne sie waere Text auf den hellen Kristallspitzen nicht
sicher lesbar, und genau dort macht ein schoenes Bild eine Seite
unbrauchbar.

Von 2,5 MB auf 96-229 KB (WebP), auf dem Handy 30-73 KB. Das System
nennt fuer Mobile ausdruecklich Ladezeit als Kriterium.

DAS LOGO IM BILD -- eine Entscheidung gegen die woertliche Vorgabe
Zwei der drei Motive zeigen das Dogfather-Logo gross in der Mitte. Als
Seitenhintergrund waere das ein zweites Logo neben dem echten in der
Kopfzeile: zwei Marken, die um dieselbe Aufmerksamkeit ringen. Beim
ersten Versuch schien es an der Zugangswand hinter den Karten durch
(gemessen 15,9 % helle Flaeche im Logobereich). Die Zugangswand nutzt
deshalb jetzt einen Ausschnitt der linken Bildhaelfte -- dieselbe
Kristallwelt, dasselbe Licht, nur ohne Wappen. Die Logo-Motive bleiben
als Blickfang-Klasse erhalten, dort wo sie fuer sich stehen duerfen.

MATERIALIEN (Seite 7)
Optic Glass, Frosted Ice, Prism Edge und Void Navy als Klassen. "Hell"
heisst auf einer dunklen Seite aufgehelltes Navy, nicht Weiss -- echtes
Weiss waere ein Loch im Bildschirm, und das System verbietet weisse
Vollflaechen ausdruecklich.

RANGSYSTEM (Seite 14)
Drei Stufen fehlten: Premium (Indigo), VIP (Champagne), Gefahr (Coral).
Wenn jeder Knopf gleich laut ist, ist keiner mehr laut.

Zwei bewusste Abweichungen von der naheliegenden Loesung:
- Indigo steht als FLAECHE unter hellem Text, nie als Schriftfarbe. Es
  erreicht als Text nirgends 4,5:1 (gemessen 3,25-4,29). Der Test haelt
  das dauerhaft fest.
- Gefahr ist NICHT vollflaechig rot. Ein voll gefuellter roter Knopf
  zieht den Blick staerker an als die Hauptaktion und wird dadurch
  versehentlich gedrueckt. Kritische Aktionen sollen auffindbar sein,
  nicht verlockend.

FOKUSRING (Seite 19)
Doppelter Aqua-Ring: 2 px Aqua aussen, dunkler Innenring. Kein Schmuck
-- ein einfacher heller Ring verschwindet auf hellen Flaechen, ein
dunkler auf dunklen. Die Kombination ist auf JEDEM Untergrund sichtbar.

STATUS-SPEKTRUM (Seite 16)
Acht Zustaende. Die Klasse faerbt nur -- den Text liefert immer das
Markup. Es gibt bewusst keine Variante, die nur einen farbigen Punkt
zeigt: Wer Farben nicht unterscheiden kann, saehe dann gar nichts. Der
Test prueft, dass keine Statusmarke ohne Text existiert.

Dabei einen eigenen Fehler gefunden und behoben: Ich hatte beim Bauen
des Premium-Knopfes selbst einen losen Hex-Wert eingesetzt -- genau
das, was Token-Regel 05 verbietet und was der Test dann meldete.

Geprueft: 43 (CRYONOVA, von 24 erweitert) + 0 Fundstellen (WCAG ueber
12 Seiten x 5 Sprachen, jetzt MIT Bildhintergruenden) + 55 + 40 + 69 +
54 + 42 + 16 + 5 + 20 + 64 + 58 -- alles gruen.
This commit is contained in:
2026-08-24 22:43:58 +02:00
parent de8819f1fd
commit e92daed103
14 changed files with 453 additions and 4 deletions
+130
View File
@@ -260,6 +260,136 @@ for (const datei of seiten) {
pruefe(`alle ${seiten.length} Seiten nutzen dieselbe Farbquelle`,
abweichend.length === 0, abweichend.join(", ") || "einheitlich");
console.log("\n=== 7. AKTIONEN MIT RANG (Seite 14) ===");
await seite.goto("http://127.0.0.1:4372/webdesign/index.html");
await seite.waitForTimeout(400);
const raenge = await seite.evaluate(() => {
const alle = Array.from(document.styleSheets)
.flatMap((b) => { try { return Array.from(b.cssRules); } catch { return []; } })
.map((r) => r.cssText).join("\n");
return ["haupt", "zweit", "still", "premium", "vip", "gefahr"]
.filter((r) => alle.includes(".wd-btn--" + r));
});
pruefe("alle sechs Rangstufen sind hinterlegt", raenge.length === 6, raenge.join(", "));
/* Prism Indigo erreicht als SCHRIFT nirgends 4,5:1 (3,25-4,29 gemessen).
Es darf deshalb nur als Flaeche unter hellem Text vorkommen. Diese
Pruefung haelt genau das fest -- sonst rutscht die Farbe beim
naechsten Feinschliff doch in eine Textregel. */
const indigoAlsText = await seite.evaluate(() => {
const alle = Array.from(document.styleSheets)
.flatMap((b) => { try { return Array.from(b.cssRules); } catch { return []; } })
.filter((r) => r.style && r.style.color)
.filter((r) => /--wd-indigo/.test(r.style.color));
return alle.map((r) => r.selectorText);
});
pruefe("Prism Indigo wird NIE als Schriftfarbe benutzt",
indigoAlsText.length === 0, indigoAlsText.join(", ") || "keine");
console.log("\n=== 8. MATERIALIEN (Seite 7) ===");
const materialien = await seite.evaluate(() => {
const alle = Array.from(document.styleSheets)
.flatMap((b) => { try { return Array.from(b.cssRules); } catch { return []; } })
.map((r) => r.cssText).join("\n");
return {
glas: alle.includes(".wd-glasflaeche"),
frost: alle.includes(".wd-frost"),
prisma: alle.includes(".wd-prisma"),
void_: alle.includes(".wd-void"),
/* Das System verbietet weisse Vollflaechen ausdruecklich (Seite 23
und 31). Auf einer dunklen Seite waere echtes Weiss ein Loch. */
keinWeiss: !/background:\s*(#fff|#ffffff|white)\b/i.test(alle),
};
});
pruefe("Optic Glass ist gebaut", materialien.glas);
pruefe("Frosted Ice ist gebaut", materialien.frost);
pruefe("Prism Edge ist gebaut", materialien.prisma);
pruefe("Void Navy ist gebaut", materialien.void_);
pruefe("keine weisse Vollflaeche als Hintergrund", materialien.keinWeiss);
console.log("\n=== 9. STATUS: NIE NUR FARBE (Seite 16) ===");
const status = await seite.evaluate(() => {
const alle = Array.from(document.styleSheets)
.flatMap((b) => { try { return Array.from(b.cssRules); } catch { return []; } })
.map((r) => r.cssText).join("\n");
return ["neu", "arbeit", "wartet", "freigabe", "bezahlt", "kritisch", "pausiert", "vip"]
.filter((n) => alle.includes(".wd-status--" + n));
});
pruefe("alle acht Zustaende sind hinterlegt", status.length === 8, status.length + " von 8");
/* Der Kern der Regel: Eine Statusmarke muss Text enthalten. Eine
Variante, die nur einen farbigen Punkt zeigt, waere fuer jeden
unlesbar, der Farben nicht unterscheiden kann. */
const statusOhneText = await seite.evaluate(() => {
const raus = [];
for (const el of document.querySelectorAll("[class*='wd-status--'], [class*='vw-status--'], .po-frist")) {
if (!(el.textContent || "").trim()) raus.push(el.className);
}
return raus;
});
pruefe("keine Statusmarke ohne Text", statusOhneText.length === 0,
statusOhneText.join(", ") || "alle mit Text");
console.log("\n=== 10. FOKUSRING (Seite 19) ===");
const fokus = await seite.evaluate(() => {
const el = document.querySelector("a[href], button");
if (!el) return null;
el.focus();
const st = getComputedStyle(el);
return { breite: st.outlineWidth, abstand: st.outlineOffset, farbe: st.outlineColor };
});
pruefe("der Fokusring ist mindestens 2px breit",
fokus && parseFloat(fokus.breite) >= 2, fokus && fokus.breite);
pruefe("er hat sichtbaren Abstand zum Element",
fokus && parseFloat(fokus.abstand) >= 2, fokus && fokus.abstand);
pruefe("er ist in Aqua gehalten",
fokus && /46,\s*242,\s*224/.test(fokus.farbe), fokus && fokus.farbe);
console.log("\n=== 11. BILDWELT (Seiten 24-29) ===");
/* Das System verlangt: keine leere weisse Vollflaeche, jede Seite
zeigt ein Motiv oder eine erkennbare Ableitung. Geprueft wird, dass
die Motive wirklich laden -- ein Tippfehler im Dateinamen faellt
sonst erst live auf. */
for (const [seitenname, datei] of [
["Startseite", "index.html"],
["Zugangswand", "zugang.html"],
["Portal", "portal.html"],
]) {
await seite.goto("http://127.0.0.1:4372/webdesign/" + datei);
await seite.waitForTimeout(500);
const motiv = await seite.evaluate(() => {
const el = document.querySelector(".wd-motiv");
if (!el) return null;
const vor = getComputedStyle(el, "::before");
return { bild: vor.backgroundImage, deckkraft: vor.opacity };
});
pruefe(`${seitenname}: ein Motiv ist gesetzt`,
motiv && /cryo-/.test(motiv.bild), motiv ? motiv.bild.slice(0, 50) : "keins");
/* Das Motiv darf den Text nicht ueberstrahlen -- deshalb gedaempft. */
pruefe(`${seitenname}: das Motiv ist gedaempft, nicht voll`,
motiv && parseFloat(motiv.deckkraft) < 0.7, motiv && motiv.deckkraft);
}
/* Die Bilddateien muessen wirklich existieren und klein genug sein.
Ein 2,5-MB-Hintergrund waere auf dem Handy inakzeptabel -- das
System nennt fuer Mobile ausdruecklich Ladezeit als Kriterium. */
/* cryo-kristall ist der logofreie Ausschnitt fuer die Zugangswand --
siehe Begruendung im Stilblatt: zwei Logos auf einem Bildschirm
waeren keine Wirkung, sondern ein Fehler. */
const bilder = ["cryo-ring", "cryo-wappen", "cryo-monolith", "cryo-kristall"];
let zuGross = [];
for (const b of bilder) {
for (const variante of [b + ".webp", b + "-klein.webp"]) {
const pfad = join(W, "assets/img", variante);
if (!existsSync(pfad)) { zuGross.push(variante + " FEHLT"); continue; }
const kb = readFileSync(pfad).length / 1024;
if (kb > 300) zuGross.push(variante + " " + Math.round(kb) + " KB");
}
}
pruefe("alle Motive sind vorhanden und unter 300 KB",
zuGross.length === 0, zuGross.join(", ") || "alle schlank");
await browser.close();
srv.close();
const f = ergebnisse.filter((e) => !e.ok).length;