diff --git a/server/pruef-calls-rudel.mjs b/server/pruef-calls-rudel.mjs new file mode 100644 index 00000000..213ac874 --- /dev/null +++ b/server/pruef-calls-rudel.mjs @@ -0,0 +1,312 @@ +/* ===================================================================== + pruef-calls-rudel.mjs — Calls & Protokolle bei Team Dogi + ===================================================================== + + Filipe, 22.09.2026: „ich will diese kachel vom workspace auch im team + dogi seite. ich will dass jeder immer nur seine eigenen sachen von + seinem kalender sieht mehr nicht. perfektioniere das bitte. jeder der + einen kalender hat soll auch sowas haben danke." + + Das sind ZWEI Aussagen, und beide werden hier gemessen: + + (1) WER EINEN KALENDER HAT, HAT AUCH CALLS. + Nicht „vier Listen tragen die Kachel" -- das waere eine + Abschrift, die beim naechsten neuen Rollenzuschnitt + auseinanderlaeuft. Geprueft wird die Regel: In JEDER Liste + steht Calls genau dann, wenn dort auch ein Kalender steht -- + und direkt dahinter, in derselben Gruppe. + + Gemessen vor dem Bauen: + admin 31 Kacheln, Kalender ja, Calls NEIN + hand 31 ja NEIN + linke 30 ja NEIN + modi 26 ja NEIN + gast 12 nein nein + Die Community hat keinen Kalender -- sein Satz beschreibt die + Lage also genau, und „alle bekommen es" waere falsch gewesen. + + (2) JEDER SIEHT NUR SEINE EIGENEN. + Die Regel dafuer gibt es seit dem 07.09. (`termineSichtbar`). + Sie wird hier aber nicht GELESEN, sondern am laufenden Server + ausprobiert -- mit zwei Leuten, die sich gegenseitig nicht + sehen duerfen, und einem dritten, der dabei ist und deshalb + schon. + + Eine Regel, die im Quelltext richtig steht, kann trotzdem an + einer Stelle umgangen werden, die sie nicht aufruft. Das ist + genau der Grund, warum hier eine Messung steht und kein Verweis + auf den Kommentar dort. + + WARUM node:http UND NICHT fetch: Team Dogi und die Agentur melden + sich an verschiedenen Waenden an, und welche gilt, entscheidet der + Host-Kopf. `fetch` in Node setzt den selbst und ignoriert einen + gesetzten. + + AUFRUF: node server/pruef-calls-rudel.mjs + ===================================================================== */ + +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { request as httpAnfrage } from "node:http"; +import { eigenerPort } from "./helfer-port.mjs"; +import { notbremse } from "./helfer-notbremse.mjs"; + +const PORT = await eigenerPort(import.meta, "pruef-calls-rudel"); +const ordner = mkdtempSync(join(tmpdir(), "ws-calls-")); +process.env.WORKSPACE_DB = join(ordner, "workspace.db"); +process.env.PORT = String(PORT); +process.env.SITE_ACCESS_SECRET = "lokaler-test"; +process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; + +const express = (await import("express")).default; +const ec = express.response.cookie; +express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +await import("./index.js"); +notbremse(240_000, "pruef-calls-rudel"); +await new Promise((r) => setTimeout(r, 900)); + +process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); }); +process.on("unhandledRejection", (f) => { console.error("ABSTURZ (Versprechen):", f); process.exit(7); }); + +let fehler = 0, geprueft = 0; +const melde = (t) => console.log(t); +const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; + +const CREW = "crew.dogfather-universe.com"; + +/* ===================================================================== + 1. DIE REGEL: Kalender und Calls gehoeren zusammen + ===================================================================== */ +melde("\n=== 1. Wer einen Kalender hat, hat auch Calls ==="); +const w = await import("./workspace.js"); +const { darfSeite } = await import("./rechte.js"); + +const ROLLEN = ["admin", "hand", "linke", "modi", "gast"]; +{ + let mitKalender = 0, ohneKalender = 0; + for (const rolle of ROLLEN) { + const liste = w.bereicheFuer({ rolle, haus: "crew" }) || []; + const iKal = liste.findIndex((k) => String(k.ziel || "").startsWith("kalender.html")); + const iCall = liste.findIndex((k) => String(k.ziel || "").startsWith("calls.html")); + + if (iKal >= 0) { + mitKalender++; + ok(iCall === iKal + 1, + `${rolle}: Calls steht direkt hinter dem Kalender ` + + `(Kalender #${iKal}, Calls ${iCall < 0 ? "fehlt" : "#" + iCall})`); + ok(iCall >= 0 && liste[iCall].gruppe === liste[iKal].gruppe, + `${rolle}: und in derselben Gruppe ` + + `("${liste[iCall]?.gruppe}" / "${liste[iKal].gruppe}")`); + } else { + ohneKalender++; + /* DIE GEGENRICHTUNG IST GENAUSO WICHTIG. Eine Calls-Kachel ohne + Kalender waere eine Seite ueber Termine bei jemandem, der + keine hat. */ + ok(iCall < 0, + `${rolle}: kein Kalender, also auch keine Calls ` + + `(${iCall < 0 ? "stimmt" : "aber da ist eine"})`); + } + } + /* DIE ANZAHL GEHOERT IN DIE BEDINGUNG: Ohne Rollen mit Kalender + waere die Schleife leer und trotzdem gruen. */ + ok(mitKalender >= 4 && ohneKalender >= 1, + `${mitKalender} Rollen mit Kalender, ${ohneKalender} ohne — beide Faelle kamen vor`); +} + +melde(""); +melde("=== 1b. Und wer die Kachel sieht, darf die Seite auch oeffnen ==="); +{ + for (const rolle of ROLLEN) { + const liste = w.bereicheFuer({ rolle, haus: "crew" }) || []; + const hatKachel = liste.some((k) => String(k.ziel || "").startsWith("calls.html")); + const darf = darfSeite({ rolle, haus: "crew" }, "/workspace/calls.html"); + ok(hatKachel === darf, + `${rolle}: Kachel ${hatKachel ? "ja" : "nein"} und Zutritt ${darf ? "ja" : "nein"} ` + + `— ${hatKachel === darf ? "passt zusammen" : "PASST NICHT"}`); + } + /* GEGENPROBE: Die Messung muss einen Unterschied auch sehen koennen. + Eine Seite, die es nicht gibt, darf niemand oeffnen. */ + ok(!darfSeite({ rolle: "admin", haus: "crew" }, "/workspace/gibtsnicht.html"), + "Gegenprobe: eine erfundene Seite darf auch DogFather nicht"); +} + +/* ===================================================================== + 2. AM LAUFENDEN SERVER: jeder sieht nur seine eigenen + ===================================================================== */ +const { DatabaseSync } = await import("node:sqlite"); +const { scryptSync, randomBytes, createHmac } = await import("node:crypto"); +const d = new DatabaseSync(process.env.WORKSPACE_DB); +const jetzt = new Date().toISOString(); +const schluessel = randomBytes(32).toString("hex"); +d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)" + + " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING") + .run("code_kennung_schluessel", schluessel, jetzt); +function anlegen(name, rolle, code) { + const salt = randomBytes(16).toString("hex"); + const hash = scryptSync(code, salt, 64, { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex"); + d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, code_kennung, aktiv, erstellt)" + + " VALUES (?,?,?,?,?,?,1,?)") + .run(name, rolle, hash, salt, 32768, createHmac("sha256", schluessel).update(code).digest("hex"), jetzt); + return d.prepare("SELECT last_insert_rowid() AS id").get().id; +} +const idChef = anlegen("Filipe", "admin", "CODE-DOGI-0001"); +const idHand = anlegen("Rieke", "hand", "CODE-HAND-0001"); +const idLinke = anlegen("Lino", "linke", "CODE-LINK-0001"); +const idModi = anlegen("Frida", "modi", "CODE-MODI-0001"); +d.close(); + +function roh(pfad, wie = {}) { + return new Promise((fertig, schief) => { + const daten = wie.body === undefined ? null : Buffer.from(JSON.stringify(wie.body)); + const a = httpAnfrage({ + host: "127.0.0.1", port: PORT, path: pfad, + method: wie.methode || (daten ? "POST" : "GET"), + headers: { + Host: CREW, + ...(daten ? { "Content-Type": "application/json", "Content-Length": daten.length } : {}), + ...(wie.keks ? { Cookie: wie.keks } : {}), + Origin: `https://${CREW}`, Referer: `https://${CREW}/workspace/`, + }, + }, (antwort) => { + const st = []; + antwort.on("data", (x) => st.push(x)); + antwort.on("end", () => { + const text = Buffer.concat(st).toString("utf8"); + fertig({ code: antwort.statusCode, text, + json: (() => { try { return JSON.parse(text); } catch { return null; } })() }); + }); + }); + a.on("error", schief); + if (daten) a.write(daten); + a.end(); + }); +} +async function anmelden(rolle, code) { + const r = await new Promise((fertig, schief) => { + const daten = Buffer.from(JSON.stringify({ rolle, code })); + const a = httpAnfrage({ + host: "127.0.0.1", port: PORT, path: "/workspace/api/anmelden", method: "POST", + headers: { Host: CREW, "Content-Type": "application/json", "Content-Length": daten.length, + Origin: `https://${CREW}`, Referer: `https://${CREW}/workspace/` }, + }, (antwort) => { + const st = []; + antwort.on("data", (x) => st.push(x)); + antwort.on("end", () => fertig({ code: antwort.statusCode, kopf: antwort.headers })); + }); + a.on("error", schief); + a.write(daten); a.end(); + }); + const kekse = [].concat(r.kopf?.["set-cookie"] || []); + return { keks: kekse.map((c) => c.split(";")[0]).join("; "), code: r.code }; +} + +const chef = await anmelden("admin", "CODE-DOGI-0001"); +const hand = await anmelden("hand", "CODE-HAND-0001"); +const linke = await anmelden("linke", "CODE-LINK-0001"); +const modi = await anmelden("modi", "CODE-MODI-0001"); + +melde(""); +melde("=== 2. Alle vier kommen herein ==="); +for (const [wer, s] of [["DogFather", chef], ["rechte Hand", hand], + ["linke Hand", linke], ["Modi", modi]]) { + ok(s.code === 200 && s.keks.length > 0, `${wer} ist angemeldet (${s.code})`); +} + +/* Ein Zeitpunkt, der nicht vom Kalender eingeholt wird. Ein festes + Datum waere eine Zeitbombe: Es ist richtig, bis es vorbei ist. */ +const inZweiTagen = new Date(Date.now() + 2 * 86400_000) + .toISOString().slice(0, 16); + +const callAnlegen = (keks, titel, teilnehmer) => + roh("/workspace/api/termine", { + keks, body: { titel, beginn: inZweiTagen, art: "call", dauer_min: 30, + ...(teilnehmer ? { teilnehmer } : {}) }, + }); +const callsVon = (keks) => roh("/workspace/api/calls", { keks }); + +melde(""); +melde("=== 3. Jeder sieht nur seine eigenen Calls ==="); +{ + const a = await callAnlegen(hand.keks, "Gespraech der rechten Hand"); + const b = await callAnlegen(modi.keks, "Gespraech des Modis"); + ok(a.code === 201 || a.code === 200, `die rechte Hand legt einen Call an (${a.code})`); + ok(b.code === 201 || b.code === 200, `der Modi auch (${b.code})`); + + const titelVon = (r) => { + const j = r.json || {}; + return [...(j.offen || []), ...(j.anstehend || []), ...(j.serien || []), ...(j.erledigt || [])] + .map((c) => c.titel); + }; + + const beiHand = titelVon(await callsVon(hand.keks)); + const beiModi = titelVon(await callsVon(modi.keks)); + const beiLinke = titelVon(await callsVon(linke.keks)); + + ok(beiHand.includes("Gespraech der rechten Hand"), + `die rechte Hand sieht ihren eigenen (${beiHand.length} insgesamt)`); + ok(!beiHand.includes("Gespraech des Modis"), + "und NICHT den des Modis"); + ok(beiModi.includes("Gespraech des Modis"), + `der Modi sieht seinen eigenen (${beiModi.length} insgesamt)`); + ok(!beiModi.includes("Gespraech der rechten Hand"), + "und NICHT den der rechten Hand"); + ok(beiLinke.length === 0, + `die linke Hand, die an keinem beteiligt ist, sieht keinen (${beiLinke.length})`); + + /* AUCH DOGFATHER NICHT -- und das ist die eigentliche Aussage. + Bis zum 07.09. bekam er jeden Termin von jedem. Ein Kalender, in + dem alles steht, ist kein Kalender mehr. */ + const beiChef = titelVon(await callsVon(chef.keks)); + ok(!beiChef.includes("Gespraech des Modis") + && !beiChef.includes("Gespraech der rechten Hand"), + `auch DogFather sieht die fremden nicht (${beiChef.length} bei ihm)`); +} + +melde(""); +melde("=== 4. Wer eingetragen ist, sieht ihn schon ==="); +{ + /* GEGENPROBE ZU ABSCHNITT 3. Ohne sie waere „niemand sieht etwas" + genauso gruen, wenn die Liste schlicht kaputt waere. */ + const a = await callAnlegen(hand.keks, "Gespraech zu zweit", [idLinke]); + ok(a.code === 201 || a.code === 200, `ein Call mit der linken Hand als Teilnehmerin (${a.code})`); + + const titelVon = (r) => { + const j = r.json || {}; + return [...(j.offen || []), ...(j.anstehend || []), ...(j.serien || []), ...(j.erledigt || [])] + .map((c) => c.titel); + }; + const beiLinke = titelVon(await callsVon(linke.keks)); + const beiModi = titelVon(await callsVon(modi.keks)); + + ok(beiLinke.includes("Gespraech zu zweit"), + `die linke Hand sieht ihn jetzt (${beiLinke.length})`); + ok(!beiModi.includes("Gespraech zu zweit"), + "der Modi, der nicht eingetragen ist, weiterhin nicht"); +} + +melde(""); +melde("=== 5. Die Community kommt gar nicht an die Seite ==="); +{ + /* Sie hat keinen Kalender, also auch keine Calls -- geprueft nicht + an der Kachel (die kann man verstecken), sondern an der Tuer. */ + const r = await roh("/workspace/api/calls", {}); + ok(r.code === 401 || r.code === 403 || r.code === 404, + `ohne Anmeldung kommt nichts (${r.code})`); +} + +/* ===================================================================== + ERGEBNIS + ===================================================================== */ +melde(""); +/* Die Zahl gehoert in die Bedingung, nicht nur in den Text. */ +if (geprueft < 25) { + console.log(` FEHL nur ${geprueft} Pruefungen gelaufen -- erwartet waren 25+`); + fehler++; +} +melde(fehler === 0 + ? `Alles in Ordnung. ${geprueft} Pruefungen, 0 Fehler.` + : `${fehler} FEHLER bei ${geprueft} Pruefungen.`); +melde(""); +try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ } +process.exit(fehler ? 1 : 0); diff --git a/server/pruef-kachelfarben.mjs b/server/pruef-kachelfarben.mjs index fa94ce65..bcc19643 100644 --- a/server/pruef-kachelfarben.mjs +++ b/server/pruef-kachelfarben.mjs @@ -39,21 +39,19 @@ import { dirname, join } from "node:path"; const HIER = dirname(fileURLToPath(import.meta.url)); const CSS = join(HIER, "..", "workspace", "assets", "css", "start.css"); -const GRUND = "#0a121e"; +/* DIE SCHWELLEN STEHEN SEIT DEM 22.09.2026 IN tools/kachelton-regeln.mjs. -/* DIE SCHWELLE IST GEMESSEN, NICHT GERATEN. - Vor dem Lauf vom 22.09.2026 lag das engste Paar der benutzten Töne - bei 0,0741 („Was ansteht" neben dem neuen Rot von „Draußen") — - das war der Zustand, den Filipe als „ähnlich" gemeldet hat. - Danach: 0,1028. 0,090 liegt dazwischen: Der heutige Stand hat - Luft, ein Rückfall auf „sieht gleich aus" schlägt an. */ -const MINDEST_ABSTAND = 0.090; + 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. -/* Der Median der Buntheit aller Töne lag bei 0,168, die fünf - blassesten zwischen 0,064 und 0,088. Geprüft werden nur die - BENUTZTEN — unter den unbenutzten stehen noch blasse, die niemand - sieht und die beim nächsten Gebrauch ohnehin neu gerechnet werden. */ -const MINDEST_BUNTHEIT = 0.12; + 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 — mit dem Grund, aus dem es festgelegt wurde. */ const FESTGELEGT = [ diff --git a/server/workspace.js b/server/workspace.js index 115723bc..c186d22ed 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -1588,11 +1588,99 @@ const MEINE_ENTWICKLUNG_KACHEL = { workspace-werdegang.js steht `leitung ? alle : []`, in workspace-entwicklungs-punkte.js dasselbe. Die Kachel oeffnet die Seite -- was darauf steht, entscheidet der Server. */ -const MODI_MIT_TREFF = [...MODI_BEREICHE, BEFINDEN_KACHEL, +/* ===================================================================== + CALLS & PROTOKOLLE GEHOEREN NEBEN JEDEN KALENDER (22.09.2026) + ===================================================================== + Filipe: „ich will diese kachel vom workspace auch im team dogi seite. + ich will dass jeder immer nur seine eigenen sachen von seinem + kalender sieht mehr nicht. perfektioniere das bitte. jeder der einen + kalender hat soll auch sowas haben danke." + + GEMESSEN, BEVOR GEBAUT WURDE: + + Rolle Kacheln Kalender Calls + admin 31 ja NEIN + hand 31 ja NEIN + linke 30 ja NEIN + modi 26 ja NEIN + gast 12 nein nein + + Vier Listen mit Kalender, keine mit Calls -- und die Community hat + keinen Kalender und braucht deshalb auch keine Calls. Sein Satz + „jeder der einen kalender hat" beschreibt die Lage also genau. + + DESHALB IST ES EINE REGEL UND KEINE VIERFACHE EINTRAGUNG. Vier + Stellen von Hand zu pflegen waere die Stelle, an der es beim + naechsten neuen Rollenzuschnitt auseinanderlaeuft: Einer bekommt + einen Kalender, und die Calls vergisst man. + + DIE GRUPPE WIRD MITGENOMMEN, nicht danebengeschrieben: Die neue + Kachel erbt `gruppe` und `gruppeUnter` vom Kalender, neben dem sie + steht. Zieht der Kalender eines Tages in eine andere Gruppe, zieht + sie mit. + + DER TON IST GERECHNET, NICHT UEBERNOMMEN -- und der erste Versuch + war falsch. + + Naheliegend war Ton 15, derselbe wie im anderen Haus: dieselbe + Sache, dieselbe Farbe. Als Zahl war er frei. Auf dem BILDSCHIRM + nicht: pruef-kachelfarben misst den Abstand zwischen je zwei + Kacheln an echten Bildpunkten, und 15 lag bei 0,0139 zu „Was + ansteht" -- unter der Grenze von 0,02. Zwei Kacheln, die gleich + aussehen, sind eine Kachel zu viel. + + Also ausgerechnet, welcher freie Ton am weitesten von ALLEN + benutzten wegliegt (OKLab-Abstand): + + Ton 22 #fee0b2 0,1135 aber Buntheit nur 0,068 -> zu blass + Ton 7 #098f71 0,1067 Buntheit 0,112 -> zu blass + Ton 16 #19b2c2 0,0854 Abstand unter 0,090 -> zu nah + Ton 15 #01d7e3 0,0279 (der erste Versuch) + + KEINER DER FREIEN TOENE ERFUELLTE ALLE HAUSREGELN. Die Palette + kannte 42 Toene, 31 davon im Einsatz -- und unter den elf uebrigen + war keiner, der gleichzeitig weit genug weg (>= 0,090) und bunt + genug (>= 0,12) ist. Eine 32. Kachel braucht einen 32. Ton. + + Also einen NEUEN gerechnet statt einen vorhandenen umzufaerben: + Ton 16 gehoert im anderen Haus zu „Start-Check" -- ihn zu aendern + haette dort eine Kachel umgefaerbt, nach der niemand gefragt hat. + `tools/kachel-farbe-einzeln.mjs` haelt alle vorhandenen fest und + sucht fuer EINEN die Farbe, die am weitesten von allen wegliegt: + + Ton 43 #027afb Abstand 0,0925 Buntheit 0,212 Kontrast 4,63:1 + + Alle drei Regeln erfuellt, und es bleibt ein Blau -- die Kachel ist + damit wiedererkennbar als dieselbe wie im anderen Haus. + ===================================================================== */ +const CALLS_KACHEL = { + name: "Calls & Protokolle", + unter: "Gespräche mit To-dos", + zeichen: "calls", + ton: 43, + ziel: "calls.html", + szene: "lounge", +}; + +/** Hinter jeden Kalender einer Liste eine Calls-Kachel setzen. */ +function mitCalls(liste) { + const raus = []; + for (const k of liste) { + raus.push(k); + /* Am ZIEL erkannt, nicht am Namen: Der Name einer Kachel ist im + Haus schon mehrfach gewandert, das Ziel nicht. */ + if (String(k.ziel || "").startsWith("kalender.html")) { + raus.push({ gruppe: k.gruppe, gruppeUnter: k.gruppeUnter, ...CALLS_KACHEL }); + } + } + return raus; +} + +const MODI_MIT_TREFF = mitCalls([...MODI_BEREICHE, BEFINDEN_KACHEL, ENTWICKLUNG_KACHEL, WERDEGANG_KACHEL, ...TREFF_BEREICHE, - TREFF_MODERATION_KACHEL]; -const HAND_MIT_TREFF = [...HAND_BEREICHE, ...TREFF_BEREICHE, - TREFF_MODERATION_KACHEL, HILFE_KACHEL]; + TREFF_MODERATION_KACHEL]); +const HAND_MIT_TREFF = mitCalls([...HAND_BEREICHE, ...TREFF_BEREICHE, + TREFF_MODERATION_KACHEL, HILFE_KACHEL]); /* DIE LINKE HAND SIEHT "DEIN TEAM" NICHT (22.09.2026). diff --git a/tools/kachel-farbe-einzeln.mjs b/tools/kachel-farbe-einzeln.mjs index 31e97a6f..410d3176 100644 --- a/tools/kachel-farbe-einzeln.mjs +++ b/tools/kachel-farbe-einzeln.mjs @@ -60,9 +60,11 @@ import { readFileSync, writeFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; +import { GRUND, MINDEST_BUNTHEIT, HOECHST_BUNTHEIT, MINDEST_KONTRAST } + from "./kachelton-regeln.mjs"; + const HIER = dirname(fileURLToPath(import.meta.url)); const CSS = join(HIER, "..", "workspace", "assets", "css", "start.css"); -const GRUND = "#0a121e"; /* ---------- Farbrechnung (gleich wie in kachel-farben.mjs) ---------- */ const linear = (c) => (c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4); @@ -156,7 +158,16 @@ function beste(andere) { for (let L = 0.56; L <= 0.94; L += 0.01) { /* Von oben herunter, damit die Farbe so satt wie erlaubt wird und nicht blass -- Filipe: „richtig geil und speziell“. */ - for (let C = 0.30; C >= 0.04; C -= 0.004) { + /* DIE UNTERE GRENZE IST NICHT 0,04, SONDERN DIE DER PRUEFUNG. + Bis zum 22.09.2026 lief die Suche bis 0,04 herunter -- und + lieferte fuer Ton 43 ein blasses #bcdbff mit Buntheit 0,060. + Abstand 0,1339, der beste Wert weit und breit, und + pruef-kachelfarben hat ihn trotzdem abgelehnt: zu blass. + Ein Werkzeug, dessen Vorschlaege die Pruefung danach + verwirft, ist schlimmer als keines -- man haelt das + Ergebnis fuer fertig. Beide lesen die Zahl jetzt aus + kachelton-regeln.mjs. */ + for (let C = HOECHST_BUNTHEIT; C >= MINDEST_BUNTHEIT; C -= 0.004) { const hex = ausOklch(L, C, H); if (!hex) continue; if (kontrast(hex) < 4.5) break; diff --git a/tools/kachelton-regeln.mjs b/tools/kachelton-regeln.mjs new file mode 100644 index 00000000..cb2394ec --- /dev/null +++ b/tools/kachelton-regeln.mjs @@ -0,0 +1,51 @@ +/* ===================================================================== + DIE REGELN FÜR EINEN KACHELTON — an einer Stelle (22.09.2026) + ===================================================================== + + ENTSTANDEN AUS EINEM WIDERSPRUCH, den niemand bemerkt hätte, bis er + Arbeit gekostet hat. + + Für die neue Kachel „Calls & Protokolle" musste ein Ton gerechnet + werden. `tools/kachel-farbe-einzeln.mjs` lieferte `#bcdbff` mit + einem Abstand von 0,1339 — dem besten Wert weit und breit. + `server/pruef-kachelfarben.mjs` lehnte ihn ab: + + FEHL keiner ist blasser als 0.12 — Ton 43 #bcdbff (0.060) + + Beide hatten recht. Das Werkzeug suchte die Buntheit von 0,30 bis + herunter auf 0,04, die Prüfung verlangt mindestens 0,12. Zwei + Zahlen für dieselbe Regel, in zwei Dateien, die nichts voneinander + wissen — die eine kannte die andere nicht. + + Das ist dieselbe Krankheit wie eine abgeschriebene Spaltenliste: + Es wird nicht falsch, es wird UNZUSTÄNDIG. Ein Werkzeug, das + Vorschläge macht, die die Prüfung danach ablehnt, ist schlimmer als + keines — man hält das Ergebnis für fertig. + + Deshalb stehen die Schwellen jetzt hier, und beide Seiten lesen sie + von hier. Wer eine ändert, ändert sie für alle. + ===================================================================== */ + +/** Der dunkle Grund, gegen den jede Kachelfarbe gemessen wird. */ +export const GRUND = "#0a121e"; + +/* DIE SCHWELLE IST GEMESSEN, NICHT GERATEN. + Vor dem Lauf vom 22.09.2026 lag das engste Paar der benutzten Töne + bei 0,0741 („Was ansteht" neben dem neuen Rot von „Draußen") — das + war der Zustand, den Filipe als „ähnlich" gemeldet hat. Danach: + 0,1028. 0,090 liegt dazwischen: Der heutige Stand hat Luft, ein + Rückfall auf „sieht gleich aus" schlägt an. */ +export const MINDEST_ABSTAND = 0.090; + +/* Der Median der Buntheit aller Töne lag bei 0,168, die fünf + blassesten zwischen 0,064 und 0,088. Filipe: „die farben sollen auch + richtig geil und speziell sein." Augenschonend heißt dunkles Schema + und kein Flackern — NICHT blass. */ +export const MINDEST_BUNTHEIT = 0.12; + +/* Die Obergrenze. Über 0,30 wird es auf einem dunklen Grund grell, und + grell ist das, was die Augen belastet. */ +export const HOECHST_BUNTHEIT = 0.30; + +/* Nicht verhandelbar: Eine Kachelfarbe trägt auch Text und Zahlen. */ +export const MINDEST_KONTRAST = 4.5; diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index 97e22552..99fd0a53 100644 --- a/workspace/anruf-probe.html +++ b/workspace/anruf-probe.html @@ -16,15 +16,15 @@ verdeckt. Gefunden hat es pruef-struktur. Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. --> - - + + - +