Die Bildschau und 2520 Farben statt 360

=== 1. BILDER OEFFNEN SICH MITTIG, MIT HUSKY UND HASE ===

Filipe: "wenn man bilder aufmacht, will ich dass die in der mitte sind.
beim hochformat soll links der husky sein und rechts der hase und beim
querformat soll oben dogfather und unten team dogi stehen da wo platz
ist ... ich will nicht dahin geschmissen sondern was richtig geiles mit
einem geilen hintergrund."

WAS VORHER PASSIERTE, und es war kein Fehler im Code: Der Klick fuehrte
auf die DATEI. Was man dann sah, war die eingebaute Bildanzeige des
Browsers -- schwarzer Grund, Bild oben links in der Ecke, auf seinem
Bildschirmfoto 1200 px Schwarz daneben. Eine Datei hat keine
Gestaltung. Wer Gestaltung will, muss eine SEITE zeigen.

Die Schau liegt jetzt UEBER dem Chat. Escape bringt einen dorthin
zurueck, wo man war, und das Gespraech laeuft im Hintergrund weiter --
ein zweiter Tab braeuchte eine eigene Seite, eine eigene Anmeldung und
einen eigenen Weg zurueck.

DER WEG IN DEN TAB BLEIBT TROTZDEM. Der Verweis hat unveraendert
`href`, `target` und `rel`; mittlere Maustaste, "In neuem Tab oeffnen"
und Herunterladen gehen wie seit jeher. Abgefangen wird nur der
gewoehnliche Klick -- und auch der nur, wenn die Schau geladen ist.
Wer Strg, Umschalt oder Befehl haelt, bekommt den alten Weg; das sind
die Griffe, die Leute seit zwanzig Jahren benutzen.

DIE BEGLEITER STEHEN DA, WO PLATZ IST -- und die Antwort darauf wird
GEMESSEN, nicht geschaetzt: nicht die Fensterbreite, sondern der Rest
neben dem Bild, nachdem es eingepasst ist. Eine Schwelle wie "ab
1100 px" waere fuer ein 3:4-Foto richtig und fuer ein 9:16-Foto auf
demselben Schirm falsch. Unter 170 px bleibt der Husky weg: Eine Figur,
die ein Streifen ist, steht besser nicht da.

Husky und Hase sind eigens verkleinert (2,1 MB und 1,0 MB werden 40 kB
und 32 kB) und bekommen weiche Raender -- ihr Hintergrund ist dunkel,
aber nicht durchsichtig; als Rechteck saehe man zwei Kaesten.

Der erste Anlauf spiegelte den Hasen, damit beide "nach innen" schauen
-- ein alter Reflex aus dem Plakatsatz. Auf dem Probebild stand danach
"Hasi Dog" seitenverkehrt auf seinem Hoodie. Schrift im Bild spiegelt
man nie.

