From 177c3c012a3f9e3533abfbadd6b4c7c5e09e803a Mon Sep 17 00:00:00 2001 From: Dogfather Date: Fri, 25 Sep 2026 04:35:12 +0200 Subject: [PATCH] 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 --- server/chat-kreis-stufen.js | 141 +++++++++++++ server/pruef-chat-anhaenge.mjs | 136 +++++++++++++ server/pruef-chat-neu.mjs | 144 ++++++++++++-- server/workspace-chat.js | 30 ++- workspace/anruf-probe.html | 10 +- workspace/app.webmanifest | 8 +- workspace/assets/css/chat.css | 275 +++++++++++++++++++++++++- workspace/assets/img/schau-hase.webp | Bin 0 -> 32464 bytes workspace/assets/img/schau-husky.webp | Bin 0 -> 40226 bytes workspace/assets/js/bildschau.js | 234 ++++++++++++++++++++++ workspace/assets/js/chat.js | 233 +++++++++++++++++++++- workspace/aufgaben.html | 38 ++-- workspace/automation.html | 32 +-- workspace/befinden.html | 34 ++-- workspace/bereich.html | 44 ++--- workspace/bewerben.html | 38 ++-- workspace/bewerbungen.html | 36 ++-- workspace/calls.html | 34 ++-- workspace/chat.html | 99 +++++++--- workspace/content.html | 34 ++-- workspace/crew-index.html | 12 +- workspace/crew.webmanifest | 8 +- workspace/dateien.html | 34 ++-- workspace/entwicklung.html | 36 ++-- workspace/hilfe.html | 30 +-- workspace/index.html | 10 +- workspace/kalender.html | 34 ++-- workspace/leistung.html | 32 +-- workspace/material.html | 32 +-- workspace/personen.html | 32 +-- workspace/profil.html | 36 ++-- workspace/rechte.html | 34 ++-- workspace/report.html | 36 ++-- workspace/scouting.html | 32 +-- workspace/start.html | 32 +-- workspace/startcheck.html | 34 ++-- workspace/steckbrief.html | 36 ++-- workspace/support.html | 28 +-- workspace/talente.html | 36 ++-- workspace/team.html | 32 +-- workspace/teamlage.html | 34 ++-- workspace/teilen.html | 30 +-- workspace/treff-moderation.html | 32 +-- workspace/treff-regeln.html | 32 +-- workspace/uebersicht.html | 32 +-- workspace/unsere-seiten.html | 30 +-- workspace/unterstuetzen.html | 32 +-- workspace/werdegang.html | 32 +-- workspace/willkommen.html | 30 +-- workspace/wissen.html | 34 ++-- 50 files changed, 1855 insertions(+), 659 deletions(-) create mode 100644 server/chat-kreis-stufen.js create mode 100644 workspace/assets/img/schau-hase.webp create mode 100644 workspace/assets/img/schau-husky.webp create mode 100644 workspace/assets/js/bildschau.js diff --git a/server/chat-kreis-stufen.js b/server/chat-kreis-stufen.js new file mode 100644 index 00000000..79f88053 --- /dev/null +++ b/server/chat-kreis-stufen.js @@ -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; +} diff --git a/server/pruef-chat-anhaenge.mjs b/server/pruef-chat-anhaenge.mjs index ca5beb7f..6e53493a 100644 --- a/server/pruef-chat-anhaenge.mjs +++ b/server/pruef-chat-anhaenge.mjs @@ -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 diff --git a/server/pruef-chat-neu.mjs b/server/pruef-chat-neu.mjs index 5de480ec..6760388c 100644 --- a/server/pruef-chat-neu.mjs +++ b/server/pruef-chat-neu.mjs @@ -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}"`); diff --git a/server/workspace-chat.js b/server/workspace-chat.js index dc696de4..77db76a1 100644 --- a/server/workspace-chat.js +++ b/server/workspace-chat.js @@ -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), diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index 4e603c81..932bf84f 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`. --> - - + + - +