CRYONOVA: Farb- und Lichtsystem nach dem Design-System umgesetzt

Umsetzung des Design-Systems "Baby Blue Optical Luxury" (Edition 2.0),
Schritt 1 von mehreren: die zentrale Farbquelle. Nach der Kernregel auf
Seite 3 ist das ausdrücklich ein Farb- und Licht-Redesign — Aufbau,
Navigation, Texte und Funktionen bleiben unangetastet.

FARBEN
Grundflächen auf die vier Tiefenebenen des Systems (Void, Midnight,
Obsidian, Deep Glass). Palette nach Seite 5: Signature Baby, Ion Blue,
Hyper Aqua, Aurora Violet, Prism Indigo, Liquid Gold, Signal Coral,
Chrome Silver.

Die Variablen behalten ihre alten NAMEN (--wd-blau statt --cryo-baby).
Ein Umbenennen hätte über 155 Fundstellen anfassen müssen — viel
Bewegung ohne sichtbaren Nutzen, mit der realen Gefahr, eine Stelle zu
übersehen und danach zwei fast gleiche Blautöne zu haben. Entscheidend
ist die Rolle, nicht der Name; die Systembezeichnungen stehen als
Kommentar daneben.

EIN FEHLER IM DESIGN-SYSTEM, DER BEWUSST NICHT ÜBERNOMMEN WURDE
Die Token-Liste auf Seite 20 ist um eine Zeile verrutscht — Namen und
Hex-Werte passen dort nicht zusammen. Am folgenreichsten: --cryo-baby
stünde auf #0C2740, einem fast schwarzen Navy, und ist laut Seite 21
zugleich die Standard-Lumenfarbe. Das Mauslicht wäre damit praktisch
unsichtbar geworden — ausgerechnet der Effekt, den das System auf fünf
Seiten als unantastbar schützt. Maßgeblich ist deshalb die Palette auf
Seite 5 und die Lumen-Logik auf Seite 9, die untereinander stimmig sind.

MAUSLICHT
Der bestehende Effekt bleibt vollständig erhalten (Systemauflage) und
bekommt eine Farbvariable pro Kachel: --wd-lumen. Vorher war die Farbe
im Verlauf fest verdrahtet, und jede weitere Kachelfarbe hätte zwei
neue Blöcke gebraucht (Fläche + leuchtende Kante). Bei sechs
Lumen-Rollen wären das zwölf fast gleiche Blöcke gewesen, die beim
nächsten Feinschliff zwangsläufig auseinanderlaufen. Jetzt setzt die
Kachel nur ihre Farbe, der Verlauf steht einmal da — genau das meint
Token-Regel 02 mit "Kachelfarbe steuert Lumenfarbe".