pruef-chat-anhaenge bekommt 14 neue Punkte, darunter beide Formate und
zwei Gegenproben: Strg+Klick oeffnet die Schau NICHT (sonst hiesse "sie
geht auf" nur, dass der alte Weg verloren ist), und im Hochformat liegt
KEINE der Figuren ueber dem Foto.

NEBENBEFUND, den die Pruefung gefunden hat: Die Sprachnachricht war auf
165 px geschrumpft -- zu schmal fuer den Schieber. Ursache war keine
Aenderung an ihr, sondern eine Folge: Die Blase ist so breit wie ihr
breitester Inhalt, und seit die Handgriffe darunter Zeichen statt
Woerter sind (123 statt ueber 400 px), bestimmt das Abspielgeraet die
Breite selbst. Eine Reihe kuerzer zu machen hat einen Schieber schmaler
gemacht, drei Bildschirme weiter.

=== 2. DER FARBKREIS: HELLER UND DUNKLER ===

Filipe: "dieser kreis muss viel perfekter sein. ich will dass die leute
auch heller und dunkler aussuchen koennen ... so dass die leute viel
krassere moeglichkeiten haben."

WARUM DAS BIS HEUTE NICHT GING: Die 360 Toene liegen alle auf DERSELBEN
Leuchtdichte. Das war kein Zufall -- solange die Blase in der gewaehlten
Farbe stand, musste jede dieser Flaechen dieselbe Schrift tragen. Eine
hellere Farbe zuzulassen hiesse damals: irgendwo wird eine Nachricht
unlesbar.

SEIT DEM 25.09.2026 IST DIE BEDINGUNG EINE ANDERE. Die Blase ist fuer
alle dunkles Glas; der Ton ist Kante und NAME. Damit faellt die alte
Regel weg, und an ihre Stelle tritt: der Ton muss als Name auf dem
Blasengrund lesbar bleiben.

DIE ZAHLEN SIND GEMESSEN, NICHT GEWAEHLT. Ueber alle 360 Winkel, jeweils
der schlechteste Fall:

    40 % schwarz   4,468   -- unter der Schwelle, faellt raus
    34 % schwarz   4,555   -- ginge, aber ohne Reserve
    30 % schwarz   4,619   <- die dunkelste Stufe
     0 %           5,12    <- die Mitte, der alte Kreis
    55 % weiss     9,6     <- die hellste Stufe

Sieben Stufen, 2520 Farben statt 360. pruef-chat-neu rechnet jede
einzelne nach -- die knappste hat 2,6 Prozent Reserve.

DIE ALTEN SCHLUESSEL BLEIBEN GUELTIG. "ton-214" heisst weiterhin genau
dieselbe Farbe; die Stufe steht als Zusatz dahinter ("ton-214-s6"). Wer
seit dem 23.09. eine Farbe traegt, traegt nach diesem Umbau dieselbe.

DIE PROBE IN DER MITTE ZEIGT ENDLICH, WAS MAN BEKOMMT. Dort stand eine
gefuellte Flaeche mit heller Schrift -- so sah die Blase bis heute frueh
aus. Jetzt steht dort derselbe Blasengrund und darauf der Ton als Name,
mit genau der Rechnung aus chat.css. Das ist nicht nur ehrlicher, es
macht die sieben Stufen erst moeglich: Eine gefuellte Flaeche muesste
Schrift tragen, und bei sehr hellen Toenen schafft das keine der beiden
Schriftfarben mehr (4,1 statt noetiger 7). Als Name auf dunklem Grund
ist derselbe helle Ton besonders gut lesbar (8,0).

Der Erklaersatz im Fenster war damit falsch geworden ("Alle Toene
leuchten gleich stark") und sagt jetzt, was stimmt.

ZWEI PRUEFUNGEN WURDEN GENAUER:
  pruef-chat-neu mass den HINTERGRUND der Mitte -- eine Darstellung,
  die es nicht mehr gibt. Sie misst jetzt die Schriftfarbe und rechnet
  die Mischung nach. Dabei stolperte sie ueber eine dritte
  Farbschreibweise: Chromium gibt `color-mix`-Ergebnisse als
  `color(srgb 0.676 0.645 0.504)` zurueck. Die Zeile war rot, obwohl
  die Farbe auf den Bildpunkt genau stimmte -- ein Fehlalarm, der
  teurer ist als ein echter Befund, weil man am Aussehen sucht und der
  Fehler im Lesegeraet liegt.
  Und sie prueft nicht mehr 360, sondern 360 x 7 -- nur den Grundton zu
  messen hiesse, sechs Siebtel ungeprueft auszuliefern.

Geprueft: pruef-chat-anhaenge (123), pruef-chat-neu (36),
pruef-chatkachel (40), pruef-chat, pruef-chat-ausbau (69),
pruef-chat-optik, pruef-tippziele (11), pruef-css-klassen.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-25 04:35:12 +02:00
co-authored by Claude Opus 5
parent 4b48aa46f6
commit 177c3c012a
50 changed files with 1855 additions and 659 deletions
+141
View File
@@ -0,0 +1,141 @@
/* =====================================================================
HELLER UND DUNKLER (25.09.2026)
=====================================================================
Filipe: „dieser kreis muss viel perfekter sein. ich will dass die
leute auch heller und dunkler aussuchen koennen. perfektionier das
bitte viel krasser. viel profissioneller. so dass die leute viel
krassere moeglichkeiten haben."
---------------------------------------------------------------------
WARUM DAS BIS HEUTE NICHT GING
Die 360 Toene in chat-kreis.js liegen ALLE auf derselben
Leuchtdichte. Das war kein Zufall und keine Bequemlichkeit: Solange
die Blase in der gewaehlten Farbe stand, musste jede dieser Flaechen
dieselbe Schrift tragen -- „gleiche Leuchtdichte heisst gleicher
Kontrast fuer alle". Eine hellere Farbe zuzulassen hiesse damals:
irgendwo im Haus wird eine Nachricht unlesbar.
SEIT DEM 25.09.2026 IST DAS ANDERS. Die Blase ist fuer alle dunkles
Glas (`--blasengrund`); der gewaehlte Ton ist KANTE und NAME. Damit
faellt die alte Bedingung weg -- und eine neue tritt an ihre Stelle:
Der Ton muss als NAME auf dem Blasengrund lesbar bleiben.
Die Oberflaeche hellt ihn dafuer mit Weiss auf (`--namen-anteil`,
50 %). Genau diese Rechnung prueft pruef-chat-neu fuer alle Toene.
---------------------------------------------------------------------
DIE ZAHLEN SIND GEMESSEN, NICHT GEWAEHLT
Nachgerechnet ueber alle 360 Winkel, jeweils der schlechteste:
40 % schwarz 4,468 -- unter der Schwelle, faellt raus
34 % schwarz 4,555 -- ginge, aber ohne Reserve
30 % schwarz 4,619 <- die dunkelste Stufe
20 % schwarz 4,7
10 % schwarz 4,9
0 % 5,12 <- die Mitte, der alte Kreis
18 % weiss 6,4
36 % weiss 7,9
55 % weiss 9,6 <- die hellste Stufe
Noetig sind 4,5. Die dunkelste Stufe hat also 0,12 Reserve, die
hellste mehr als das Doppelte. Wer hier eine Stufe hinzufuegt, misst
nach -- pruef-chat-neu rechnet jede Stufe an jedem der 360 Winkel
nach und wird rot, bevor es jemand auf dem Bildschirm sieht.
---------------------------------------------------------------------
WARUM SIEBEN UND NICHT DREI
Drei (dunkel / normal / hell) waere die bequeme Zahl. Sieben sind
der Punkt, an dem zwei benachbarte Stufen noch unterscheidbar sind
und man trotzdem das Gefuehl hat, etwas einstellen zu koennen --
Filipes „viel krassere moeglichkeiten" heisst nicht „zwei mehr".
Zusammen mit 360 Winkeln sind es 2520 Farben statt 360.
---------------------------------------------------------------------
WARUM EINE EIGENE DATEI
chat-kreis.js wird von tools/chat-kacheln-rechnen.mjs ERZEUGT und
traegt oben „NICHT VON HAND AENDERN". Etwas hineinzuschreiben waere
beim naechsten Lauf des Werkzeugs weg. Die Stufen sind ausserdem
keine Tabelle, sondern eine Rechnung -- sie gehoeren nicht in eine
erzeugte Liste.
===================================================================== */
import { KREIS } from "./chat-kreis.js";
/** Die sieben Stufen. Minus heisst dunkler, Plus heller.
*
* `wert` ist der Teil, der im Schluessel steht ("ton-214-s2").
* `misch` ist der Anteil, mit dem der Grundton nach Schwarz (negativ)
* oder nach Weiss (positiv) gezogen wird.
* `name` ist das, was ein Vorleseprogramm sagt -- eine Zahl von 1 bis
* 7 saehe aus wie eine Note.
*/
export const KREIS_STUFEN = [
{ wert: "s1", misch: -0.30, name: "sehr dunkel" },
{ wert: "s2", misch: -0.20, name: "dunkler" },
{ wert: "s3", misch: -0.10, name: "leicht dunkel" },
{ wert: "s4", misch: 0, name: "normal" },
{ wert: "s5", misch: 0.18, name: "leicht hell" },
{ wert: "s6", misch: 0.36, name: "heller" },
{ wert: "s7", misch: 0.55, name: "sehr hell" },
];
/** Welche Stufe ist die Mitte -- und damit das, was "ton-214" ohne
* Zusatz bedeutet. Abgeleitet aus `misch === 0` und nicht als Index
* hingeschrieben: Wer eine Stufe vorn einfuegt, verschiebt sonst
* stillschweigend die Bedeutung aller alten Schluessel. */
export const KREIS_MITTE = KREIS_STUFEN.find((s) => s.misch === 0)?.wert || "s4";
/** Zerlegt einen Schluessel in Winkel und Stufe.
*
* "ton-214" -> { winkel: 214, stufe: "s4" } (die Mitte)
* "ton-214-s6" -> { winkel: 214, stufe: "s6" }
* alles andere -> null
*
* DIE ALTEN SCHLUESSEL BLEIBEN GUELTIG, und zwar ohne Umstellung in
* der Datenbank. Wer seit dem 23.09. "ton-214" traegt, traegt danach
* genau dieselbe Farbe -- eine Umstellung, die stillschweigend die
* Wahl von fuenfzehn Leuten verschiebt, waere kein Fortschritt.
*/
export function tonAus(wert) {
const t = /^ton-(\d{1,3})(?:-(s[1-7]))?$/.exec(String(wert ?? ""));
if (!t) return null;
const winkel = Number(t[1]);
if (!(winkel >= 0 && winkel < KREIS.length)) return null;
const stufe = t[2] || KREIS_MITTE;
if (!KREIS_STUFEN.some((s) => s.wert === stufe)) return null;
return { winkel, stufe };
}
/** Zwei Farben mischen, wie `color-mix(in srgb, a p%, b)` es tut. */
function mischen(a, b, anteilB) {
const z = (h) => {
const s = String(h).replace("#", "");
return [0, 2, 4].map((i) => parseInt(s.slice(i, i + 2), 16));
};
const [ar, ag, ab] = z(a), [br, bg, bb] = z(b);
const m = (x, y) => Math.round(x * (1 - anteilB) + y * anteilB);
return "#" + [m(ar, br), m(ag, bg), m(ab, bb)]
.map((v) => v.toString(16).padStart(2, "0")).join("");
}
/** Die fertige Farbe zu Winkel und Stufe. */
export function tonFarbe(winkel, stufe) {
const grund = KREIS[winkel];
if (!grund) return null;
const s = KREIS_STUFEN.find((x) => x.wert === stufe) || { misch: 0 };
if (!s.misch) return grund;
return s.misch < 0
? mischen(grund, "#000000", -s.misch)
: mischen(grund, "#ffffff", s.misch);
}
/** Die Farbe zu einem ganzen Schluessel, oder null. */
export function farbeZuSchluessel(wert) {
const t = tonAus(wert);
return t ? tonFarbe(t.winkel, t.stufe) : null;
}
+136
View File
@@ -418,6 +418,142 @@ let pdfNachrichtId = 0;
ok(kaeme === 0, `für ein PDF wird kein Bildkasten gebaut (${kaeme})`);
}
/* =======================================================================
2c. DIE BILDSCHAU (25.09.2026)
=======================================================================
Filipe: „wenn man bilder aufmacht, will ich dass die in der mitte
sind. beim hochformat soll links der husky sein und rechts der hase
und beim querformat soll oben dogfather und unten team dogi stehen
da wo platz ist."
VORHER OEFFNETE DER KLICK DIE DATEI in einem neuen Tab -- und was man
sah, war die eingebaute Bildanzeige des Browsers: schwarzer Grund,
Bild oben links. Das ist kein Fehler des Browsers; eine Datei hat
keine Gestaltung.
GEMESSEN WIRD, WAS ER GESAGT HAT, und jedes einzeln:
1. Der Klick oeffnet die Schau (und nicht mehr den Tab).
2. Das Bild steht in der MITTE -- gemessen als Abstand seines
Mittelpunkts zur Fenstermitte, nicht als „es ist da".
3. Bei einem breiten Bild stehen die zwei Schriftzuege da.
4. Escape bringt einen zurueck.
5. GEGENPROBE: Mit Strg gedrueckt oeffnet sie NICHT -- der Weg in
den neuen Tab bleibt, wie er war. Ohne diese Zeile haette
„die Schau geht auf" auch dann gestimmt, wenn sie IMMER aufgeht
und der alte Weg verloren ist.
======================================================================= */
{
await oeffne(luna.seite, raumId);
/* Das Foto aus Abschnitt 2 ist 240x150 -- also quer. */
const bildKasten = ".chat-nachricht .chat-bild";
await luna.seite.waitForSelector(bildKasten, { timeout: 8000 });
/* GEGENPROBE ZUERST: mit Strg bleibt der alte Weg. */
await luna.seite.click(bildKasten, { modifiers: ["Control"] });
await luna.seite.waitForTimeout(400);
const mitStrg = await luna.seite.evaluate(() =>
!!document.querySelector("dialog.bildschau[open]"));
ok(mitStrg === false,
`Gegenprobe: Strg+Klick öffnet die Schau NICHT (${mitStrg}) — der Weg in den Tab bleibt`);
await luna.seite.click(bildKasten);
await luna.seite.waitForTimeout(600);
const schau = await luna.seite.evaluate(() => {
const d = document.querySelector("dialog.bildschau[open]");
if (!d) return { offen: false };
const b = d.querySelector(".bildschau__bild");
const r = b.getBoundingClientRect();
const worte = [...d.querySelectorAll(".bildschau__wort")]
.filter((e) => Number(getComputedStyle(e).opacity) > .5)
.map((e) => e.textContent.trim());
return {
offen: true,
lage: d.dataset.lage,
/* Wie weit ist die Bildmitte von der Fenstermitte weg? Bei
„mittig" sind das ein paar Pixel, bei „oben links" Hunderte. */
abX: Math.round(Math.abs((r.left + r.width / 2) - window.innerWidth / 2)),
abY: Math.round(Math.abs((r.top + r.height / 2) - window.innerHeight / 2)),
worte,
figuren: [...d.querySelectorAll(".bildschau__figur")]
.filter((e) => Number(getComputedStyle(e).opacity) > .5).length,
grund: getComputedStyle(d.querySelector(".bildschau__grund")).backgroundImage.length,
};
});
ok(schau.offen === true, `ein Klick öffnet die Bildschau (${schau.offen})`);
/* 40 px Spielraum: Die Buehne hat oben mehr Polsterung als unten
(dort steht die Leiste), der Mittelpunkt liegt also nie exakt auf
dem Pixel. Bei „oben links in der Ecke" waeren es Hunderte. */
ok(schau.abX <= 40 && schau.abY <= 60,
`und das Bild steht in der Mitte (${schau.abX} px seitlich, ${schau.abY} px hoch daneben)`);
ok(schau.lage === "quer",
`ein breites Bild bekommt die Querformat-Fassung ("${schau.lage}")`);
ok(schau.worte.length === 2
&& /DogFather/i.test(schau.worte[0]) && /Team Dogi/i.test(schau.worte[1]),
`mit „DogFather" oben und „Team Dogi" unten (${JSON.stringify(schau.worte)})`);
ok(schau.figuren === 0,
`und OHNE die zwei Figuren — die gehören ins Hochformat (${schau.figuren})`);
ok(schau.grund > 80,
`der Grund ist gestaltet und nicht schwarz (${schau.grund} Zeichen Verlauf)`);
/* EIN BILD FUER DEN MENSCHEN. Andere Pruefungen in diesem Haus legen
ebenso eines ab (pruef-handy.png, pruef-crew-wand-handy.png) --
eine Zahl sagt „mittig", ein Bild sagt, ob es gut aussieht. */
await luna.seite.screenshot({ path: "server/pruef-bildschau.png" });
await luna.seite.keyboard.press("Escape");
await luna.seite.waitForTimeout(400);
const zu = await luna.seite.evaluate(() =>
!!document.querySelector("dialog.bildschau[open]"));
ok(zu === false, `Escape bringt einen zurück (${zu})`);
/* ---- UND DAS HOCHFORMAT -----------------------------------------
Der andere Fall aus Filipes Satz -- „beim hochformat soll links
der husky sein und rechts der hase". Ohne ein zweites, HOHES Bild
waere dieser halbe Auftrag nie gemessen worden; das quere Bild
oben kann ihn nicht beantworten. */
const hoch = await hochladen(luna.seite, raumId, {
inhalt: { art: "png", breite: 300, hoehe: 800 },
typ: "image/png", name: "hochformat.png", text: "Und hochkant",
});
ok(hoch.status === 201, `ein hohes Foto kommt an (HTTP ${hoch.status})`);
await oeffne(luna.seite, raumId);
await luna.seite.waitForSelector(".chat-nachricht:last-of-type .chat-bild", { timeout: 8000 });
await luna.seite.click(".chat-nachricht:last-of-type .chat-bild");
await luna.seite.waitForTimeout(700);
const hochSchau = await luna.seite.evaluate(() => {
const d = document.querySelector("dialog.bildschau[open]");
if (!d) return { offen: false };
const b = d.querySelector(".bildschau__bild");
const r = b.getBoundingClientRect();
const sicht = (s) => [...d.querySelectorAll(s)]
.filter((e) => Number(getComputedStyle(e).opacity) > .5);
const figuren = sicht(".bildschau__figur");
return {
offen: true, lage: d.dataset.lage,
abX: Math.round(Math.abs((r.left + r.width / 2) - window.innerWidth / 2)),
figuren: figuren.length,
/* LIEGT EINE DAVON UEBER DEM BILD? Das waere der Schaden, den
man erst sieht, wenn ein Gesicht halb verdeckt ist. */
drueber: figuren.filter((e) => {
const f = e.getBoundingClientRect();
return Math.min(f.right, r.right) - Math.max(f.left, r.left) > 4;
}).length,
worte: sicht(".bildschau__wort").length,
};
});
ok(hochSchau.lage === "hoch", `ein hohes Bild bekommt die Hochformat-Fassung ("${hochSchau.lage}")`);
ok(hochSchau.figuren === 2, `Husky und Hase stehen daneben (${hochSchau.figuren})`);
ok(hochSchau.drueber === 0,
`und KEINE davon liegt über dem Foto (${hochSchau.drueber})`);
ok(hochSchau.worte === 0,
`die Schriftzüge bleiben weg — die gehören ins Querformat (${hochSchau.worte})`);
ok(hochSchau.abX <= 40, `auch hier steht das Bild mittig (${hochSchau.abX} px daneben)`);
await luna.seite.screenshot({ path: "server/pruef-bildschau-hoch.png" });
await luna.seite.keyboard.press("Escape");
await luna.seite.waitForTimeout(300);
}
/* =======================================================================
3. WAS NICHT HEREINDARF — die eigentliche Prüfung
+129 -15
View File
@@ -313,6 +313,23 @@ console.log("\n=== 3. Der Ruf ans Rudel ===");
const zuZahl = (f) => {
const t = /^#?([0-9a-f]{6})$/i.exec(String(f).trim());
if (t) return [0, 2, 4].map((i) => parseInt(t[1].slice(i, i + 2), 16));
/* ==== DIE DRITTE SCHREIBWEISE (25.09.2026) ====================
Seit die Probe ihre Farbe aus `color-mix(in srgb, …)` bekommt,
gibt Chromium sie als `color(srgb 0.676 0.645 0.504)` zurueck --
Anteile von 0 bis 1, keine Ganzzahlen, kein Komma. Die zwei
Muster darueber trafen das nicht, `zuZahl` lieferte `null`, und
die Zeile wurde rot, obwohl die Farbe auf den Bildpunkt genau
stimmte (172,5 / 164,5 / 128,5 gegen erwartete 173/165/129).
Das ist die Sorte Fehlalarm, die teurer ist als ein echter
Befund: Man sucht am Aussehen, und der Fehler liegt im
Lesegeraet. */
const s = /color\(srgb\s+([^)]+)\)/i.exec(String(f));
if (s) {
return s[1].trim().split(/[\s/]+/).slice(0, 3)
.map((x) => Math.round(parseFloat(x) * 255));
}
const r = /rgba?\(([^)]+)\)/i.exec(String(f));
return r ? r[1].split(",").slice(0, 3).map((x) => Math.round(parseFloat(x))) : null;
};
@@ -375,21 +392,55 @@ console.log("\n=== 3. Der Ruf ans Rudel ===");
const schlimmerGrund = grund.map((v) => Math.round(v * 0.92 + 255 * 0.08));
const anteil = Number(anteilTreffer[1]) / 100;
const hell = zuZahl("#f2f8ff");
/* ==== JEDE STUFE AN JEDEM WINKEL (25.09.2026) =================
Filipe: „ich will dass die leute auch heller und dunkler
aussuchen koennen."
Seit heute gibt es zu jedem der 360 Winkel sieben
Helligkeiten -- 2520 Farben statt 360. Nur den Grundton zu
pruefen hiesse, sechs Siebtel des Angebots ungeprueft
auszuliefern; und die dunkelste Stufe ist genau die, bei der
es kippen wuerde (gemessen 4,62 gegen noetige 4,5 -- die
naechste Stufe darunter faellt durch).
DIE MISCHANTEILE WERDEN GELESEN, nicht abgeschrieben: Sie
stehen in chat-kreis-stufen.js, und wer sie dort aendert,
aendert damit diese Pruefung mit. Eine zweite Liste hier waere
die, die beim naechsten Umbau nicht mitzieht. */
const { KREIS_STUFEN } = await import("./chat-kreis-stufen.js");
const mischen2 = (rgb, misch) => {
if (!misch) return rgb;
const ziel = misch < 0 ? 0 : 255;
const t2 = Math.abs(misch);
return rgb.map((v) => Math.round(v * (1 - t2) + ziel * t2));
};
let schlimmster = Infinity, schlimmsterWinkel = -1, gemessen = 0;
let schlimmsteStufe = "";
for (let w = 0; w < KREIS.length; w++) {
const f = zuZahl(KREIS[w]);
if (!f) continue;
gemessen++;
const name = f.map((v, i) => Math.round(v * anteil + hell[i] * (1 - anteil)));
const v2 = kontrast(name, schlimmerGrund) / 4.5;
if (v2 < schlimmster) { schlimmster = v2; schlimmsterWinkel = w; }
for (const st of KREIS_STUFEN) {
gemessen++;
const ton = mischen2(f, st.misch);
const name = ton.map((v, i) => Math.round(v * anteil + hell[i] * (1 - anteil)));
const v2 = kontrast(name, schlimmerGrund) / 4.5;
if (v2 < schlimmster) {
schlimmster = v2; schlimmsterWinkel = w; schlimmsteStufe = st.wert;
}
}
}
/* DIE ANZAHL STEHT IN DER BEDINGUNG. Waere die Tabelle leer,
bliebe schlimmster auf Infinity und die Zeile waere gruen,
ohne eine einzige Farbe angesehen zu haben. */
ok(gemessen === 360 && schlimmster >= 1,
`alle ${gemessen} Toene des Rings sind als Name auf der Blase lesbar`
+ ` (knappster: Winkel ${schlimmsterWinkel}, Faktor ${schlimmster.toFixed(3)})`);
/* DIE ANZAHL STEHT IN DER BEDINGUNG -- und sie ist seit heute
360 mal die Zahl der Stufen. Waere die Stufenliste leer,
bliebe `gemessen` bei 0 und die Zeile waere gruen, ohne eine
einzige Farbe angesehen zu haben. */
ok(gemessen === 360 * KREIS_STUFEN.length && schlimmster >= 1,
`alle ${gemessen} Farben des Rings (360 Winkel x ${KREIS_STUFEN.length} `
+ `Helligkeiten) sind als Name auf der Blase lesbar (knappste: Winkel `
+ `${schlimmsterWinkel}, Stufe ${schlimmsteStufe}, Faktor ${schlimmster.toFixed(3)})`);
}
}
if (textRgb && leiseRgb) {
@@ -466,21 +517,84 @@ console.log("\n=== 3. Der Ruf ans Rudel ===");
const stand = await frida.seite.evaluate(() => {
const r = document.getElementById("kachelrad-ring");
const m = document.getElementById("kachelrad-mitte");
const p2 = document.querySelector(".kachelrad__probe");
const leiter = document.getElementById("kachelhell-leiter");
return { winkel: Number(r?.getAttribute("aria-valuenow")),
wort: r?.getAttribute("aria-valuetext") || "",
mitte: m ? getComputedStyle(m).backgroundColor : "" };
/* NICHT MEHR DER GRUND, SONDERN DIE SCHRIFT. Siehe unten. */
probe: p2 ? getComputedStyle(p2).color : "",
stufen: leiter ? leiter.children.length : 0,
stufeJetzt: leiter?.getAttribute("aria-valuenow") || "" };
});
ok(Math.abs((stand.winkel || 0) - 90) <= 2,
`nach dem Drehen auf drei Uhr steht der Ring bei ${stand.winkel} Grad (erwartet 90)`);
const erwartet = zuZahl(KREIS[stand.winkel] || "");
const gezeigt = zuZahl(stand.mitte);
/* ==== DIE PROBE MISST SEIT HEUTE ETWAS ANDERES (25.09.2026) =====
Hier stand: „die Mitte zeigt genau die Farbe dieses Winkels" --
gemessen am HINTERGRUND der Mitte. Das war richtig, solange die
Blase in der gewaehlten Farbe stand.
Seit dem 25.09.2026 ist die Blase fuer alle dunkles Glas, und
der gewaehlte Ton ist der NAME darauf. Die Probe zeigte damit
eine Darstellung, die es im Chat nicht mehr gibt -- und die
Pruefung haette sie festgehalten.
GEMESSEN WIRD JETZT DIE SCHRIFT der Probe: Sie muss genau die
Farbe tragen, die ein Name in der Blase traegt -- der Ton, mit
`--namen-anteil` nach #f2f8ff aufgehellt. Das ist dieselbe
Rechnung wie in chat.css, und sie wird hier NACHGERECHNET statt
abgeschrieben. */
const grundTon = zuZahl(KREIS[stand.winkel] || "");
const hell = zuZahl("#f2f8ff");
const anteil = 0.5; /* --namen-anteil; weiter unten aus dem CSS geprueft */
const erwartet = grundTon
? grundTon.map((v, i) => Math.round(v * anteil + hell[i] * (1 - anteil)))
: null;
const gezeigt = zuZahl(stand.probe);
ok(!!erwartet && !!gezeigt
&& Math.abs(erwartet[0] - gezeigt[0]) <= 1
&& Math.abs(erwartet[1] - gezeigt[1]) <= 1
&& Math.abs(erwartet[2] - gezeigt[2]) <= 1,
`die Mitte zeigt genau die Farbe dieses Winkels (${stand.mitte} gegen ${KREIS[stand.winkel]})`);
&& Math.abs(erwartet[0] - gezeigt[0]) <= 2
&& Math.abs(erwartet[1] - gezeigt[1]) <= 2
&& Math.abs(erwartet[2] - gezeigt[2]) <= 2,
`die Probe zeigt den Namen in genau dieser Farbe (${stand.probe} gegen `
+ `${erwartet ? "rgb(" + erwartet.join(", ") + ")" : "?"})`);
/* ==== UND DIE SIEBEN HELLIGKEITEN (25.09.2026) =================
Filipe: „ich will dass die leute auch heller und dunkler
aussuchen koennen … so dass die leute viel krassere
moeglichkeiten haben." */
ok(stand.stufen === 7,
`die Helligkeitsleiter hat sieben Stufen (${stand.stufen})`);
ok(stand.stufeJetzt === "4",
`und steht von sich aus in der Mitte (${stand.stufeJetzt})`);
/* EINE STUFE WAEHLEN UND NACHSEHEN, OB SIE ANKOMMT. Ohne das
waere „es gibt sieben Felder" ein Haken ueber Zierde: Sieben
Kaestchen, die nichts tun, sehen genauso aus. */
await frida.seite.click(".kachelhell__feld[data-stufe='s7']");
await frida.seite.waitForTimeout(700);
const hellStand = await frida.seite.evaluate(() => {
const p2 = document.querySelector(".kachelrad__probe");
const leiter = document.getElementById("kachelhell-leiter");
const an = leiter?.querySelector('[data-an="ja"]');
return { probe: p2 ? getComputedStyle(p2).color : "",
stufe: leiter?.getAttribute("aria-valuenow") || "",
markiert: an?.dataset.stufe || "" };
});
ok(hellStand.stufe === "7" && hellStand.markiert === "s7",
`eine hellere Stufe laesst sich waehlen (${hellStand.stufe}, ${hellStand.markiert})`);
const vorher2 = zuZahl(stand.probe), nachher2 = zuZahl(hellStand.probe);
ok(!!vorher2 && !!nachher2 && leucht(nachher2) > leucht(vorher2),
`und die Probe wird dabei wirklich heller `
+ `(${leucht(vorher2).toFixed(3)} auf ${leucht(nachher2).toFixed(3)})`);
await frida.seite.screenshot({ path: "server/pruef-farbkreis.png" });
/* Und zurueck in die Mitte, damit die Pruefungen danach mit dem
gewohnten Stand weiterarbeiten. */
await frida.seite.click(".kachelhell__feld[data-stufe='s4']");
await frida.seite.waitForTimeout(500);
ok(/\d+ Grad/.test(stand.wort) && stand.wort.length > 8,
`und sie wird vorgelesen als "${stand.wort}"`);
+29 -1
View File
@@ -51,6 +51,7 @@ import {
import { erwaehnteIds, rudelGerufen, RUDEL_WORT } from "./chat-erwaehnung.js";
import { KREIS, winkelAus } from "./chat-kreis.js";
import { KREIS_STUFEN, KREIS_MITTE, tonAus } from "./chat-kreis-stufen.js";
import { KATALOG, ERLAUBT, VORSCHLAG, FAVORITEN_MAX } from "./workspace-reaktionen.js";
/* Der Treff-Chat: eigener Raum, eigene Nachtruhe, keine Anrufe.
@@ -425,7 +426,20 @@ export function kachelPruefen(wert) {
Die alten Schluessel bleiben gueltig. Wer "veilchen" gewaehlt hat,
behaelt es -- eine Umstellung, die stillschweigend die Wahl von
fuenfzehn Leuten zuruecksetzt, waere kein Fortschritt. */
if (winkelAus(wert) !== null) return String(wert);
/* SEIT DEM 25.09.2026 GIBT ES ZU JEDEM WINKEL SIEBEN HELLIGKEITEN.
Filipe: „ich will dass die leute auch heller und dunkler
aussuchen koennen."
Der Schluessel heisst dann "ton-214-s6". Ohne Zusatz gilt die
Mitte -- die alten Schluessel bleiben damit Zeichen fuer Zeichen
gueltig und bedeuten dieselbe Farbe wie vorher.
`tonAus` ERSETZT `winkelAus` HIER, laesst es aber stehen: Es wird
an anderer Stelle noch gebraucht, und zwei Pruefungen fuer
denselben Schluessel waeren eine Gelegenheit, dass eine davon
mehr durchlaesst. */
if (tonAus(wert) !== null) return String(wert);
return CHAT_KACHELN.some((k) => k.wert === String(wert)) ? String(wert) : null;
}
@@ -1031,6 +1045,20 @@ chatRouter.get("/workspace/api/chat/kacheln", (req, res) => {
Kacheln -- also tragen sie auch dieselbe Schrift. Einmal
gerechnet und mitgeschickt, statt 360-mal dasselbe. */
kreis_schrift: schriftFuer(KREIS[0]),
/* DIE SIEBEN HELLIGKEITEN (25.09.2026).
Nicht 2520 fertige Farben, sondern 360 Grundtoene und sieben
Mischanteile: Der Browser mischt selbst, und zwar mit genau
den Zahlen, die hier stehen. Die ganze Tabelle zu schicken
waere 20 kB fuer etwas, das eine Zeile Rechnung ist.
DIE ZAHLEN KOMMEN TROTZDEM VOM SERVER und stehen nicht im
Browser: Sie sind aus der Kontrastrechnung hergeleitet (siehe
chat-kreis-stufen.js), und eine zweite Fassung im Browser
waere ab der naechsten Messung falsch -- unbemerkt, denn sie
lieferte ja weiter „irgendeine Farbe". */
kreis_stufen: KREIS_STUFEN,
kreis_mitte: KREIS_MITTE,
/* Die eigene WAHL, nicht der getragene Ton -- sonst stuende im
Waehler ein Haken bei einer Farbe, die niemand gewaehlt hat. */
meine: gewaehlteKachelFuer(req.person.id),