Filipe: „fuer die modis, rechte und linke hand und dogfather eine
kachel hinzufuegst, sie soll: Notizen, heissen. ich will dass du das
auch wie ein notizblock erstellst. ich will dass es uebelst geil und
einzigartig ist."
=================================================================
TEIL 1: DIE FARBE -- UND WAS DABEI AUFFIEL
=================================================================
Fuer die neue Kachel braucht es einen Ton. Beim Suchen fiel auf, dass
tools/kachelton-entzerren.mjs zwoelf der 45 Farben fuer FREI hielt.
Sie sind es nicht. bereicheFuer() gibt fuer die fuenf Rollen des
Agenturhauses null zurueck -- das heisst „nimm die Liste aus dem
Browser", und die steht in workspace/assets/js/bereiche.js. Dort
stehen die Toene 1 bis 22 und 44: Dashboard, Zahlen, Team-Lage,
Scout-Pipeline. Kacheln, die jeden Tag jemand ansieht.
pruef-kachelfarben hatte denselben blinden Fleck. Sie meldete seit
Wochen „33 benutzte Toene" und war gruen. Was sie dadurch NICHT sah:
* Der Farblauf von gestern Nacht hat zwoelf dieser Kacheln
verschoben und drei davon unter die Buntheitsgrenze gedrueckt.
Gruen geblieben.
* Ton 7, 16 und 22 lagen SEIT JEHER unter der Grenze (0,112 / 0,116
/ 0,068). Nie gemeldet.
Das ist die Sorte gruener Haken, vor der die Hausregel warnt: Er sagt
nur, dass die Bedingung erfuellt war -- nicht, dass sie das Richtige
angesehen hat.
BEHOBEN, und zwar an der Wurzel: Die REGELN (Kontrast, Buntheit)
gelten jetzt fuer jeden Ton, der irgendwo an einer Kachel steht --
gelesen aus denselben fuenf Dateien, die auch pruef-kachel-universum
liest. Dazu die Namen der Kacheln, damit „Ton 1 traegt Dashboard"
ueberhaupt pruefbar ist.
DIE PALETTE WURDE NEU GERECHNET, mit dem dritten Verfahren an einem
Tag -- die ersten zwei stehen als Fehler im Kopf des Werkzeugs:
1. Alle 45 neu verteilt. Lief ueber zwei ausdrueckliche Wuensche
hinweg (#ff1a1a, #a8d8ff).
2. Nur die Kollisionen, aber immer nur EINEN der beiden bewegt.
Ergebnis: Ton 1 sprang vom Tuerkis ins Altrosa, 0,197 weit.
3. BEIDE duerfen sich bewegen, und zwar beide nur ein bisschen.
Zwei Toene, die 0,02 auseinanderstehen und 0,09 brauchen, teilen
sich das -- jeder rueckt 0,045, und beide bleiben, was sie waren.
kleinster Abstand 0,0154 -> 0,0905
zu blass 4 -> 0
sichtbar veraendert 7 Kacheln (ueber 0,05)
kaum zu sehen 28 (13 zwischen 0,02 und 0,05, 15 darunter)
EINE AUSNAHME MIT ZAHL, keine mit Achselzucken: Ton 1 (Dashboard)
bleibt acht Tausendstel unter der Buntheitsgrenze. Gemessen ueber ALLE
16,7 Millionen sRGB-Farben ist die naechste, die alle Regeln haelt,
#7a75c8 -- ein Blauviolett, 0,113 entfernt. Tuerkis erreicht in sRGB
schlicht keine hoehere Buntheit, und das schmale Band teilen sich
schon sieben Toene. Eine Kachel, die ihre Farbfamilie behaelt, ist die
bessere Antwort auf „richtig geil und speziell" als eine, die acht
Tausendstel bunter ist und niemand wiedererkennt. blassBis sagt jetzt
bei jeder Ausnahme, WIE WEIT sie reicht -- vorher hiess blassErlaubt
schlicht „hier wird weggesehen".
DIE NEUE FARBE IST GRUEN UND WOLLTE BERNSTEIN SEIN. Gesucht war die
Farbe von Papier. Gemessen gibt es sie nicht mehr: Der beste Bernstein
im ganzen Farbraum haelt 0,0799 Abstand -- unter der Hausgrenze. Und
Platz schaffen hilft nicht: Setzt man ihn fest, muss ein warmer Ton
das Band verlassen (Ton 45 waere 0,212 weit ins Magenta gewandert).
Die groesste wirklich freie Luecke liegt im Gruen, bei 0,0917. Ein
linierter Block in Gruen ist Papier, seit es Papier gibt.
=================================================================
TEIL 2: DER BLOCK
=================================================================
DREI ENTSCHEIDUNGEN, AUS DENEN DER REST FOLGT:
1. ES GIBT KEINEN SPEICHERN-KNOPF. Nirgends. Wer einen Block
aufschlaegt und lostippt, drueckt hinterher nicht auf „sichern";
er klappt ihn zu. Gesichert wird 800 ms nach dem letzten
Tastendruck. Geht das nicht, bleibt der Text im Browser liegen und
wird nachgereicht -- und der Fuss sagt ehrlich „noch nicht
gesichert", statt einen Verlust zu melden, den man gerade nicht
verhindern kann.
2. DAS BLATT IST EIN SCHREIBFELD MIT EINER DECKSCHICHT. Das Feld
traegt Text und Cursor und ist unsichtbar; darueber zeichnet eine
zweite Schicht denselben Text noch einmal -- mit Kaestchen,
Ueberschriften, Strichen und Links.
DARAUS FOLGT EINE EISERNE REGEL, und sie steht dreimal im Code:
KEINE Auszeichnung darf die BREITE eines Zeichens aendern. Kein
Fettdruck, keine andere Groesse, keine Sperrung. Erlaubt sind nur
Farbe, Flaeche, Rahmen und Durchstreichen. Ein einziges
font-weight: 700 verschoebe den Umbruch, und ab der zweiten Zeile
stuende der sichtbare Text neben dem Cursor.
pruef-notizen misst das am echten Umbruch: eine Probe mit allen
Auszeichnungen und einer Zeile, die umbrechen MUSS. Sind beide
Schichten verschieden hoch, sitzt der Umbruch woanders. Mit
Gegenprobe -- Fettdruck auf der Deckschicht bricht die Deckung um
genau eine Zeile (30 px), und die Zeile wird rot.
3. DIE TASTATUR FUEHRT DIE LISTE FORT, NICHT EINE LEISTE. Ein
Spiegelstrich und Enter macht den naechsten Punkt, ein leeres
Kaestchen und Enter das naechste Kaestchen -- und ein GESETZTER
Haken wird dabei nicht mitgenommen, die naechste Aufgabe ist ja
noch nicht erledigt. Zweimal Enter beendet die Liste. Ein Klick
aufs Kaestchen hakt ab. Es gibt keine Werkzeugleiste, weil man beim
Schreiben nie den Stift wechselt.
UND: NIEMAND SIEHT DIE NOTIZEN EINES ANDEREN. Auch DogFather nicht.
Es gibt in workspace-notizen.js keinen siehtAlles()-Zweig, keinen
Umschalter, keine fremde Liste -- jede Abfrage hat person_id = ? fest
eingebaut. Die Kachel steht deshalb in „Fuer dich" und nicht in
„Taeglich": Die Gruppe sagt die wichtigste Eigenschaft, bevor man sie
anfasst.
NUR AUF DER TEAM-ADRESSE. Auf der Agenturadresse ist die Seite ein
404 -- auch fuer DogFather. Das ist dieselbe Trennung wie bei Chat,
Kalender und Aufgaben, und sie steht an zwei Stellen: in
GEHOERT_ZU_ADRESSE (die Datei) und in der Schnittstelle (die Daten).
Die Seite ist nur HTML; die Daten sind die Sache.
Dazu: sechs Papierfarben, Anheften, Suche mit Hervorhebung im Text,
Papierkorb mit dreissig Tagen und einem Eintrag in der
Aufbewahrungsliste (ohne den waere die Frist ein Satz in einem
Kommentar -- genau das ist dem Support am 24.09. passiert).
=================================================================
EIN FUND BEIM BAUEN, DER ALLEN GEHOERT
=================================================================
DELETE /workspace/api/notizen/:id gab es schon -- in
workspace-aufgaben.js, fuer die Notizen AN einer Aufgabe. Weil jener
Router frueher eingehaengt ist, hat er jeden Loeschversuch des Blocks
abgefangen und mit 404 beantwortet.
Von aussen sah das aus wie ein Fehler im neuen Modul: Anlegen ging,
Aendern ging, Loeschen nicht. Man sucht dann im eigenen Code, und dort
ist nichts. Express meldet so etwas nicht -- es nimmt die erste Route,
die passt, und schweigt ueber die zweite.
Der Block liegt jetzt unter /workspace/api/notizblock. Und
pruef-notizen geht seitdem den Routenbaum von express durch und meldet
jedes Paar aus Methode und Pfad, das zweimal vergeben ist. Gemessen:
307 Wege, keine Dublette. Die Pruefung gilt fuers ganze Haus, auch
wenn sie in dieser Datei steht -- hier ist sie gefunden worden.
Nebenbei berichtigt: pruef-crew-adresse verlangte von jedem Eintrag
der Haustafel HTTP 200 mit Inhalt. Das stimmte, solange dort nur
oeffentliche Dateien standen. notizen.html ist die erste Seite hinter
der Anmeldung -- sie antwortet mit 302, und das ist richtig. Gefragt
wird jetzt, was die Tafel wirklich meint: GIBT es das hier.
GEMESSEN, alles nach den Aenderungen:
pruef-notizen 76 Punkte, 0 Fehler (neu)
pruef-kachelfarben 31 Punkte, 0 Fehler (vorher 26)
pruef-kachel-universum 13 Punkte, 0 Fehler
pruef-crew-adresse 157 Punkte, 0 Fehler
pruef-buehne 230 Punkte, 0 Fehler -- notizen.html neu in
der Liste, schlechtester Kontrast 6,73:1,
also 50 % ueber der Grenze
pruef-rechtetafel, -haus-seiten, -struktur, -css-klassen,
-jeder-hat-eine-seite, -workspace-seiten, -kachelraster,
-rueckmeldung alle 0 Fehler
Co-Authored-By: Claude Opus 5 <[email protected]>
472 lines
22 KiB
JavaScript
472 lines
22 KiB
JavaScript
/* =====================================================================
|
|
pruef-kachelfarben.mjs — Keine zwei Kacheln sehen gleich aus
|
|
---------------------------------------------------------------------
|
|
Filipe, 22.09.2026: „es sind viele kacheln die noch ähnliche farbe
|
|
haben, alle die sich ähnlich sind bitte immer verändern bis auf eins
|
|
… alle kacheln sollen eine andere farbe haben." Und: „diese zwei
|
|
kacheln sollen egal wie dan auch so bleiben wie hier erwartet."
|
|
|
|
WARUM DAS EINE PRÜFUNG BRAUCHT UND NICHT NUR EINEN LAUF
|
|
Die Farben einmal auseinanderzuziehen war eine Rechnung. Sie in
|
|
einem halben Jahr noch auseinander zu haben, ist die schwierigere
|
|
Aufgabe: Wer morgen eine Kachel anlegt, greift sich eine Tonnummer
|
|
— und merkt nicht, dass sie neben einer anderen liegt. Auf dem
|
|
Bildschirm sieht das nicht nach einem Fehler aus, sondern nach zwei
|
|
Kacheln, die man verwechselt.
|
|
|
|
Und die zwei festgelegten Töne kann ein späterer Farblauf jederzeit
|
|
überschreiben. Eine Festlegung, die nur in einem Kommentar steht,
|
|
ist eine Bitte; eine, die rot wird, ist eine Regel.
|
|
|
|
VIER FRAGEN
|
|
1. GETRENNT — kein Paar benutzter Töne liegt näher beieinander als
|
|
die Schwelle.
|
|
2. EINDEUTIG — keine Ansicht (Rolle x Haus) zeigt zwei Kacheln mit
|
|
derselben Farbe.
|
|
3. FESTGELEGT — Ton 28 trägt „Vertraulich melden", Ton 38 ist das
|
|
knallige Rot von „Draußen".
|
|
4. LESBAR UND NICHT BLASS — 4,5:1 gegen den Grund, Buntheit über
|
|
der gemessenen Schwelle.
|
|
|
|
Welche Töne benutzt werden, wird ABGELEITET (`bereicheFuer` /
|
|
`zusatzBereicheFuer` über alle Rollen und beide Häuser), nicht
|
|
aufgezählt. Eine zweite Liste wäre die, die veraltet.
|
|
===================================================================== */
|
|
|
|
import { readFileSync } from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
import { dirname, join } from "node:path";
|
|
|
|
const HIER = dirname(fileURLToPath(import.meta.url));
|
|
const CSS = join(HIER, "..", "workspace", "assets", "css", "start.css");
|
|
/* DIE SCHWELLEN STEHEN SEIT DEM 22.09.2026 IN tools/kachelton-regeln.mjs.
|
|
|
|
Sie standen vorher hier UND im Rechenwerkzeug — mit verschiedenen
|
|
Werten. Das Werkzeug suchte die Buntheit bis 0,04 herunter, diese
|
|
Prüfung verlangt 0,12; ein gerechneter Ton wurde deshalb hier
|
|
abgelehnt, obwohl er dort der beste war. Zwei Zahlen für dieselbe
|
|
Regel in zwei Dateien, die nichts voneinander wissen.
|
|
|
|
Geprüft werden nur die BENUTZTEN Töne — unter den unbenutzten
|
|
stehen noch blasse, die niemand sieht und die beim nächsten
|
|
Gebrauch ohnehin neu gerechnet werden. */
|
|
const { GRUND, MINDEST_ABSTAND, MINDEST_BUNTHEIT } =
|
|
await import("../tools/kachelton-regeln.mjs");
|
|
|
|
/* Was festgelegt ist, steht in tools/kachelton-regeln.mjs -- zusammen
|
|
mit den Schwellen und aus demselben Grund: Eine Regel, die an zwei
|
|
Orten steht, steht irgendwann in zwei Fassungen da. */
|
|
const { FESTGELEGT } = await import("../tools/kachelton-regeln.mjs");
|
|
|
|
let ok = 0, fehl = 0;
|
|
const pruef = (name, bedingung, zusatz = "") => {
|
|
if (bedingung) { ok++; console.log(` OK ${name}`); }
|
|
else { fehl++; console.log(` FEHL ${name}${zusatz ? " — " + zusatz : ""}`); }
|
|
};
|
|
|
|
/* ---- Farbrechnung (OKLab, wie in den Farbwerkzeugen) --------------- */
|
|
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 lum = (hex) => {
|
|
const [r, g, b] = srgb(hex).map(linear);
|
|
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
};
|
|
const kontrast = (hex) => (lum(hex) + 0.05) / (lum(GRUND) + 0.05);
|
|
const buntheit = (hex) => { const [, a, b] = oklab(hex); return Math.hypot(a, b); };
|
|
const abstand = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
|
|
|
|
console.log("\n===== KACHELFARBEN =====\n");
|
|
|
|
/* ---- start.css lesen: vollständig oder gar nicht ------------------- */
|
|
const css = readFileSync(CSS, "utf8");
|
|
const roh = new Set([...css.matchAll(/\[data-ton="(\d+)"\]/g)].map((m) => Number(m[1])));
|
|
const farben = new Map();
|
|
for (const zeile of css.split("\n")) {
|
|
const m = zeile.match(/\[data-ton="(\d+)"\]\s*\{\s*--ton:\s*(#[0-9a-fA-F]{6})/);
|
|
if (m) farben.set(Number(m[1]), m[2].toLowerCase());
|
|
}
|
|
/* DER DRITTE AUSGANG. Liest die Prüfung nicht alle Töne, misst sie
|
|
gegen einen Stand, den es nicht gibt — und wäre womöglich grün. */
|
|
if (roh.size !== farben.size) {
|
|
console.error(`KONNTE NICHT NACHSEHEN: ${roh.size} Toene stehen in start.css, `
|
|
+ `gelesen wurden ${farben.size}.`);
|
|
console.error(" Vermutlich hat sich die Schreibweise der Zeile geaendert.");
|
|
process.exit(2);
|
|
}
|
|
console.log(` (${farben.size} Toene in start.css gelesen)`);
|
|
|
|
/* ---- Welche Töne ein Mensch wirklich sieht (abgeleitet) ------------ */
|
|
const w = await import("./workspace.js");
|
|
const rollen = ["admin", "hand", "linke", "modi", "gast",
|
|
"manager", "scout", "creator", "spicy"];
|
|
const benutzt = new Map(); /* ton -> Set(Namen) */
|
|
const ansichten = [];
|
|
for (const r of rollen) {
|
|
for (const haus of ["crew", "agentur"]) {
|
|
const p = { id: 1, rolle: r, name: "X" };
|
|
let k = [];
|
|
try {
|
|
k = [...(w.bereicheFuer(p, haus) || []), ...(w.zusatzBereicheFuer(p, haus) || [])];
|
|
} catch { continue; }
|
|
const hier = new Map();
|
|
for (const kachel of k) {
|
|
if (!kachel || kachel.ton == null) continue;
|
|
if (!benutzt.has(kachel.ton)) benutzt.set(kachel.ton, new Set());
|
|
benutzt.get(kachel.ton).add(kachel.name);
|
|
if (!hier.has(kachel.ton)) hier.set(kachel.ton, []);
|
|
hier.get(kachel.ton).push(kachel.name);
|
|
}
|
|
if (hier.size) ansichten.push({ rolle: r, haus, toene: hier });
|
|
}
|
|
}
|
|
|
|
/* DIE ZAHL GEHÖRT IN DIE BEDINGUNG. Fände diese Prüfung null benutzte
|
|
Töne, wäre alles Folgende trivial grün — und bliebe es für immer. */
|
|
/* =====================================================================
|
|
DIE ZWEITE QUELLE -- UND DER BLINDE FLECK, DEN SIE SCHLIESST
|
|
===================================================================== (26.09.2026)
|
|
|
|
`benutzt` oben entsteht aus bereicheFuer() je Rolle. Das ist die
|
|
richtige Quelle fuer die Frage „welche Kachel traegt welchen Ton
|
|
in welcher Ansicht" -- und es ist die falsche fuer die Frage
|
|
„welche Toene sind ueberhaupt in Gebrauch".
|
|
|
|
Denn fuer die fuenf Rollen des AGENTURHAUSES gibt bereicheFuer()
|
|
`null` zurueck. Das heisst „nimm die Liste aus dem Browser", und
|
|
die steht in workspace/assets/js/bereiche.js. Dort stehen die
|
|
Toene 1 bis 22 und 44 -- zweiundzwanzig Stueck, und keiner davon
|
|
ist hier je angekommen.
|
|
|
|
WAS DAS GEKOSTET HAT: Diese Pruefung meldete „33 benutzte Toene"
|
|
und war gruen. Am 25.09. hat ein Farblauf daraufhin zwoelf
|
|
vermeintlich freie Toene verschoben und drei davon unter die
|
|
Buntheitsgrenze gedrueckt -- darunter „Dashboard", „Zahlen" und
|
|
„Scout-Pipeline", Kacheln, die jeden Tag jemand ansieht. Die
|
|
Pruefung blieb gruen. Ausserdem lagen Ton 7, 16 und 22 SEIT JEHER
|
|
unter der Grenze, ohne dass es je gemeldet wurde.
|
|
|
|
Das ist die Sorte gruener Haken, vor der die Hausregel warnt: Er
|
|
sagt nur, dass die Bedingung erfuellt war -- nicht, dass sie das
|
|
Richtige angesehen hat.
|
|
|
|
Ab hier gelten die REGELN (Kontrast, Buntheit) fuer jeden Ton, der
|
|
irgendwo an einer Kachel steht. Gelesen werden dieselben fuenf
|
|
Quellen wie in pruef-kachel-universum. Die Fragen nach ANSICHTEN
|
|
(zwei gleiche nebeneinander, die Willkommenskachel) bleiben bei
|
|
`benutzt` -- dort geht es wirklich um das, was der Server
|
|
ausgibt. */
|
|
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 IN_GEBRAUCH = new Set();
|
|
/* Und WELCHE Kachel einen Ton traegt -- auch das aus den Quellen,
|
|
nicht nur aus dem, was der Server ausgibt. Gesucht wird der Name
|
|
VOR der Tonnummer im selben Objekt; `[^{}]*?` sorgt dafuer, dass
|
|
die Suche an der naechsten geschweiften Klammer endet und nicht
|
|
den Namen der vorigen Kachel einsammelt. */
|
|
const TRAEGT = new Map();
|
|
for (const q of QUELLEN) {
|
|
const text = readFileSync(join(HIER, q), "utf8");
|
|
for (const m of text.matchAll(/ton:\s*(\d+)/g)) IN_GEBRAUCH.add(Number(m[1]));
|
|
for (const m of text.matchAll(/name:\s*["']([^"']+)["'][^{}]*?ton:\s*(\d+)/g)) {
|
|
const n = Number(m[2]);
|
|
if (!TRAEGT.has(n)) TRAEGT.set(n, new Set());
|
|
TRAEGT.get(n).add(m[1]);
|
|
}
|
|
}
|
|
pruef(`${IN_GEBRAUCH.size} Toene stehen an einer Kachel -- gelesen aus `
|
|
+ `${QUELLEN.length} Dateien, nicht nur aus dem, was der Server ausgibt`,
|
|
IN_GEBRAUCH.size >= benutzt.size,
|
|
`nur ${IN_GEBRAUCH.size} gefunden, der Server gibt schon ${benutzt.size} aus`);
|
|
|
|
pruef(`es gibt benutzte Kachelfarben (${benutzt.size} Toene, ${ansichten.length} Ansichten)`,
|
|
benutzt.size >= 20 && ansichten.length >= 5,
|
|
`nur ${benutzt.size} Toene in ${ansichten.length} Ansichten gefunden`);
|
|
|
|
/* ---- 1. Getrennt ---------------------------------------------------- */
|
|
console.log("\n=== Kein Paar sieht gleich aus ===");
|
|
{
|
|
const liste = [...benutzt.keys()]
|
|
.filter((n) => farben.has(n))
|
|
.map((n) => ({ n, hex: farben.get(n), lab: oklab(farben.get(n)) }));
|
|
pruef(`jeder benutzte Ton ist in start.css definiert (${liste.length}/${benutzt.size})`,
|
|
liste.length === benutzt.size,
|
|
"eine Kachel zeigt auf eine Tonnummer, die es nicht gibt");
|
|
|
|
let eng = { d: Infinity, a: null, b: null };
|
|
for (let i = 0; i < liste.length; i++) {
|
|
for (let j = i + 1; j < liste.length; j++) {
|
|
const d = abstand(liste[i].lab, liste[j].lab);
|
|
if (d < eng.d) eng = { d, a: liste[i], b: liste[j] };
|
|
}
|
|
}
|
|
const namen = (t) => [...(benutzt.get(t.n) || [])].join("/");
|
|
pruef(`das engste Paar liegt ueber ${MINDEST_ABSTAND} (${eng.d.toFixed(4)}: `
|
|
+ `${namen(eng.a)} <-> ${namen(eng.b)})`,
|
|
eng.d >= MINDEST_ABSTAND,
|
|
`${eng.d.toFixed(4)} zwischen Ton ${eng.a?.n} und ${eng.b?.n}`);
|
|
}
|
|
|
|
/* ---- 1b. Die Willkommenskachel traegt WIRKLICH alle Farben ---------- */
|
|
/* Filipe: „die kachel soll alle farben haben die wir benutzt haben."
|
|
---------------------------------------------------------------------
|
|
`tools/kachel-regenbogen.mjs` schreibt den Streifenblock aus den
|
|
benutzten Toenen. Einmal gelaufen ist er aber ab da eine ABSCHRIFT --
|
|
und Abschriften altern. Kommt morgen eine Kachel dazu, hat „alle
|
|
Farben" eben doch nicht alle, und niemand sieht es: 30 Streifen sehen
|
|
aus wie 31.
|
|
|
|
Deshalb wird hier nicht geprueft, ob der Block existiert, sondern ob
|
|
er noch zu den heute benutzten Toenen passt -- in BEIDE Richtungen.
|
|
Fehlt eine Farbe, ist die Kachel unvollstaendig; steht eine zu viel
|
|
darin, zeigt sie eine Farbe, die es auf der Wand nicht mehr gibt.
|
|
|
|
Die Pruefung sagt auch, was zu tun ist. Eine Pruefung, die einen
|
|
Fehler findet, ihn aber nicht behebbar macht, kostet mehr Zeit als
|
|
sie spart:
|
|
node tools/kachel-regenbogen.mjs --schreiben
|
|
-------------------------------------------------------------------- */
|
|
console.log("\n=== Die Willkommenskachel hat alle Farben ===");
|
|
{
|
|
const ANF = "/* ---- ANFANG GERECHNET: kachel-regenbogen.mjs ---- */";
|
|
const ENDE = "/* ---- ENDE GERECHNET ---- */";
|
|
const a = css.indexOf(ANF);
|
|
const e = css.indexOf(ENDE);
|
|
if (a < 0 || e < 0) {
|
|
pruef("KONNTE NICHT NACHSEHEN: der gerechnete Block steht nicht in"
|
|
+ " start.css", false,
|
|
"erwartet zwischen den Marken ANFANG/ENDE GERECHNET");
|
|
} else {
|
|
const block = css.slice(a, e);
|
|
/* Gezaehlt werden die Farben im STREIFENVERLAUF, nicht im ganzen
|
|
Block -- Lesesaum und Vignette bringen eigene Werte mit, die
|
|
nichts mit den Kacheltoenen zu tun haben. */
|
|
const imBlock = new Set(
|
|
[...block.matchAll(/color-mix\(in srgb, (#[0-9a-fA-F]{6}) /g)]
|
|
.map((m) => m[1].toLowerCase()));
|
|
const sollen = new Set([...benutzt.keys()]
|
|
.filter((n) => farben.has(n))
|
|
.map((n) => farben.get(n).toLowerCase()));
|
|
|
|
const fehlen = [...sollen].filter((f) => !imBlock.has(f));
|
|
const zuviel = [...imBlock].filter((f) => !sollen.has(f));
|
|
|
|
pruef(`die Kachel zeigt ${imBlock.size} Farben, benutzt werden ${sollen.size}`,
|
|
imBlock.size >= 20 && imBlock.size === sollen.size,
|
|
`${imBlock.size} statt ${sollen.size}`);
|
|
pruef("keine benutzte Farbe fehlt in der Kachel",
|
|
fehlen.length === 0,
|
|
`es fehlen ${fehlen.length}: ${fehlen.join(", ")}`
|
|
+ " — node tools/kachel-regenbogen.mjs --schreiben");
|
|
pruef("und keine Farbe steht darin, die niemand mehr benutzt",
|
|
zuviel.length === 0,
|
|
`${zuviel.length} zu viel: ${zuviel.join(", ")}`
|
|
+ " — node tools/kachel-regenbogen.mjs --schreiben");
|
|
|
|
/* GEGENPROBE. Eine Mengenpruefung, die nur bestaetigen kann,
|
|
bestaetigt nichts: Waere der Streifenverlauf leer, waeren alle
|
|
drei Zeilen oben trivial gruen. */
|
|
const gefaelscht = new Set([...imBlock].filter((f) => f !== [...imBlock][0]));
|
|
pruef("Gegenprobe — fehlt eine Farbe, faellt es auf",
|
|
[...sollen].filter((f) => !gefaelscht.has(f)).length > 0,
|
|
"die Mengenpruefung merkt eine fehlende Farbe nicht");
|
|
}
|
|
}
|
|
|
|
/* ---- 2. Eindeutig je Ansicht --------------------------------------- */
|
|
console.log("\n=== Keine Ansicht zeigt zwei gleiche ===");
|
|
{
|
|
const treffer = [];
|
|
for (const a of ansichten) {
|
|
for (const [ton, namen] of a.toene) {
|
|
if (namen.length > 1) treffer.push(`${a.rolle}/${a.haus}: Ton ${ton} = ${namen.join(" + ")}`);
|
|
}
|
|
}
|
|
pruef(`keine Ansicht zeigt zwei Kacheln mit derselben Farbe (${ansichten.length} geprueft)`,
|
|
treffer.length === 0, treffer.join(" | "));
|
|
}
|
|
|
|
/* ---- 3. Festgelegt -------------------------------------------------- */
|
|
console.log("\n=== Was festgelegt ist, bleibt ===");
|
|
for (const f of FESTGELEGT) {
|
|
/* AUS BEIDEN QUELLEN. Bis zum 26.09. stand hier nur `benutzt` --
|
|
die Kacheln, die der Server ausgibt. Ton 1 traegt „Dashboard"
|
|
im Agenturhaus, und das steht im Browser; die Zeile meldete
|
|
deshalb „traegt stattdessen: nichts" fuer eine Kachel, die es
|
|
gibt. */
|
|
const traeger = [...new Set([...(benutzt.get(f.ton) || []),
|
|
...(TRAEGT.get(f.ton) || [])])];
|
|
pruef(`Ton ${f.ton} traegt "${f.kachel}" (${f.warum})`,
|
|
traeger.includes(f.kachel),
|
|
`Ton ${f.ton} traegt stattdessen: ${traeger.join(", ") || "nichts"}`);
|
|
if (f.hex) {
|
|
pruef(` und hat die Farbe ${f.hex} (jetzt ${farben.get(f.ton)})`,
|
|
farben.get(f.ton) === f.hex,
|
|
`${farben.get(f.ton)} statt ${f.hex}`);
|
|
}
|
|
}
|
|
|
|
/* ---- 4. Lesbar und nicht blass -------------------------------------- */
|
|
console.log("\n=== Lesbar und kraeftig ===");
|
|
{
|
|
const dunkel = [], blass = [];
|
|
/* Die benannten Ausnahmen -- abgeleitet aus FESTGELEGT und nicht
|
|
hier noch einmal aufgezaehlt. Eine zweite Liste waere die, die
|
|
beim naechsten Mal vergessen wird. */
|
|
const darfBlassSein = new Map(
|
|
FESTGELEGT.filter((f) => f.blassErlaubt).map((f) => [f.ton, f.blassErlaubt]));
|
|
/* WIE BLASS GENAU -- eine Ausnahme mit Zahl statt eines Freibriefs
|
|
(26.09.2026). Vorher hiess `blassErlaubt` schlicht „diese
|
|
Pruefung sieht hier weg"; ein Ton haette danach beliebig
|
|
verblassen koennen, ohne dass es auffaellt. Jetzt steht neben
|
|
jeder Ausnahme, wie weit sie reicht. */
|
|
const blassBis = new Map(
|
|
FESTGELEGT.filter((f) => f.blassBis).map((f) => [f.ton, f.blassBis]));
|
|
for (const n of IN_GEBRAUCH) {
|
|
const hex = farben.get(n);
|
|
if (!hex) continue;
|
|
if (kontrast(hex) < 4.5) dunkel.push(`Ton ${n} ${hex} (${kontrast(hex).toFixed(2)}:1)`);
|
|
const grenze = blassBis.get(n) ?? MINDEST_BUNTHEIT;
|
|
if (buntheit(hex) < grenze) {
|
|
blass.push(`Ton ${n} ${hex} (${buntheit(hex).toFixed(3)}, erlaubt ab ${grenze})`);
|
|
}
|
|
}
|
|
pruef(`alle ${IN_GEBRAUCH.size} Toene an Kacheln erreichen 4,5:1 gegen den Grund`,
|
|
dunkel.length === 0, dunkel.join(", "));
|
|
pruef(`keiner ist blasser als ${MINDEST_BUNTHEIT}`
|
|
+ (darfBlassSein.size ? ` (ausser ${darfBlassSein.size} benannten)` : ""),
|
|
blass.length === 0, blass.join(", "));
|
|
|
|
/* WAS DIE AUSNAHME KOSTET, WIRD EINGEFORDERT. Ohne diese Zeile waere
|
|
`blassErlaubt` ein Freibrief: Man setzt eine Farbe fest, und die
|
|
Pruefung sieht ab da weg. So ist es umgekehrt -- wer blass sein
|
|
darf, muss beim Abstand BESSER sein als die Hausgrenze, und das
|
|
wird gemessen, nicht versprochen. */
|
|
for (const [ton, noetig] of darfBlassSein) {
|
|
const hex = farben.get(ton);
|
|
if (!hex) { pruef(`Ton ${ton} ist festgelegt, steht aber nicht in start.css`, false); continue; }
|
|
let naechste = Infinity, wer = "-";
|
|
for (const [m, h2] of farben) {
|
|
if (m === ton || !IN_GEBRAUCH.has(m)) continue;
|
|
const d = abstand(oklab(hex), oklab(h2));
|
|
if (d < naechste) { naechste = d; wer = `Ton ${m}`; }
|
|
}
|
|
pruef(`Ton ${ton} darf blass sein und haelt dafuer ${noetig} Abstand `
|
|
+ `(${naechste.toFixed(4)} zu ${wer})`, naechste >= noetig,
|
|
`nur ${naechste.toFixed(4)} -- die Ausnahme ist damit nicht mehr gedeckt`);
|
|
}
|
|
}
|
|
|
|
/* ---- Gegenprobe: kann diese Pruefung ueberhaupt rot werden? --------- */
|
|
console.log("\n=== Gegenprobe ===");
|
|
{
|
|
/* Zwei erfundene Toene, die fast gleich sind. Fände die Rechnung sie
|
|
nicht, waere jede gruene Meldung oben wertlos. */
|
|
const a = oklab("#3366cc"), b = oklab("#3467cd");
|
|
pruef(`zwei fast gleiche Farben werden als zu nah erkannt `
|
|
+ `(${abstand(a, b).toFixed(4)} < ${MINDEST_ABSTAND})`,
|
|
abstand(a, b) < MINDEST_ABSTAND);
|
|
const weit = oklab("#ff1a1a"), weit2 = oklab("#11aadb");
|
|
pruef(`zwei deutlich verschiedene nicht (${abstand(weit, weit2).toFixed(4)})`,
|
|
abstand(weit, weit2) >= MINDEST_ABSTAND);
|
|
pruef("ein zu dunkler Ton wuerde auffallen (#101820)", kontrast("#101820") < 4.5);
|
|
pruef("ein blasser Ton wuerde auffallen (#ffe5ae)", buntheit("#ffe5ae") < MINDEST_BUNTHEIT);
|
|
/* Und die Ausnahme selbst muss zu eng werden koennen. Sonst waere
|
|
die Bedingung darueber nur Text. */
|
|
{
|
|
const nah = FESTGELEGT.find((f) => f.blassErlaubt);
|
|
pruef("die Ausnahme-Bedingung schlaegt bei zu wenig Abstand an "
|
|
+ `(0.05 < ${nah?.blassErlaubt ?? "-"})`,
|
|
!!nah && 0.05 < nah.blassErlaubt);
|
|
}
|
|
}
|
|
|
|
/* =====================================================================
|
|
5. WAS AUF DEM BILDSCHIRM ANKOMMT -- der Teil, der gefehlt hat
|
|
=====================================================================
|
|
Alles bisher misst `--ton`: die ROHFARBE aus start.css. Und genau
|
|
damit war diese Pruefung gruen, waehrend Filipe am 22.09.2026 sagte:
|
|
"ich seh immer wieder sehr viele die einfach komplett die gleiche
|
|
farbe haben."
|
|
|
|
Er hatte recht. Die Kachel zeichnet ihren Ton nicht als Flaeche,
|
|
sondern als Verlauf unter einem Sternenfeld, einer Vignette und
|
|
(bis heute) einer fest blauen Wolke. Gemessen an echten
|
|
Bildpunkten: Von 0,0978 Abstand kamen 0,0141 an -- 14,4 Prozent.
|
|
ACHT Paare waren fuer ein Auge nicht mehr zu unterscheiden.
|
|
|
|
Eine Pruefung, die die Rohfarbe misst, beantwortet die Frage
|
|
"sehen die Kacheln verschieden aus" NICHT. Sie beantwortet
|
|
"stehen verschiedene Zahlen in der Datei". Das ist derselbe Fehler
|
|
wie am 01.09.2026 beim `every()` auf einem leeren Feld: Sie lief,
|
|
sie war gruen, und sie war wertlos.
|
|
|
|
Deshalb hier der Browser. Er kostet zwei Minuten -- und er ist der
|
|
einzige, der die Frage beantworten kann.
|
|
===================================================================== */
|
|
if (process.env.OHNE_BROWSER !== "ja") {
|
|
console.log("\n=== Und was davon auf dem Bildschirm ankommt ===");
|
|
const { pruefeEchteFarben } = await import("./helfer-kachel-echtfarbe.mjs");
|
|
const e = await pruefeEchteFarben();
|
|
|
|
if (!e.moeglich) {
|
|
/* DER DRITTE AUSGANG. Kein Browser, kein openssl, kein Port --
|
|
das ist "konnte nicht nachsehen", nicht "in Ordnung". */
|
|
console.log(` KONNTE NICHT NACHSEHEN: ${e.grund}`);
|
|
console.log(" (mit OHNE_BROWSER=ja laeuft die Pruefung bewusst ohne diesen Teil)");
|
|
fehl++;
|
|
} else {
|
|
pruef(`${e.anzahl} Kacheln am echten Bildschirm gemessen`, e.anzahl >= 20,
|
|
`nur ${e.anzahl} gefunden`);
|
|
|
|
/* DIE ZAHL GEHOERT IN DIE BEDINGUNG -- sonst waere dieser ganze
|
|
Abschnitt bei null Kacheln trivial gruen. */
|
|
pruef("kein Paar sieht auf dem Bildschirm gleich aus "
|
|
+ `(engstes: ${e.engstes.d.toFixed(4)}, ${e.engstes.a} <-> ${e.engstes.b})`,
|
|
e.anzahl >= 20 && e.ununterscheidbar === 0,
|
|
`${e.ununterscheidbar} von ${e.paare} Paaren unter 0,02`);
|
|
|
|
/* WIE VIEL VON DER ROHFARBE UEBERHAUPT ANKOMMT. Faellt das wieder
|
|
unter ein Drittel, ist die Gestaltung der Kachel der Grund --
|
|
und dann nuetzt die schoenste Rohfarbe nichts. */
|
|
pruef(`mindestens ein Drittel der Rohfarbe kommt an (${e.anteil.toFixed(1)} %)`,
|
|
e.anteil >= 33,
|
|
`nur ${e.anteil.toFixed(1)} % -- die Kachelgestaltung schluckt die Farbe`);
|
|
|
|
/* UND DER TEXT MUSS DARAUF LESBAR BLEIBEN.
|
|
================================================================
|
|
Diese zwei Zeilen sind die wichtigsten des ganzen Abschnitts.
|
|
Am 22.09.2026 wurde die Farbe kraeftiger gemacht -- richtig, und
|
|
gemessen. Dabei fielen 27 von 31 Kacheltexten unter 4,5:1;
|
|
vorher war es EINER. Kein Messwert hat das gemeldet, weil keine
|
|
Pruefung danach gefragt hat: `pruef-buehne` nimmt Stichproben
|
|
und traf die Kacheltexte nicht.
|
|
|
|
Farbe und Lesbarkeit gehoeren in DIESELBE Messung. Wer sie
|
|
trennt, verbessert das eine und verliert das andere -- und
|
|
merkt es erst, wenn jemand sagt, er koenne nichts mehr lesen. */
|
|
pruef(`${e.textGemessen} Kacheltexte auf Lesbarkeit gemessen`,
|
|
e.textGemessen >= 20, `nur ${e.textGemessen}`);
|
|
pruef("jeder Kacheltext erreicht 4,5:1 auf seiner Kachel"
|
|
+ (e.textSchlechteste
|
|
? ` (schlechtester: ${e.textSchlechteste.name} ${e.textSchlechteste.kontrast.toFixed(2)}:1)`
|
|
: ""),
|
|
e.textGemessen >= 20 && e.textZuWenig === 0,
|
|
`${e.textZuWenig} zu dunkel: ${e.textNamen.join(", ")}`);
|
|
}
|
|
}
|
|
|
|
console.log(`\n ${ok} bestanden, ${fehl} fehlgeschlagen\n`);
|
|
process.exit(fehl ? 1 : 0);
|