/* ===================================================================== WELCHE FARBE HAT DIESER TEXT WIRKLICH -- UND WORAUF LIEGT ER? ===================================================================== Gebaut am 25.09.2026, weil `pruef-neue-seiten` einen Kontrast von 1,07 meldete ("Schoen, dass du da bist") und die Stelle im CSS tadellos aussah. Diese Messung liest die Farben an Ort und Stelle aus und geht die Elternkette hoch, bis sie eine deckende Flaeche findet -- genau das, was die Pruefung tut, nur sichtbar. Ergebnis damals: dunkler Text auf HELLBLAUER Flaeche, rund 10:1. Die Pruefung konnte das Format `color(srgb ...)` nicht lesen, ging deshalb zum fast schwarzen Elternteil weiter und rechnete dunkel gegen dunkel. Ohne diese Messung haette ich an einem CSS geaendert, an dem nichts war. Aufrufen mit: node server/mess-farbe-am-text.mjs "" ===================================================================== */ import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; const SEITE = process.argv[2] || "treff-regeln.html"; const SUCHE = process.argv[3] || "Schön, dass du da bist"; import { eigenerPort } from "./helfer-port.mjs"; const PORT = await eigenerPort(import.meta); const ordner = mkdtempSync(join(tmpdir(), "ws-kontrast-")); 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 }); }; import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); notbremse(200_000, "mess-kontrast"); await new Promise((r) => setTimeout(r, 900)); const BASIS = `http://127.0.0.1:${PORT}`; 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 sch = randomBytes(32).toString("hex"); d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)" + " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING") .run("code_kennung_schluessel", sch, jetzt); const anlegen = (name, rolle, code) => { const s = randomBytes(16).toString("hex"); const h = scryptSync(code, s, 64, { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex"); return d.prepare("INSERT INTO personen" + " (name, rolle, code_hash, code_salt, code_n, code_kennung, aktiv, erstellt)" + " VALUES (?,?,?,?,?,?,1,?) RETURNING id") .get(name, rolle, h, s, 32768, createHmac("sha256", sch).update(code).digest("hex"), jetzt).id; }; anlegen("Filipe", "admin", "CODE-DOGI-0001"); d.close(); const an = await fetch(BASIS + "/workspace/api/anmelden", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ rolle: "admin", code: "CODE-DOGI-0001" }), }); const keks = (an.headers.getSetCookie?.() || []).map((z) => z.split(";")[0]).join("; "); const pw = await import( "file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); const browser = await pw.chromium.launch(); const kontext = await browser.newContext({ viewport: { width: 1440, height: 900 } }); await kontext.addCookies(keks.split("; ").map((k) => { const [n, ...r] = k.split("="); return { name: n, value: r.join("="), domain: "127.0.0.1", path: "/" }; })); const seite = await kontext.newPage(); await seite.goto(`${BASIS}/workspace/${SEITE}`, { waitUntil: "networkidle" }); await seite.waitForTimeout(900); const fund = await seite.evaluate((suche) => { const alle = [...document.querySelectorAll("*")].filter( (e) => e.children.length === 0 && (e.textContent || "").trim().startsWith(suche)); if (!alle.length) return { da: false }; const e = alle[0]; const st = getComputedStyle(e); const kette = []; let p = e; while (p && p !== document.documentElement) { const ps = getComputedStyle(p); kette.push({ was: p.tagName.toLowerCase() + (p.className && typeof p.className === "string" ? "." + p.className.trim().split(/\s+/).slice(0, 3).join(".") : ""), farbe: ps.backgroundColor, bild: ps.backgroundImage === "none" ? "" : ps.backgroundImage.slice(0, 60), deckkraft: ps.opacity, }); p = p.parentElement; } const r = e.getBoundingClientRect(); return { da: true, text: (e.textContent || "").trim().slice(0, 40), farbe: st.color, groesse: st.fontSize, fett: st.fontWeight, clip: st.webkitBackgroundClip || st.backgroundClip, eigenBild: st.backgroundImage === "none" ? "" : st.backgroundImage.slice(0, 80), deckkraft: st.opacity, stelle: `${Math.round(r.left)},${Math.round(r.top)} ${Math.round(r.width)}x${Math.round(r.height)}`, kette: kette.slice(0, 6), }; }, SUCHE); console.log(`\n=== "${SUCHE}" auf ${SEITE} ===`); if (!fund.da) console.log(" NICHT GEFUNDEN"); else { console.log(` Text "${fund.text}"`); console.log(` Farbe ${fund.farbe} ${fund.groesse} / ${fund.fett}`); console.log(` Deckkraft ${fund.deckkraft}`); console.log(` clip ${fund.clip}`); if (fund.eigenBild) console.log(` eigenes Hintergrundbild: ${fund.eigenBild}`); console.log(` Stelle ${fund.stelle}`); console.log(" Kette (von innen nach aussen):"); for (const k of fund.kette) { console.log(` ${k.was.padEnd(38)} bg ${k.farbe}` + (k.bild ? ` bild ${k.bild}` : "") + (k.deckkraft !== "1" ? ` opacity ${k.deckkraft}` : "")); } } await browser.close(); try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ } process.exit(0);