/* ===================================================================== JEDE SEITE TRÄGT IHRE FARBE — auch ganz oben in der Leiste ===================================================================== Filipe am 22.09.2026: „je nachdem auf welcher seite ich bin wechseln gaaanz oben in der leiste gewissene symbole und sachen mit ... ich will das auf jeder seite ... nicht mehr wie auf screen3 sondern genau gleich wie da." Sein Vorbild war die Highlights-Seite. Und das ist der Schlüssel zum Befund: Highlights ist `bereich.html`, und `bereich.html` ist eine von DREI Seiten, die `bereich.css` laden. Dort stand die Regel, die aus `data-ton` eine Akzentfarbe macht. Gemessen: `kopf.js` setzt `data-ton` am auf jeder Seite. Auf zweiunddreißig von fünfunddreißig las es niemand — die Farbe war da und kam nicht an. Seit dem 22.09. steht die Regel in `start.css`, und die liegt auf allen fünfunddreißig. --------------------------------------------------------------------- WAS HIER GEMESSEN WIRD — und warum nicht der Quelltext Ob eine CSS-Regel WIRKT, steht nicht in der Datei. `--akzent` könnte von einer späteren Regel überschrieben werden, `data-ton` könnte fehlen, das Haus könnte etwas anderes sagen. Deshalb ein echter Browser und `getComputedStyle` — die Farbe, die auf dem Schirm ankommt. ZWEI SEITEN MIT VERSCHIEDENEN TÖNEN, und beide Richtungen: * auf jeder kommt eine Farbe an * und es ist nicht dieselbe Ohne den zweiten Teil wäre eine Seite, die überall dieselbe Farbe zeigt, ebenfalls grün — und genau das war der Zustand vorher. ===================================================================== */ import { mkdtempSync, rmSync, readFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { request as httpAnfrage } from "node:http"; import { execFileSync } from "node:child_process"; import { createServer as httpsServer } from "node:https"; import { notbremse } from "./helfer-notbremse.mjs"; import { eigenerPort } from "./helfer-port.mjs"; import { zertifikatBauen } from "./helfer-openssl.mjs"; const CREW = "crew.dogfather-universe.com"; const PORT = await eigenerPort(import.meta, "pruef-kopfleiste-farbe"); const ordner = mkdtempSync(join(tmpdir(), "ws-kopffarbe-")); process.env.WORKSPACE_DB = join(ordner, "workspace.db"); process.env.PORT = `${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(180_000, "pruef-kopfleiste-farbe"); await new Promise((r) => setTimeout(r, 900)); let fehler = 0, geprueft = 0, offen = 0; const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; const unklar = (t) => { offen++; console.log(" -- " + t); }; /* ---------- Ein Mensch ------------------------------------------------- */ 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 k = randomBytes(32).toString("hex"); d.prepare("INSERT INTO einstellungen (schluessel,wert,geaendert,von) VALUES (?,?,?,NULL)" + " ON CONFLICT(schluessel) DO NOTHING").run("code_kennung_schluessel", k, jetzt); { const salt = randomBytes(16).toString("hex"); const hash = scryptSync("CODE-DOGI-0001", 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("Filipe", "admin", hash, salt, 32768, createHmac("sha256", k).update("CODE-DOGI-0001").digest("hex"), jetzt); } d.close(); /* ---------- Der Vorbau ------------------------------------------------- */ const schl = join(ordner, "p.key"), zert = join(ordner, "p.crt"); zertifikatBauen(schl, zert, CREW); const HP = PORT + 1; const vorbau = httpsServer({ key: readFileSync(schl), cert: readFileSync(zert) }, (von, nach) => { const w = httpAnfrage({ host: "127.0.0.1", port: PORT, path: von.url, method: von.method, headers: { ...von.headers, host: `${CREW}:${HP}` } }, (a) => { nach.writeHead(a.statusCode || 502, a.headers); a.pipe(nach); }); von.pipe(w); }); await new Promise((r) => vorbau.listen(HP, "127.0.0.1", r)); const SEITE = `https://${CREW}:${HP}`; const pw = await import( "file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); const browser = await pw.chromium.launch({ args: [`--host-resolver-rules=MAP ${CREW} 127.0.0.1`, "--ignore-certificate-errors"] }); const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } }); const seite = await ctx.newPage(); const meckern = []; seite.on("pageerror", (f) => meckern.push(String(f).slice(0, 140))); console.log("=== Jede Seite traegt ihre Farbe ==="); console.log(""); await seite.goto(`${SEITE}/workspace/`, { waitUntil: "networkidle" }); await seite.click('.rolle[data-rolle="admin"]'); await seite.fill("#code", "CODE-DOGI-0001"); await seite.click("button[type=submit]"); await seite.waitForTimeout(900); /* WELCHE SEITEN GEMESSEN WERDEN, STEHT NICHT HIER. ----------------------------------------------------------------- Die Kacheln der Startseite sagen, welche Seiten es gibt und welchen Ton jede hat. Eine Liste in dieser Datei waere die, die beim naechsten Umbau eine Seite nennt, die es nicht mehr gibt -- oder, schlimmer, die neue nicht kennt. */ await seite.goto(`${SEITE}/workspace/start.html`, { waitUntil: "networkidle" }); await seite.waitForTimeout(1800); /* DIE KACHEL IST DAS
  • , DER VERWEIS LIEGT DARIN. Beim ersten Anlauf stand hier `.kachel[href]` -- das trifft nichts, und die Pruefung meldete "0 Kacheln". Das war richtig gemeldet und trotzdem ein Fehler von mir: Die Struktur steht in start.js (`li.kachel` mit `a.kachel__link` darin), nachgesehen statt geraten. */ const kacheln = await seite.evaluate(() => [...document.querySelectorAll('li.kachel[data-ton]')] .map((li) => ({ ziel: li.querySelector('a')?.getAttribute("href") || null, ton: li.dataset.ton || null, })) .filter((x) => x.ziel && /^[a-z-]+\.html$/.test(x.ziel) && x.ton !== null)); ok(kacheln.length >= 4, `${kacheln.length} Kacheln mit eigenem Ton auf der Startseite gefunden`); /* Zwei mit VERSCHIEDENEN Toenen -- ohne Unterschied liesse sich nicht zeigen, dass die Farbe der Seite folgt und nicht der Leiste. */ const nachTon = new Map(); for (const x of kacheln) if (!nachTon.has(x.ton)) nachTon.set(x.ton, x); const auswahl = [...nachTon.values()].slice(0, 3); ok(auswahl.length >= 2, `davon ${auswahl.length} mit verschiedenen Toenen (${auswahl.map((x) => x.ton).join(", ")})`); const gemessen = []; for (const x of auswahl) { await seite.goto(`${SEITE}/workspace/${x.ziel}`, { waitUntil: "networkidle" }); await seite.waitForTimeout(1500); const w = await seite.evaluate(() => { const koerper = getComputedStyle(document.body); const leiste = document.querySelector(".kopfleiste"); return { adresse: location.pathname.split("/").pop(), ton: document.body.dataset.ton || null, akzent: koerper.getPropertyValue("--akzent").trim(), tonFarbe: koerper.getPropertyValue("--ton").trim(), leisteDa: !!leiste, /* Die Kante unter der Leiste -- gemessen an dem, was wirklich gezeichnet wird, nicht an der Regel im Quelltext. */ kante: leiste ? getComputedStyle(leiste, "::before").backgroundImage : "", }; }); gemessen.push(w); } console.log(""); console.log("--- Was auf den Seiten ankommt ---"); for (const w of gemessen) { console.log(` ${w.adresse.padEnd(18)} Ton ${String(w.ton).padStart(2)}` + ` --akzent ${w.akzent}`); } ok(gemessen.length >= 2 && gemessen.every((w) => w.ton !== null), `auf allen ${gemessen.length} Seiten steht ein Ton am Koerper`); /* DIE ZAHL GEHOERT IN DIE BEDINGUNG. `every()` auf einem leeren Feld liefert `true` -- beim ersten Lauf waren drei Zeilen hier gruen, ohne dass eine einzige Seite gemessen worden war. Genau der Fehler, vor dem die Hausregel warnt, und er ist mir trotzdem passiert. */ ok(gemessen.length >= 2 && gemessen.every((w) => w.akzent && w.akzent !== "none"), `auf allen ${gemessen.length} kommt daraus eine Akzentfarbe an`); /* DER ENTSCHEIDENDE VERGLEICH. Vorher war --akzent auf 32 von 35 Seiten die feste Hausfarbe -- also ueberall dieselbe. */ const verschieden = new Set(gemessen.map((w) => w.akzent)).size; ok(gemessen.length >= 2 && verschieden === gemessen.length, `und sie ist auf jeder Seite eine andere (${verschieden} von ${gemessen.length})`); ok(gemessen.length >= 2 && gemessen.every((w) => w.leisteDa), `die Kopfleiste steht auf allen ${gemessen.length} gemessenen Seiten`); ok(gemessen.length >= 2 && gemessen.every((w) => /gradient/i.test(w.kante || "")), "und traegt unten eine Kante in der Farbe der Seite"); /* GEGENPROBE: Eine Seite OHNE Ton darf die Hausfarbe behalten. Eine Regel, die auch dort zuschlaegt, wuerde eine Farbe erfinden. */ console.log(""); console.log("--- Gegenprobe ---"); { const ohne = await seite.evaluate(() => { const vorher = document.body.dataset.ton; delete document.body.dataset.ton; const a = getComputedStyle(document.body).getPropertyValue("--akzent").trim(); if (vorher !== undefined) document.body.dataset.ton = vorher; return a; }); const mit = gemessen.at(-1)?.akzent || null; ok(!!ohne && !!mit && ohne !== mit, `ohne Ton gilt wieder die Hausfarbe (${ohne} statt ${mit})`); } ok(meckern.length === 0, `der Browser meldet nichts (${meckern[0] || "still"})`); /* ---------- Ende ------------------------------------------------------ */ await browser.close(); await new Promise((r) => vorbau.close(r)); try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Windows haelt manchmal */ } console.log(""); console.log(`${fehler === 0 ? "BESTANDEN" : "FEHLGESCHLAGEN"}` + ` -- ${geprueft} geprueft, ${fehler} Fehler, ${offen} nicht nachsehbar`); process.exit(fehler === 0 ? 0 : 1);