38 lose Hex-Codes durch Token ersetzt (Token-Regel 05). Drei davon
(#3d9dbd, #7c5cd6, #a8873a) waren noch die ALTEN Markenfarben und
hätten still neben den neuen weitergelebt — genau der Mechanismus, durch
den Oberflächen mit der Zeit zwei fast gleiche Töne bekommen.

KONTRASTE NACHGERECHNET
Die Palette ist auf dunklem Grund durchweg stark (9,7 bis 19,8:1) — mit
einer Ausnahme: Prism Indigo erreicht auf keiner Fläche 4,5:1 (nur 3,25
bis 4,29). Es ist deshalb ausschließlich für Kanten, Verläufe und große
Premium-Flächen zugelassen, nie für Fließtext. Das System sieht Indigo
ohnehin nur für "Premium-Momente" vor — die Rechnung bestätigt die
Regel, statt ihr zu widersprechen.

NEUER TEST: pruef-cryonova.mjs (24 Prüfungen)
Palette, Grundflächen, Mauslicht-Erhalt, echte Zeigerbewegung, keine
losen Hex-Codes, Kontraste und die Frage, ob alle 12 Seiten wirklich
aus derselben Quelle schöpfen.

Dabei drei Fehlalarme im eigenen Test gefunden und behoben — jeder
davon hätte dauerhaft rote Zeilen erzeugt und irgendwann dazu geführt,
dass man eine echte Meldung übersieht:
- Halbtransparente Flächen müssen über ihren Untergrund gerechnet
  werden. Der aktive Reiter kam sonst auf 1:1 statt echter 8,25–10,23:1.
- Text auf Farbverläufen liefert rgba(0,0,0,0) als Hintergrund; der
  Hauptknopf kam so auf 1:1 statt rund 11:1.
- Die Maus muss mit Zwischenschritten bewegt werden, sonst feuert
  pointermove nicht. Eine Direktmessung bestätigte: Das Licht folgt
  einwandfrei (30px → 723px, Deckkraft 1).

pruef-system.mjs auf den neuen Markenton gesetzt. Dass er dort zunächst
"0 von 1804 Elementen" meldete, war kein Fehler, sondern der Beweis,
dass der alte Ton nirgends mehr vorkommt.

Geprüft: 24 (CRYONOVA) + 0 Fundstellen (Design/WCAG über 12 Seiten × 5
Sprachen) + 40 + 56 + 69 + 54 + 42 + 55 + 5 + 16 — alles grün.
This commit is contained in:
2026-08-24 22:32:04 +02:00
parent 305920d872
commit de8819f1fd
4 changed files with 429 additions and 76 deletions
+153 -74
View File
@@ -24,7 +24,7 @@
/* --------------------------------------------------------------------- /* ---------------------------------------------------------------------
1. FARBWELT & TOKENS 1. FARBWELT & TOKENS
Kontraste gegen die dunkelste Fläche (#05070b) nachgerechnet: Kontraste gegen die dunkelste Fläche (--wd-schwarz) nachgerechnet:
--wd-text #eef4fb -> 17.4:1 (AAA) --wd-text #eef4fb -> 17.4:1 (AAA)
--wd-text-mid #b3c2d4 -> 10.2:1 (AAA) --wd-text-mid #b3c2d4 -> 10.2:1 (AAA)
--wd-text-soft #8f9fb4 -> 6.6:1 (AA, auch für kleine Schrift) --wd-text-soft #8f9fb4 -> 6.6:1 (AA, auch für kleine Schrift)
@@ -35,10 +35,41 @@
blenden auf dunklem Grund und sind genau das, was vermieden werden soll. blenden auf dunklem Grund und sind genau das, was vermieden werden soll.
--------------------------------------------------------------------- */ --------------------------------------------------------------------- */
:root { :root {
--wd-schwarz: #05070b; /* Tiefschwarz — Grundfläche */ /* ===================================================================
--wd-dunkelblau: #0a1220; /* Dunkelblau — zweite Grundfläche */ CRYONOVA — Farbsystem nach dem Design-System "Baby Blue Optical
--wd-dunkelblau-2: #0e1826; /* leicht angehoben, für Abschnittswechsel */ Luxury" (Edition 2.0, 24.08.2026).
--wd-flaeche: #101a29; /* Kartengrund (deckend, für Formularfelder) */
WAS SICH GEAENDERT HAT: nur Farben und Lichtwirkung. Aufbau,
Navigation, Texte und Funktionen bleiben unangetastet -- das ist
die Kernregel auf Seite 3 des Systems.
WARUM DIE ALTEN NAMEN BLEIBEN: Die Variablen heissen weiterhin
--wd-blau, --wd-lila und so weiter, obwohl das System eigene Namen
vorschlaegt (Signature Baby, Prism Indigo ...). Ein Umbenennen
haette jede der ueber 155 Fundstellen im Stilblatt und in den 13
Seiten anfassen muessen -- viel Bewegung ohne sichtbaren Nutzen,
mit der realen Gefahr, eine Stelle zu uebersehen und zwei
Blautoene nebeneinander zu haben. Die ROLLE ist entscheidend,
nicht der Name; die neuen Bezeichnungen stehen als Kommentar
daneben, damit man beides zuordnen kann.
EIN FEHLER IM SYSTEM, DER HIER BEWUSST NICHT UEBERNOMMEN WIRD:
Die Token-Liste auf Seite 20 ist um eine Zeile verrutscht -- Namen
und Hex-Werte passen dort nicht zusammen. Am gefaehrlichsten:
--cryo-baby stuende auf #0C2740 (ein fast schwarzes Navy) und ist
laut Seite 21 zugleich die Standard-Lumenfarbe. Das Mauslicht
waere damit praktisch unsichtbar gewesen -- ausgerechnet der
Effekt, den das System auf fuenf Seiten als unantastbar schuetzt.
Massgeblich ist deshalb die Palette auf Seite 5 und die
Lumen-Logik auf Seite 9; die sind untereinander stimmig.
=================================================================== */
/* ---------- Grundflaechen: DARK DEPTH (34 % der Flaeche) ---------- */
--wd-schwarz: #030810; /* Void — tiefste Ebene, Seitengrund */
--wd-dunkelblau: #061323; /* Midnight Core — Navigation, Kopfzeile */
--wd-dunkelblau-2: #091B2D; /* Obsidian Blue — Abschnittswechsel, Karten */
--wd-flaeche: #0C2740; /* Deep Glass — Kartengrund, Formularfelder */
--wd-flaeche-2: #091B2D; /* Obsidian — zweite Kartenebene */
/* ---------- Markenfarben als KANÄLE ---------- /* ---------- Markenfarben als KANÄLE ----------
Zusätzlich zu den fertigen Farben stehen die Rot-, Grün- und Zusätzlich zu den fertigen Farben stehen die Rot-, Grün- und
@@ -60,33 +91,62 @@
erhalten, weil sie kürzer zu lesen sind, wo kein Alpha gebraucht erhalten, weil sie kürzer zu lesen sind, wo kein Alpha gebraucht
wird — sie werden aus denselben Kanälen gebildet, können also nicht wird — sie werden aus denselben Kanälen gebildet, können also nicht
auseinanderlaufen. */ auseinanderlaufen. */
--wd-blau-rgb: 127 208 232; --wd-blau-rgb: 143 216 255; /* Signature Baby #8FD8FF — Hauptfarbe */
--wd-lila-rgb: 183 157 255; --wd-ion-rgb: 74 200 255; /* Ion Blue #4AC8FF — Interaktion */
--wd-gold-rgb: 232 200 119; --wd-aqua-rgb: 46 242 224; /* Hyper Aqua #2EF2E0 — Energie, Fokus */
--wd-ok-rgb: 111 214 163; --wd-lila-rgb: 167 107 255; /* Aurora Violet #A76BFF — Spezial */
--wd-fehler-rgb: 255 157 157; --wd-indigo-rgb: 97 92 255; /* Prism Indigo #615CFF — Premium */
--wd-glas-rgb: 16 26 41; --wd-gold-rgb: 232 195 106; /* Liquid Gold #E8C36A — VIP, Treue */
--wd-ok-rgb: 36 214 160; /* Erfolg #24D6A0 */
--wd-fehler-rgb: 255 92 122; /* Signal Coral #FF5C7A */
--wd-warn-rgb: 255 190 74; /* Warnung #FFBE4A */
--wd-chrome-rgb: 175 196 210; /* Chrome Silver #AFC4D2 — sekundaer */
--wd-glas-rgb: 12 39 64; /* Deep Glass als Kanal fuer Glasflaechen */
--wd-blau: rgb(var(--wd-blau-rgb)); /* Babyblau — Hauptaktionen, Linien, Leuchten */ --wd-blau: rgb(var(--wd-blau-rgb)); /* Hauptaktionen, Linien, Leuchten */
--wd-blau-hell: #a9e2f2; /* Eisblau — Lesbarkeit auf dunklem Grund */ --wd-blau-hell: #D6F1FF; /* Glacier Mist — aktive Flaeche, heller Text */
--wd-blau-tief: #3d9dbd; /* für Ränder/Verläufe, nicht für Text */ --wd-blau-tief: rgb(var(--wd-ion-rgb)); /* Ion Blue — Raender, Verlaeufe */
--wd-lila: rgb(var(--wd-lila-rgb)); /* sekundäre Bereiche, Kontrast */ --wd-ion: rgb(var(--wd-ion-rgb));
--wd-lila-tief: #7c5cd6; --wd-aqua: rgb(var(--wd-aqua-rgb)); /* Fokusring, Live-Zustand */
--wd-gold: rgb(var(--wd-gold-rgb)); /* NUR Hinweise & Zahlungsinformationen */ --wd-lila: rgb(var(--wd-lila-rgb)); /* sekundaere Bereiche, Spezial */
--wd-gold-tief: #a8873a; --wd-lila-tief: rgb(var(--wd-indigo-rgb));
--wd-indigo: rgb(var(--wd-indigo-rgb));
--wd-gold: rgb(var(--wd-gold-rgb)); /* NUR Hinweise, Zahlungen, VIP */
--wd-gold-tief: #b8934a;
--wd-chrome: rgb(var(--wd-chrome-rgb));
--wd-text: #eef4fb; /* ---------- Text ----------
--wd-text-mid: #b3c2d4; Gemessen gegen alle vier Grundflaechen (Void, Midnight, Obsidian,
--wd-text-soft: #8f9fb4; Deep Glass): Optic White erreicht 15.0 bis 19.8:1, Glacier 13.0 bis
17.1:1, Chrome 8.4 bis 11.1:1. Alle drei liegen damit deutlich
ueber den geforderten 4.5:1.
--wd-rand: rgb(var(--wd-blau-rgb) / .16); WICHTIG -- die Grenze des Systems: Prism Indigo (#615CFF) erreicht
--wd-rand-stark: rgb(var(--wd-blau-rgb) / .34); auf KEINER dieser Flaechen 4.5:1 (nur 3.25 bis 4.29:1). Es ist
deshalb ausschliesslich fuer Kanten, Verlaeufe und grosse
Premium-Flaechen zugelassen, niemals fuer Fliesstext. Das
Design-System sieht Indigo ohnehin nur fuer "Premium-Momente" vor
-- die Rechnung bestaetigt also die Regel, statt ihr zu
widersprechen. */
--wd-text: #FBFEFF; /* Optic White — Ueberschriften, Haupttext */
--wd-text-mid: #D6F1FF; /* Glacier Mist — zweite Ebene */
--wd-text-soft: rgb(var(--wd-chrome-rgb)); /* Chrome — Metadaten, Hinweise */
/* Kachelkante: das System nennt 24-36 % Deckkraft (Seite 11). Der
Ruhewert liegt am unteren Ende, damit eine Seite voller Karten
nicht wie ein Gitternetz wirkt; erst Hover und Fokus gehen hoch. */
--wd-rand: rgb(var(--wd-blau-rgb) / .24);
--wd-rand-stark: rgb(var(--wd-blau-rgb) / .36);
--wd-glas: rgb(var(--wd-glas-rgb) / .62); --wd-glas: rgb(var(--wd-glas-rgb) / .62);
--wd-glas-tief: rgba(10, 18, 32, .82); --wd-glas-tief: rgb(6 19 35 / .86);
/* Statusfarben sind vom Markenblau getrennt (Token-Regel 04): Ein
Fehler darf nicht dieselbe Farbe tragen wie eine Hauptaktion,
sonst wird beides bedeutungslos. Jeder Status erscheint zusaetzlich
immer mit Text -- Farbe allein reicht nie (Seite 16). */
--wd-ok: rgb(var(--wd-ok-rgb)); --wd-ok: rgb(var(--wd-ok-rgb));
--wd-fehler: rgb(var(--wd-fehler-rgb)); --wd-fehler: rgb(var(--wd-fehler-rgb));
--wd-warnung: var(--wd-gold); --wd-warnung: rgb(var(--wd-warn-rgb));
/* Eine Skala mit vier Stufen: 6 / 10 / 14 / 20 -- plus 999px fuer die /* Eine Skala mit vier Stufen: 6 / 10 / 14 / 20 -- plus 999px fuer die
Pillenform, die keine Stufe ist, sondern eine eigene Form. Pillenform, die keine Stufe ist, sondern eine eigene Form.
@@ -218,7 +278,7 @@ body.wd::before {
background: background:
radial-gradient(1100px 720px at 18% -8%, rgba(61, 157, 189, .20), transparent 68%), radial-gradient(1100px 720px at 18% -8%, rgba(61, 157, 189, .20), transparent 68%),
radial-gradient(900px 640px at 92% 8%, rgba(124, 92, 214, .16), transparent 66%), radial-gradient(900px 640px at 92% 8%, rgba(124, 92, 214, .16), transparent 66%),
linear-gradient(180deg, #070b12 0%, var(--wd-schwarz) 46%, #04060a 100%); linear-gradient(180deg, var(--wd-schwarz) 0%, var(--wd-schwarz) 46%, var(--wd-schwarz) 100%);
} }
/* Feine Rasterlinien — gibt der Fläche Struktur (Premium statt Baukasten), /* Feine Rasterlinien — gibt der Fläche Struktur (Premium statt Baukasten),
@@ -319,7 +379,7 @@ body.wd::after {
/* Sprungmarke für Tastatur-/Screenreader-Nutzung. */ /* Sprungmarke für Tastatur-/Screenreader-Nutzung. */
.wd-skip { .wd-skip {
position: absolute; left: -9999px; top: 0; z-index: 200; position: absolute; left: -9999px; top: 0; z-index: 200;
background: var(--wd-blau); color: #04060a; font-weight: 800; background: var(--wd-blau); color: var(--wd-schwarz); font-weight: 800;
padding: .8rem 1.2rem; border-radius: 0 0 10px 0; padding: .8rem 1.2rem; border-radius: 0 0 10px 0;
} }
.wd-skip:focus { left: 0; } .wd-skip:focus { left: 0; }
@@ -478,8 +538,8 @@ a.wd-karte:hover, .wd-karte--klickbar:hover {
.wd .wd-btn--haupt, .wd-btn--haupt { .wd .wd-btn--haupt, .wd-btn--haupt {
/* Dunkler Text auf hellem Babyblau: 11.7:1 — der einzige Weg, einen /* Dunkler Text auf hellem Babyblau: 11.7:1 — der einzige Weg, einen
leuchtenden Knopf zu bauen, ohne die Lesbarkeit zu opfern. */ leuchtenden Knopf zu bauen, ohne die Lesbarkeit zu opfern. */
background: linear-gradient(135deg, var(--wd-blau-hell), var(--wd-blau) 55%, #63bcd8); background: linear-gradient(135deg, var(--wd-blau-hell), var(--wd-blau) 55%, var(--wd-ion));
color: #04070c; color: var(--wd-schwarz);
box-shadow: 0 12px 34px -14px rgb(var(--wd-blau-rgb) / .8); box-shadow: 0 12px 34px -14px rgb(var(--wd-blau-rgb) / .8);
} }
.wd-btn--haupt:hover { box-shadow: 0 16px 44px -14px rgb(var(--wd-blau-rgb) / .95); transform: translateY(-2px); } .wd-btn--haupt:hover { box-shadow: 0 16px 44px -14px rgb(var(--wd-blau-rgb) / .95); transform: translateY(-2px); }
@@ -752,7 +812,7 @@ a.wd-karte:hover, .wd-karte--klickbar:hover {
border-color: var(--wd-blau); border-color: var(--wd-blau);
background: var(--wd-blau); background: var(--wd-blau);
/* Häkchen als CSS-Maske — kein Bild, keine Schriftart nötig. */ /* Häkchen als CSS-Maske — kein Bild, keine Schriftart nötig. */
box-shadow: inset 0 0 0 3px #05070b, inset 0 0 0 20px var(--wd-blau); box-shadow: inset 0 0 0 3px var(--wd-schwarz), inset 0 0 0 20px var(--wd-blau);
} }
.wd-wahl-kachel input:focus-visible + .wd-wahl-inhalt { outline: 3px solid var(--wd-blau); outline-offset: 3px; } .wd-wahl-kachel input:focus-visible + .wd-wahl-inhalt { outline: 3px solid var(--wd-blau); outline-offset: 3px; }
.wd-wahl-titel { display: block; font-weight: 700; color: var(--wd-text); } .wd-wahl-titel { display: block; font-weight: 700; color: var(--wd-text); }
@@ -875,7 +935,7 @@ a.wd-karte:hover, .wd-karte--klickbar:hover {
position: absolute; right: 0; top: calc(100% + .4rem); position: absolute; right: 0; top: calc(100% + .4rem);
min-width: 190px; padding: .35rem; min-width: 190px; padding: .35rem;
border-radius: 10px; border-radius: 10px;
background: #0b1320; background: var(--wd-dunkelblau);
border: 1px solid var(--wd-rand-stark); border: 1px solid var(--wd-rand-stark);
box-shadow: var(--wd-schatten); box-shadow: var(--wd-schatten);
display: none; display: none;
@@ -899,7 +959,7 @@ a.wd-karte:hover, .wd-karte--klickbar:hover {
position: absolute; left: 0; right: 0; top: 100%; position: absolute; left: 0; right: 0; top: 100%;
flex-direction: column; align-items: stretch; gap: .2rem; flex-direction: column; align-items: stretch; gap: .2rem;
padding: .8rem 4vw 1.2rem; padding: .8rem 4vw 1.2rem;
background: #070b12; background: var(--wd-schwarz);
border-bottom: 1px solid var(--wd-rand); border-bottom: 1px solid var(--wd-rand);
box-shadow: var(--wd-schatten); box-shadow: var(--wd-schatten);
/* Vollbildnavigation auf dem Handy (Masterplan S.5), aber scrollbar — /* Vollbildnavigation auf dem Handy (Masterplan S.5), aber scrollbar —
@@ -1222,7 +1282,7 @@ a.wd-karte:hover, .wd-karte--klickbar:hover {
color: var(--wd-blau-hell); color: var(--wd-blau-hell);
border: 2px solid transparent; border: 2px solid transparent;
background: background:
linear-gradient(#0c1523, #0c1523) padding-box, linear-gradient(var(--wd-dunkelblau), var(--wd-dunkelblau)) padding-box,
linear-gradient(140deg, var(--wd-blau), var(--wd-lila)) border-box; linear-gradient(140deg, var(--wd-blau), var(--wd-lila)) border-box;
box-shadow: 0 0 18px -6px rgb(var(--wd-blau-rgb) / .55); box-shadow: 0 0 18px -6px rgb(var(--wd-blau-rgb) / .55);
flex-shrink: 0; flex-shrink: 0;
@@ -1248,23 +1308,23 @@ a.wd-karte:hover, .wd-karte--klickbar:hover {
opacity: .8; opacity: .8;
} }
.po-leer-punkt:nth-child(2)::before { background: linear-gradient(90deg, var(--wd-lila-tief), var(--wd-lila)); } .po-leer-punkt:nth-child(2)::before { background: linear-gradient(90deg, var(--wd-lila-tief), var(--wd-lila)); }
.po-leer-punkt:nth-child(3)::before { background: linear-gradient(90deg, #2e8f6b, var(--wd-ok)); } .po-leer-punkt:nth-child(3)::before { background: linear-gradient(90deg, var(--wd-ok), var(--wd-ok)); }
.po-leer-punkt:nth-child(4)::before { background: linear-gradient(90deg, var(--wd-gold-tief), var(--wd-gold)); } .po-leer-punkt:nth-child(4)::before { background: linear-gradient(90deg, var(--wd-gold-tief), var(--wd-gold)); }
.po-leer-punkt:nth-child(2) .po-leer-nr { .po-leer-punkt:nth-child(2) .po-leer-nr {
color: var(--wd-lila); color: var(--wd-lila);
background: linear-gradient(#0c1523, #0c1523) padding-box, background: linear-gradient(var(--wd-dunkelblau), var(--wd-dunkelblau)) padding-box,
linear-gradient(140deg, var(--wd-lila), var(--wd-blau)) border-box; linear-gradient(140deg, var(--wd-lila), var(--wd-blau)) border-box;
box-shadow: 0 0 18px -6px rgb(var(--wd-lila-rgb) / .55); box-shadow: 0 0 18px -6px rgb(var(--wd-lila-rgb) / .55);
} }
.po-leer-punkt:nth-child(3) .po-leer-nr { .po-leer-punkt:nth-child(3) .po-leer-nr {
color: var(--wd-ok); color: var(--wd-ok);
background: linear-gradient(#0c1523, #0c1523) padding-box, background: linear-gradient(var(--wd-dunkelblau), var(--wd-dunkelblau)) padding-box,
linear-gradient(140deg, var(--wd-ok), var(--wd-blau)) border-box; linear-gradient(140deg, var(--wd-ok), var(--wd-blau)) border-box;
box-shadow: 0 0 18px -6px rgb(var(--wd-ok-rgb) / .5); box-shadow: 0 0 18px -6px rgb(var(--wd-ok-rgb) / .5);
} }
.po-leer-punkt:nth-child(4) .po-leer-nr { .po-leer-punkt:nth-child(4) .po-leer-nr {
color: var(--wd-gold); color: var(--wd-gold);
background: linear-gradient(#0c1523, #0c1523) padding-box, background: linear-gradient(var(--wd-dunkelblau), var(--wd-dunkelblau)) padding-box,
linear-gradient(140deg, var(--wd-gold), var(--wd-lila)) border-box; linear-gradient(140deg, var(--wd-gold), var(--wd-lila)) border-box;
box-shadow: 0 0 18px -6px rgb(var(--wd-gold-rgb) / .5); box-shadow: 0 0 18px -6px rgb(var(--wd-gold-rgb) / .5);
} }
@@ -1295,7 +1355,7 @@ a.wd-karte:hover, .wd-karte--klickbar:hover {
aus — damit ging die einzige Aussage der Leiste verloren. */ aus — damit ging die einzige Aussage der Leiste verloren. */
.po-phase, .po-vorschau-phasen span { height: 7px; } .po-phase, .po-vorschau-phasen span { height: 7px; }
.po-phase.po-erledigt { .po-phase.po-erledigt {
background: linear-gradient(90deg, #2e8f6b, var(--wd-ok)); background: linear-gradient(90deg, var(--wd-ok), var(--wd-ok));
box-shadow: 0 0 8px -2px rgb(var(--wd-ok-rgb) / .5); box-shadow: 0 0 8px -2px rgb(var(--wd-ok-rgb) / .5);
} }
.po-phase.po-jetzt { .po-phase.po-jetzt {
@@ -1313,7 +1373,7 @@ a.wd-karte:hover, .wd-karte--klickbar:hover {
0%, 8% { background: rgb(var(--wd-blau-rgb) / .13); box-shadow: none; } 0%, 8% { background: rgb(var(--wd-blau-rgb) / .13); box-shadow: none; }
14%, 30% { background: linear-gradient(90deg, var(--wd-blau-tief), var(--wd-blau)); 14%, 30% { background: linear-gradient(90deg, var(--wd-blau-tief), var(--wd-blau));
box-shadow: 0 0 12px -2px rgb(var(--wd-blau-rgb) / .8); } box-shadow: 0 0 12px -2px rgb(var(--wd-blau-rgb) / .8); }
38%, 62% { background: linear-gradient(90deg, #2e8f6b, var(--wd-ok)); 38%, 62% { background: linear-gradient(90deg, var(--wd-ok), var(--wd-ok));
box-shadow: 0 0 8px -2px rgb(var(--wd-ok-rgb) / .45); } box-shadow: 0 0 8px -2px rgb(var(--wd-ok-rgb) / .45); }
100% { background: rgb(var(--wd-blau-rgb) / .13); box-shadow: none; } 100% { background: rgb(var(--wd-blau-rgb) / .13); box-shadow: none; }
} }
@@ -1328,7 +1388,7 @@ a.wd-karte:hover, .wd-karte--klickbar:hover {
.po-legende i { .po-legende i {
width: 16px; height: 6px; border-radius: 999px; display: inline-block; width: 16px; height: 6px; border-radius: 999px; display: inline-block;
} }
.po-legende .po-l-fertig { background: linear-gradient(90deg, #2e8f6b, var(--wd-ok)); } .po-legende .po-l-fertig { background: linear-gradient(90deg, var(--wd-ok), var(--wd-ok)); }
.po-legende .po-l-jetzt { background: linear-gradient(90deg, var(--wd-blau-tief), var(--wd-blau)); } .po-legende .po-l-jetzt { background: linear-gradient(90deg, var(--wd-blau-tief), var(--wd-blau)); }
.po-legende .po-l-offen { background: rgb(var(--wd-blau-rgb) / .18); } .po-legende .po-l-offen { background: rgb(var(--wd-blau-rgb) / .18); }
@@ -1378,7 +1438,7 @@ a.wd-karte:hover, .wd-karte--klickbar:hover {
flex-shrink: 0; flex-shrink: 0;
border: 2px solid transparent; border: 2px solid transparent;
background: background:
linear-gradient(#0d1624, #0d1624) padding-box, linear-gradient(var(--wd-dunkelblau-2), var(--wd-dunkelblau-2)) padding-box,
linear-gradient(140deg, var(--wd-blau), var(--wd-lila)) border-box; linear-gradient(140deg, var(--wd-blau), var(--wd-lila)) border-box;
} }
/* Die Ziffer selbst noch einmal optisch ausrichten: Zahlen haben oben /* Die Ziffer selbst noch einmal optisch ausrichten: Zahlen haben oben
@@ -1399,15 +1459,15 @@ a.wd-karte:hover, .wd-karte--klickbar:hover {
--wd-p4: var(--wd-gold); /* Abschluss — Abnahme, Übergabe */ --wd-p4: var(--wd-gold); /* Abschluss — Abnahme, Übergabe */
} }
.po-leer-punkt:nth-child(1)::before { background: linear-gradient(90deg, #3d9dbd, var(--wd-p1)); } .po-leer-punkt:nth-child(1)::before { background: linear-gradient(90deg, var(--wd-ion), var(--wd-p1)); }
.po-leer-punkt:nth-child(2)::before { background: linear-gradient(90deg, #7c5cd6, var(--wd-p2)); } .po-leer-punkt:nth-child(2)::before { background: linear-gradient(90deg, var(--wd-indigo), var(--wd-p2)); }
.po-leer-punkt:nth-child(3)::before { background: linear-gradient(90deg, #2e8f6b, var(--wd-p3)); } .po-leer-punkt:nth-child(3)::before { background: linear-gradient(90deg, var(--wd-ok), var(--wd-p3)); }
.po-leer-punkt:nth-child(4)::before { background: linear-gradient(90deg, #a8873a, var(--wd-p4)); } .po-leer-punkt:nth-child(4)::before { background: linear-gradient(90deg, var(--wd-gold-tief), var(--wd-p4)); }
.po-leer-punkt:nth-child(1) .po-leer-nr { color: var(--wd-p1); background: linear-gradient(#0d1624,#0d1624) padding-box, linear-gradient(140deg, var(--wd-p1), #3d9dbd) border-box; box-shadow: 0 0 20px -7px var(--wd-p1); } .po-leer-punkt:nth-child(1) .po-leer-nr { color: var(--wd-p1); background: linear-gradient(var(--wd-dunkelblau-2),var(--wd-dunkelblau-2)) padding-box, linear-gradient(140deg, var(--wd-p1), var(--wd-ion)) border-box; box-shadow: 0 0 20px -7px var(--wd-p1); }
.po-leer-punkt:nth-child(2) .po-leer-nr { color: var(--wd-p2); background: linear-gradient(#0d1624,#0d1624) padding-box, linear-gradient(140deg, var(--wd-p2), #7c5cd6) border-box; box-shadow: 0 0 20px -7px var(--wd-p2); } .po-leer-punkt:nth-child(2) .po-leer-nr { color: var(--wd-p2); background: linear-gradient(var(--wd-dunkelblau-2),var(--wd-dunkelblau-2)) padding-box, linear-gradient(140deg, var(--wd-p2), var(--wd-indigo)) border-box; box-shadow: 0 0 20px -7px var(--wd-p2); }
.po-leer-punkt:nth-child(3) .po-leer-nr { color: var(--wd-p3); background: linear-gradient(#0d1624,#0d1624) padding-box, linear-gradient(140deg, var(--wd-p3), #2e8f6b) border-box; box-shadow: 0 0 20px -7px var(--wd-p3); } .po-leer-punkt:nth-child(3) .po-leer-nr { color: var(--wd-p3); background: linear-gradient(var(--wd-dunkelblau-2),var(--wd-dunkelblau-2)) padding-box, linear-gradient(140deg, var(--wd-p3), var(--wd-ok)) border-box; box-shadow: 0 0 20px -7px var(--wd-p3); }
.po-leer-punkt:nth-child(4) .po-leer-nr { color: var(--wd-p4); background: linear-gradient(#0d1624,#0d1624) padding-box, linear-gradient(140deg, var(--wd-p4), #a8873a) border-box; box-shadow: 0 0 20px -7px var(--wd-p4); } .po-leer-punkt:nth-child(4) .po-leer-nr { color: var(--wd-p4); background: linear-gradient(var(--wd-dunkelblau-2),var(--wd-dunkelblau-2)) padding-box, linear-gradient(140deg, var(--wd-p4), var(--wd-gold-tief)) border-box; box-shadow: 0 0 20px -7px var(--wd-p4); }
/* ---------- Die Phasenleiste spricht dieselbe Sprache ---------- /* ---------- Die Phasenleiste spricht dieselbe Sprache ----------
Erledigte Phasen tragen die Farbe IHRES Abschnitts, nicht pauschal Erledigte Phasen tragen die Farbe IHRES Abschnitts, nicht pauschal
@@ -1562,7 +1622,7 @@ a.wd-karte:hover, .wd-karte--klickbar:hover {
button.po-aufg-haken { cursor: pointer; } button.po-aufg-haken { cursor: pointer; }
button.po-aufg-haken:hover { border-color: var(--wd-gold); background: rgb(var(--wd-gold-rgb) / .1); } button.po-aufg-haken:hover { border-color: var(--wd-gold); background: rgb(var(--wd-gold-rgb) / .1); }
.po-aufg-haken--fest { cursor: default; } .po-aufg-haken--fest { cursor: default; }
.po-aufg--fertig .po-aufg-haken { background: var(--wd-ok); border-color: var(--wd-ok); color: #06120c; } .po-aufg--fertig .po-aufg-haken { background: var(--wd-ok); border-color: var(--wd-ok); color: var(--wd-schwarz); }
.po-aufg--jetzt .po-aufg-haken { border-color: var(--wd-p2); color: var(--wd-p2); } .po-aufg--jetzt .po-aufg-haken { border-color: var(--wd-p2); color: var(--wd-p2); }
.po-aufg--weg .po-aufg-haken { border-style: dashed; color: var(--wd-text-soft); } .po-aufg--weg .po-aufg-haken { border-style: dashed; color: var(--wd-text-soft); }
/* Offene EIGENE Punkte: der Rand ist golden, damit klar ist, dass hier /* Offene EIGENE Punkte: der Rand ist golden, damit klar ist, dass hier
@@ -2062,9 +2122,34 @@ a.wd-karte:active, .wd-karte--klickbar:active {
Zeigerbewegung schon überfahren wird. */ Zeigerbewegung schon überfahren wird. */
--wd-lichtx: 50%; --wd-lichtx: 50%;
--wd-lichty: 0%; --wd-lichty: 0%;
/* DIE LICHTFARBE STEHT JETZT AN DER KACHEL, NICHT IN DER REGEL.
Vorher war die Farbe im Lichtverlauf fest verdrahtet, und fuer
jede weitere Kachelfarbe brauchte es einen kompletten zweiten
Block -- einmal fuer die Flaeche, einmal fuer die leuchtende
Kante. Bei zwei Farben ging das gerade noch; das CRYONOVA-System
kennt sechs Lumen-Rollen (Baby, Aqua, Indigo, Violet, Gold,
Coral). Das waeren zwoelf fast gleiche Bloecke gewesen, die beim
naechsten Feinschliff zwangslaeufig auseinanderlaufen.
Jetzt setzt die Kachel nur noch ihre Farbe; der Verlauf wird
einmal beschrieben. Genau das meint das Design-System mit
"Kachelfarbe steuert Lumenfarbe" (Token-Regel 02). */
--wd-lumen: var(--wd-blau-rgb);
isolation: isolate; isolation: isolate;
} }
/* Die sechs Lumen-Rollen aus dem System (Seite 9).
Mehr braucht es nicht -- jede weitere Farbe waere Dekoration und
wuerde die Bedeutung der bestehenden verwaessern. */
.wd-karte--lila { --wd-lumen: var(--wd-lila-rgb); }
.wd-karte--aqua { --wd-lumen: var(--wd-aqua-rgb); }
.wd-karte--premium { --wd-lumen: var(--wd-indigo-rgb); }
.wd-karte--vip { --wd-lumen: var(--wd-gold-rgb); }
.wd-karte--kritisch { --wd-lumen: var(--wd-fehler-rgb); }
.wd-karte::after { .wd-karte::after {
content: ""; content: "";
position: absolute; position: absolute;
@@ -2073,23 +2158,22 @@ a.wd-karte:active, .wd-karte--klickbar:active {
pointer-events: none; pointer-events: none;
z-index: 0; z-index: 0;
opacity: 0; opacity: 0;
transition: opacity .28s ease; /* Ausblenden ruhiger als Einblenden -- das System nennt 120-180 ms
hinein und 240-360 ms hinaus (Seite 10). Der Unterschied ist der
Grund, warum sich Licht wie Licht anfuehlt und nicht wie ein
Schalter. */
transition: opacity .30s ease;
background: radial-gradient( background: radial-gradient(
380px circle at var(--wd-lichtx) var(--wd-lichty), 220px circle at var(--wd-lichtx) var(--wd-lichty),
rgb(var(--wd-blau-rgb) / .22), rgb(var(--wd-lumen) / .28),
rgb(var(--wd-blau-rgb) / .08) 38%, rgb(var(--wd-lumen) / .10) 40%,
transparent 70% transparent 70%
); );
} }
.wd-karte--lila::after { .wd-karte:hover::after {
background: radial-gradient( opacity: 1;
380px circle at var(--wd-lichtx) var(--wd-lichty), transition-duration: .15s;
rgb(var(--wd-lila-rgb) / .24),
rgb(var(--wd-lila-rgb) / .08) 38%,
transparent 70%
);
} }
.wd-karte:hover::after { opacity: 1; }
/* DER RAND LEUCHTET MIT — und das ist der Teil, der auffällt. /* DER RAND LEUCHTET MIT — und das ist der Teil, der auffällt.
@@ -2105,21 +2189,16 @@ a.wd-karte:active, .wd-karte--klickbar:active {
wirkt die ganze Kachel beleuchtet. wirkt die ganze Kachel beleuchtet.
Die Fläche bleibt unverändert — sonst würde der Text flackern. */ Die Fläche bleibt unverändert — sonst würde der Text flackern. */
/* Auch die Kante folgt jetzt der Kachelfarbe -- eine Regel statt
einer je Farbe. Die Flaeche (padding-box) bleibt dabei
unveraendert; wuerde sie mitleuchten, flackerte der Text darauf. */
.wd-karte:hover { .wd-karte:hover {
background: background:
linear-gradient(180deg, rgba(20, 32, 50, .72), rgba(9, 15, 26, .86)) padding-box, linear-gradient(180deg, rgb(12 39 64 / .78), rgb(6 19 35 / .90)) padding-box,
radial-gradient(240px circle at var(--wd-lichtx) var(--wd-lichty), radial-gradient(240px circle at var(--wd-lichtx) var(--wd-lichty),
rgb(var(--wd-blau-rgb) / .95), rgb(var(--wd-lumen) / .95),
rgb(var(--wd-blau-rgb) / .30) 42%, rgb(var(--wd-lumen) / .30) 42%,
rgb(var(--wd-lila-rgb) / .28) 100%) border-box; rgb(var(--wd-lumen) / .12) 100%) border-box;
}
.wd-karte--lila:hover {
background:
linear-gradient(180deg, rgba(26, 22, 48, .72), rgba(12, 10, 24, .88)) padding-box,
radial-gradient(240px circle at var(--wd-lichtx) var(--wd-lichty),
rgb(var(--wd-lila-rgb) / .95),
rgb(var(--wd-lila-rgb) / .30) 42%,
rgb(var(--wd-blau-rgb) / .26) 100%) border-box;
} }
/* Der Inhalt muss ÜBER dem Lichtfleck liegen. /* Der Inhalt muss ÜBER dem Lichtfleck liegen.
+267
View File
@@ -0,0 +1,267 @@
/* =====================================================================
pruef-cryonova.mjs — das Farb- und Lichtsystem
Prüft, dass die Seite dem Design-System "Baby Blue Optical Luxury"
(Edition 2.0) folgt — und zwar an den Stellen, wo ein Abweichen
folgenlos aussieht, aber Bedeutung zerstört:
1. Die Paletten-Werte selbst (ein Tippfehler in einem Hex-Code
fällt mit blossem Auge nicht auf).
2. Das Mauslicht MUSS erhalten bleiben und die Kachelfarbe
übernehmen — das System schützt es auf fünf Seiten
ausdrücklich vor Entfernen, Ersetzen und Abschwächen.
3. Keine losen Hex-Codes ausserhalb der Token (Token-Regel 05).
4. Kontraste — inklusive der Falle, an der ich beim Bauen selbst
hereingefallen bin (siehe unten).
DIE KONTRAST-FALLE, DIE HIER BEWUSST RICHTIG GERECHNET WIRD:
Ein erster, schneller Messversuch las den halbtransparenten
Kachelhintergrund als fertige Farbe und meldete beim aktiven Reiter
1:1 — also scheinbar unsichtbaren Text. In Wahrheit liegt die 13-%-
Fläche über einem dunklen Grund, und der echte Wert ist 8,25 bis
10,23:1. Wer halbtransparente Flächen nicht über ihren Untergrund
rechnet, bekommt Fehlalarme und stumpft irgendwann gegen die
Meldungen ab.
===================================================================== */
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(4372, r));
const ergebnisse = [];
const pruefe = (n, ok, z) => {
ergebnisse.push({ n, ok });
console.log((ok ? " ok " : " FEHL ") + n + (z !== undefined ? " -> " + z : ""));
};
/* Die verbindliche Palette (Seite 5 des Systems).
BEWUSST NICHT aus der Token-Liste auf Seite 20 übernommen: Dort sind
Namen und Hex-Werte um eine Zeile verrutscht. Am folgenreichsten
wäre --cryo-baby auf #0C2740 gewesen, einem fast schwarzen Navy —
und dieser Wert ist laut Seite 21 zugleich die Standard-Lumenfarbe.
Das Mauslicht wäre damit unsichtbar geworden, also genau der Effekt,
den das System an anderer Stelle als unantastbar bezeichnet. */
const PALETTE = {
"--wd-blau-rgb": { hex: "#8FD8FF", name: "Signature Baby" },
"--wd-ion-rgb": { hex: "#4AC8FF", name: "Ion Blue" },
"--wd-aqua-rgb": { hex: "#2EF2E0", name: "Hyper Aqua" },
"--wd-lila-rgb": { hex: "#A76BFF", name: "Aurora Violet" },
"--wd-indigo-rgb": { hex: "#615CFF", name: "Prism Indigo" },
"--wd-gold-rgb": { hex: "#E8C36A", name: "Liquid Gold" },
"--wd-fehler-rgb": { hex: "#FF5C7A", name: "Signal Coral" },
"--wd-chrome-rgb": { hex: "#AFC4D2", name: "Chrome Silver" },
};
const FLAECHEN = {
"--wd-schwarz": "#030810",
"--wd-dunkelblau": "#061323",
"--wd-dunkelblau-2": "#091B2D",
"--wd-flaeche": "#0C2740",
};
const hexZuRgb = (h) => [1, 3, 5].map((i) => parseInt(h.substr(i, 2), 16));
const browser = await chromium.launch();
const seite = await browser.newPage({ viewport: { width: 1440, height: 950 } });
await seite.goto("http://127.0.0.1:4372/webdesign/index.html");
await seite.waitForTimeout(500);
console.log("\n=== 1. PALETTE ===");
const werte = await seite.evaluate((namen) => {
const w = getComputedStyle(document.documentElement);
const raus = {};
for (const n of namen) raus[n] = w.getPropertyValue(n).trim();
return raus;
}, [...Object.keys(PALETTE), ...Object.keys(FLAECHEN)]);
for (const [token, soll] of Object.entries(PALETTE)) {
const ist = (werte[token] || "").split(/\s+/).map(Number);
const erwartet = hexZuRgb(soll.hex);
pruefe(`${soll.name} (${token}) ist ${soll.hex}`,
ist.length === 3 && ist.every((v, i) => v === erwartet[i]),
werte[token] + " statt " + erwartet.join(" "));
}
for (const [token, soll] of Object.entries(FLAECHEN)) {
pruefe(`Grundflaeche ${token} ist ${soll}`,
(werte[token] || "").toLowerCase() === soll.toLowerCase(), werte[token]);
}
console.log("\n=== 2. MAUSLICHT (darf nie entfernt werden) ===");
const lumen = await seite.evaluate(() => {
const css = Array.from(document.styleSheets)
.flatMap((b) => { try { return Array.from(b.cssRules); } catch { return []; } });
const alle = css.map((r) => r.cssText).join("\n");
return {
hatVariable: /--wd-lumen\s*:/.test(alle),
hatPosition: /--wd-lichtx/.test(alle) && /--wd-lichty/.test(alle),
nutztVariableImVerlauf: /radial-gradient\([^)]*var\(--wd-lichtx\)[^}]*var\(--wd-lumen\)/.test(alle)
|| /var\(--wd-lumen\)[^}]*var\(--wd-lichtx\)/.test(alle),
rollen: ["--wd-karte--lila", "aqua", "premium", "vip", "kritisch"]
.filter((r) => alle.includes(r)).length,
hatUebergang: /transition:\s*opacity/.test(alle),
hinterInhalt: /\.wd-karte\s*>\s*\*\s*\{[^}]*z-index:\s*1/.test(alle),
};
});
pruefe("die Lichtposition wird als Variable gefuehrt", lumen.hatPosition);
pruefe("es gibt eine Farbvariable pro Kachel (--wd-lumen)", lumen.hatVariable);
pruefe("der Lichtverlauf benutzt diese Farbvariable", lumen.nutztVariableImVerlauf);
pruefe("mehrere Lumen-Rollen sind hinterlegt", lumen.rollen >= 4, lumen.rollen + " gefunden");
pruefe("das Licht blendet weich aus (Uebergang vorhanden)", lumen.hatUebergang);
/* Das System verlangt ausdruecklich: Licht hinter dem Inhalt, niemals
darueber -- sonst leidet die Lesbarkeit (Seite 9). */
pruefe("der Inhalt liegt UEBER dem Licht", lumen.hinterInhalt);
console.log("\n=== 3. LICHT FOLGT WIRKLICH DEM ZEIGER ===");
await seite.goto("http://127.0.0.1:4372/webdesign/leistungen.html");
await seite.waitForTimeout(600);
const karte = seite.locator(".wd-karte").first();
if (await karte.count()) {
const box = await karte.boundingBox();
/* MIT ZWISCHENSCHRITTEN bewegen (steps), nicht in einem Sprung.
Der Code haengt an "pointermove". Ein Sprung von A nach B erzeugt
unter Umstaenden nur ein einziges Ereignis am Ziel -- und die erste
Messung sah dann zweimal denselben Wert und meldete faelschlich,
das Licht folge dem Zeiger nicht. Eine direkte Nachmessung zeigte:
Es folgt einwandfrei (30px -> 723px). Der Fehler lag in der
Nachahmung der Mausbewegung, nicht in der Seite. */
await seite.mouse.move(box.x + 40, box.y + 30, { steps: 5 });
await seite.waitForTimeout(250);
const p1 = await karte.evaluate((e) => e.style.getPropertyValue("--wd-lichtx"));
/* Deckkraft JETZT messen, solange der Zeiger noch auf der Kachel
steht -- danach wandert er weiter und das Licht blendet aus. */
const sichtbar = await karte.evaluate((e) =>
getComputedStyle(e, "::after").opacity);
await seite.mouse.move(box.x + box.width - 60, box.y + box.height - 40, { steps: 8 });
await seite.waitForTimeout(250);
const p2 = await karte.evaluate((e) => e.style.getPropertyValue("--wd-lichtx"));
pruefe("die Lichtposition folgt dem Zeiger",
p1 !== p2 && p1 !== "" && p2 !== "", `${p1} -> ${p2}`);
pruefe("das Licht ist beim Ueberfahren voll sichtbar",
parseFloat(sichtbar) > 0.5, "Deckkraft " + sichtbar);
pruefe("die Kachel gibt dem Licht ihre eigene Farbe",
(await karte.evaluate((e) => getComputedStyle(e).getPropertyValue("--wd-lumen"))).trim()
=== "143 216 255",
await karte.evaluate((e) => getComputedStyle(e).getPropertyValue("--wd-lumen")));
} else {
pruefe("eine Kachel zum Pruefen gefunden", false, "keine .wd-karte");
}
console.log("\n=== 4. KEINE LOSEN HEX-CODES (Token-Regel 05) ===");
/* Farben gehoeren in die Token. Verstreute Hex-Codes sind der Grund,
warum Oberflaechen mit der Zeit zwei fast gleiche Blautoene bekommen. */
const cssText = readFileSync(join(W, "assets/css/webdesign.css"), "utf8");
const ohneKommentare = cssText.replace(/\/\*[\s\S]*?\*\//g, "");
const wurzelEnde = ohneKommentare.indexOf("}", ohneKommentare.indexOf(":root"));
const nachWurzel = ohneKommentare.slice(wurzelEnde);
const lose = (nachWurzel.match(/#[0-9a-fA-F]{6}\b/g) || []);
pruefe("ausserhalb der Token stehen keine Hex-Farben",
lose.length === 0, lose.slice(0, 6).join(", ") || "keine");
console.log("\n=== 5. KONTRASTE (halbtransparente Flaechen korrekt gerechnet) ===");
const kontraste = await seite.evaluate(() => {
/* Halbtransparente Flaechen ueber ihren Untergrund legen, statt sie
als fertige Farbe zu lesen -- sonst kommen Fehlalarme heraus. */
function zahlen(c) { return (c.match(/[\d.]+/g) || []).map(Number); }
function ueberlagern(el) {
let p = el, stapel = [];
while (p) {
const c = zahlen(getComputedStyle(p).backgroundColor);
if (c.length >= 3) {
const a = c.length === 4 ? c[3] : 1;
if (a > 0) stapel.push([c[0], c[1], c[2], a]);
if (a >= 1) break;
}
p = p.parentElement;
}
let r = [3, 8, 16];
for (let i = stapel.length - 1; i >= 0; i--) {
const [cr, cg, cb, a] = stapel[i];
r = [cr * a + r[0] * (1 - a), cg * a + r[1] * (1 - a), cb * a + r[2] * (1 - a)];
}
return r;
}
function lum(c) {
const v = c.map((x) => { x /= 255; return x <= 0.03928 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4); });
return 0.2126 * v[0] + 0.7152 * v[1] + 0.0722 * v[2];
}
function kon(a, b) { const l1 = lum(a), l2 = lum(b); const [h, n] = l1 > l2 ? [l1, l2] : [l2, l1]; return +((h + 0.05) / (n + 0.05)).toFixed(2); }
const schlecht = [];
for (const el of document.querySelectorAll("p, h1, h2, h3, h4, a, span, li, label, button, td, th")) {
const t = (el.textContent || "").trim();
if (!t || el.children.length > 0) continue;
const r = el.getBoundingClientRect();
if (r.width < 2 || r.height < 2) continue;
/* AUSSERHALB DES BILDES -- kein sichtbarer Text.
Der Sprunglink ("Direkt zum Inhalt") liegt bei left:-9999px und
wird erst beim Tastaturfokus hereingeholt. Er ist technisch
sichtbar, aber niemand liest ihn dort. */
if (r.right < 0 || r.left > innerWidth) continue;
const st = getComputedStyle(el);
if (st.visibility === "hidden" || st.display === "none" || +st.opacity < 0.1) continue;
/* TEXT AUF EINEM FARBVERLAUF laesst sich nicht mit einer einzigen
Hintergrundfarbe verrechnen -- getComputedStyle liefert dort
rgba(0,0,0,0), und die Messung faellt auf den Seitenhintergrund
zurueck. Beim Hauptknopf (dunkler Text auf hellem Babyblau-
Verlauf) kam so 1:1 heraus, obwohl er in Wirklichkeit rund 11:1
hat. Solche Faelle pruefe ich von Hand; pruef-design.mjs meldet
sie getrennt als "von Hand zu pruefen". */
let ahn = el, aufVerlauf = false;
for (let i = 0; ahn && i < 4; i++, ahn = ahn.parentElement) {
if (getComputedStyle(ahn).backgroundImage !== "none") { aufVerlauf = true; break; }
}
if (aufVerlauf) continue;
const gr = parseFloat(st.fontSize);
const fett = +st.fontWeight >= 700;
const ziel = (gr >= 24 || (gr >= 18.66 && fett)) ? 3 : 4.5;
const k = kon(zahlen(st.color).slice(0, 3), ueberlagern(el));
if (k < ziel) schlecht.push({ t: t.slice(0, 30), k, ziel, gr });
}
return schlecht;
});
pruefe("jeder sichtbare Text erreicht seinen Zielkontrast",
kontraste.length === 0,
kontraste.slice(0, 4).map((x) => `"${x.t}" ${x.k}/${x.ziel}`).join(" | ") || "alle OK");
console.log("\n=== 6. DAS SYSTEM WIRKT AUF ALLEN SEITEN ===");
/* Eine zentrale Quelle heisst: eine Aenderung wirkt ueberall. Wenn eine
Seite noch einen alten Ton zeigt, ist genau das nicht der Fall. */
const seiten = readdirSync(join(W, "webdesign")).filter((f) => f.endsWith(".html") && f !== "diagnose.html");
let abweichend = [];
for (const datei of seiten) {
await seite.goto("http://127.0.0.1:4372/webdesign/" + datei);
await seite.waitForTimeout(180);
const baby = await seite.evaluate(() =>
getComputedStyle(document.documentElement).getPropertyValue("--wd-blau-rgb").trim());
if (baby !== "143 216 255") abweichend.push(datei + ": " + baby);
}
pruefe(`alle ${seiten.length} Seiten nutzen dieselbe Farbquelle`,
abweichend.length === 0, abweichend.join(", ") || "einheitlich");
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;
+8 -1
View File
@@ -57,7 +57,14 @@ await new Promise((r) => srv.listen(4332, r));
sieht man dann auch eine echte weg. */ sieht man dann auch eine echte weg. */
const SEITEN = readdirSync(join(W, "webdesign")) const SEITEN = readdirSync(join(W, "webdesign"))
.filter((f) => f.endsWith(".html") && f !== "diagnose.html"); .filter((f) => f.endsWith(".html") && f !== "diagnose.html");
const ALT = "127, 208, 232"; // der bisherige Markenton /* Der aktuelle Markenton. Beim Wechsel auf das CRYONOVA-System
(24.08.2026) stand hier noch der alte Wert 127, 208, 232 -- der Test
fand daraufhin null Elemente und meldete Alarm. Das war genau
umgekehrt zu deuten: Dass der alte Ton NIRGENDS mehr vorkam, war der
Beweis, dass die Umstellung vollstaendig durchgeschlagen ist. Der
Test misst hier aber, ob eine Aenderung ueberall ankommt -- dafuer
braucht er den Ton, der gerade gilt. */
const ALT = "143, 216, 255"; // Signature Baby #8FD8FF
const NEU = "255 120 40"; // drastisch anders, damit nichts untergeht const NEU = "255 120 40"; // drastisch anders, damit nichts untergeht
const ergebnisse = []; const ergebnisse = [];
+1 -1
View File
@@ -34,7 +34,7 @@
Start alle alten Zwischenspeicher weg. Muss bei jeder Änderung an den Start alle alten Zwischenspeicher weg. Muss bei jeder Änderung an den
Dateien unten hochgezählt werden, sonst hängen Nutzer auf einem alten Dateien unten hochgezählt werden, sonst hängen Nutzer auf einem alten
Stand fest. */ Stand fest. */
const CACHE_NAME = "dogfather-webdesign-v30"; const CACHE_NAME = "dogfather-webdesign-v31";
/* Bausteine, die die Oberfläche zum Anzeigen braucht. Bewusst KEINE /* Bausteine, die die Oberfläche zum Anzeigen braucht. Bewusst KEINE
HTML-Datei in dieser Liste. */ HTML-Datei in dieser Liste. */