diff --git a/assets/css/webdesign.css b/assets/css/webdesign.css index 25002a68..b66453a7 100644 --- a/assets/css/webdesign.css +++ b/assets/css/webdesign.css @@ -75,6 +75,89 @@ --wd-zeit-lang: .6s; } +/* --------------------------------------------------------------------- + 1b. BILDLAUFLEISTE + + Rückmeldung 22.08.2026: "die leiste rechts zum hoch und runter, kannst + du die mal geil aussehen lassen anstatt einfach so kake weiß." + + Zu Recht. Auf einer durchgehend dunklen Seite ist eine helle + Bildlaufleiste der einzige grelle Streifen im Bild — sie zieht den + Blick genau dorthin, wo nichts Wichtiges steht, und ist bei einer + dunklen Umgebung schlicht blendend. Das widerspricht der Dauervorgabe + "augenschonend". + + Zwei Wege sind nötig, weil kein Browser beide versteht: + + * color-scheme: dark -- Firefox und Safari. Ein einziges Wort, das + dem Browser sagt: das hier ist eine dunkle Oberfläche, nimm deine + dunklen Bedienelemente. Wirkt auch auf Auswahlfelder und + Datumsauswahl, die sonst weiß aufblitzen. + + * ::-webkit-scrollbar -- Chrome, Edge, Opera. Feiner steuerbar, + aber ausschließlich dort verfügbar. + + scrollbar-width/-color (die neue Norm) NICHT verwendet: Sobald es + gesetzt ist, ignoriert Chrome die ::-webkit-Regeln und man bekommt + überall die grobe Fassung. Zwei getrennte Wege ergeben in beiden + Browserfamilien das bessere Ergebnis. + + Die Leiste ist bewusst zurückhaltend: gedeckt, erst beim Zeigen etwas + heller. Sie soll auffindbar sein, nicht leuchten. + --------------------------------------------------------------------- */ +body.wd, +.wd input, .wd select, .wd textarea { + /* Wirkt zusätzlich auf Formularfelder — sonst bleibt die Datumsauswahl + in Firefox ein weißes Rechteck mitten in der dunklen Seite. */ + color-scheme: dark; +} + +/* ACHTUNG bei den Selektoren: "body.wd::-webkit-scrollbar" (ohne + Leerzeichen) und ".wd ::-webkit-scrollbar" (mit Leerzeichen) sind + zwei VERSCHIEDENE Dinge, und man braucht beide. + + ohne Leerzeichen -> die Leiste des body selbst, also die grosse + rechts am Fensterrand. GENAU die war gemeint. + mit Leerzeichen -> die Leisten von Elementen INNERHALB der Seite, + z.B. im Detailfenster der Verwaltung. + + Beim ersten Versuch stand hier nur die zweite Fassung. Alle Messungen + sahen gut aus — an der einzigen Leiste, ueber die sich jemand + beschwert hatte, aenderte sich dadurch nichts. */ +body.wd::-webkit-scrollbar, +.wd ::-webkit-scrollbar { width: 12px; height: 12px; } + +body.wd::-webkit-scrollbar-track, +.wd ::-webkit-scrollbar-track { + background: rgba(8, 14, 24, .6); + border-radius: 999px; +} + +body.wd::-webkit-scrollbar-thumb, +.wd ::-webkit-scrollbar-thumb { + background: linear-gradient(180deg, rgba(127,208,232,.44), rgba(183,157,255,.36)); + border-radius: 999px; + /* Der durchsichtige Rand lässt die Spur seitlich durchscheinen — der + Griff wirkt dadurch schmaler als die Trefferfläche. Bedienbar bleibt + die volle Breite, sichtbar ist nur ein feiner Strich. */ + border: 3px solid transparent; + background-clip: content-box; +} +body.wd::-webkit-scrollbar-thumb:hover, +.wd ::-webkit-scrollbar-thumb:hover { + background: linear-gradient(180deg, rgba(127,208,232,.6), rgba(183,157,255,.5)); + background-clip: content-box; +} +body.wd::-webkit-scrollbar-thumb:active, +.wd ::-webkit-scrollbar-thumb:active { + background: linear-gradient(180deg, rgba(127,208,232,.8), rgba(183,157,255,.7)); + background-clip: content-box; +} +/* Die Ecke zwischen waagerechter und senkrechter Leiste — ohne diese + Zeile bleibt genau dort ein weißes Quadrat stehen. */ +body.wd::-webkit-scrollbar-corner, +.wd ::-webkit-scrollbar-corner { background: transparent; } + /* --------------------------------------------------------------------- 2. GRUNDLAGEN --------------------------------------------------------------------- */ diff --git a/pruef-detail.mjs b/pruef-detail.mjs new file mode 100644 index 00000000..0d572fbc --- /dev/null +++ b/pruef-detail.mjs @@ -0,0 +1,155 @@ +/* Prüft die neue Detailansicht der Verwaltung im echten Browser. + + Die Seite spricht mit dem internen Dienst, den es hier nicht gibt. + Deshalb werden alle Netzanfragen abgefangen und mit erfundenen, aber + REALISTISCHEN Daten beantwortet — insbesondere einer sehr knappen + Anfrage, denn genau die sah vorher schlecht aus (halbe Ansicht + "keine Angabe"). + + Geprüft wird nicht "sieht gut aus", sondern messbar: + - stehen die Spalten wirklich nebeneinander + - ist die rechte Spalte nicht breiter als die linke + - ragt nichts aus dem Fenster + - ist die Bildlaufleiste dunkel statt weiss +*/ +import { chromium } from "playwright"; +import { createServer } from "node:http"; +import { readFileSync, existsSync } from "node:fs"; +import { join, extname } from "node:path"; + +const WURZEL = process.cwd(); +const TYPEN = { ".html": "text/html", ".css": "text/css", ".js": "text/javascript", + ".json": "application/json", ".png": "image/png", ".jpg": "image/jpeg", + ".svg": "image/svg+xml", ".webp": "image/webp", ".ico": "image/x-icon" }; + +const server = createServer((req, res) => { + const pfad = join(WURZEL, decodeURIComponent(req.url.split("?")[0])); + if (existsSync(pfad) && !pfad.endsWith("/")) { + res.writeHead(200, { "content-type": TYPEN[extname(pfad)] || "text/plain" }); + return res.end(readFileSync(pfad)); + } + res.writeHead(404).end("nix"); +}); +await new Promise((r) => server.listen(4321, r)); + +const ANFRAGE_KNAPP = { + id: "t1", nummer: "A-2608-0002", name: "Selbsttest", email: "selbsttest@example.com", + telefon: null, kontaktweg: "email", sprache: "de", paket: "onepager", + ziel: "TESTANFRAGE aus dem Selbsttest - bitte archivieren.", + zielgruppe: null, seiten: null, funktionen: [], details: {}, + hat_texte: null, hat_bilder: null, hat_logo: null, hat_domain: null, + budget: null, termin: null, bemerkung: null, status: "neu", + archiviert: 0, kunde_id: null, + erstellt_am: new Date(Date.now() - 3 * 86400000).toISOString(), +}; + +const browser = await chromium.launch(); +const ergebnisse = []; +function pruefe(name, ok, zusatz) { + ergebnisse.push({ name, ok, zusatz }); + console.log((ok ? " ok " : " FEHL ") + name + (zusatz ? " -> " + zusatz : "")); +} + +for (const [bezeichnung, breite, hoehe] of [["Computer", 1440, 900], ["Handy", 390, 844]]) { + const seite = await browser.newPage({ viewport: { width: breite, height: hoehe } }); + + await seite.route("**/webdesign/admin/**", (route) => { + const u = route.request().url(); + if (u.includes("/anfragen/t1")) { + return route.fulfill({ json: { ok: true, anfrage: ANFRAGE_KNAPP, notizen: [] } }); + } + if (u.includes("/anfragen")) { + return route.fulfill({ json: { ok: true, anfragen: [ANFRAGE_KNAPP] } }); + } + return route.fulfill({ json: { ok: true, kunden: [], projekte: [] } }); + }); + await seite.route("**/auth/**", (r) => r.fulfill({ json: { ok: true, user: { name: "Test" } } })); + + await seite.addInitScript(() => { + sessionStorage.setItem("wd-vw-token", "testtoken"); + sessionStorage.setItem("wd-sitzung-aktiv", "1"); + }); + + const fehlerImProtokoll = []; + seite.on("pageerror", (e) => fehlerImProtokoll.push(e.message)); + + await seite.goto("http://127.0.0.1:4321/webdesign/verwaltung.html"); + await seite.waitForTimeout(900); + + console.log("\n=== " + bezeichnung + " (" + breite + "x" + hoehe + ") ==="); + + const karte = seite.locator(".vw-karte").first(); + if (await karte.count()) { + await karte.click(); + await seite.waitForTimeout(700); + } + + const da = await seite.locator(".vw-detail-spalten").count(); + pruefe(bezeichnung + ": Detailansicht öffnet sich", da === 1); + if (!da) { await seite.close(); continue; } + + const mass = await seite.evaluate(() => { + const li = document.querySelector(".vw-dt-links"); + const re = document.querySelector(".vw-dt-rechts"); + const blatt = document.querySelector(".vw-detail-blatt"); + const leere = document.querySelectorAll(".vw-ohne"); + const blick = document.querySelectorAll(".vw-blick-feld"); + const keineAngabe = Array.from(document.querySelectorAll(".vw-paar dd.vw-leer")).length; + return { + li: li?.getBoundingClientRect(), re: re?.getBoundingClientRect(), + blatt: blatt?.getBoundingClientRect(), + leereZeilen: leere.length, blickFelder: blick.length, keineAngabe, + fensterBreite: innerWidth, fensterHoehe: innerHeight, + ueberlauf: blatt ? blatt.scrollWidth > blatt.clientWidth + 1 : false, + }; + }); + + if (breite >= 900) { + pruefe(bezeichnung + ": Spalten stehen NEBENeinander", + Math.abs(mass.li.top - mass.re.top) < 40, `links y=${Math.round(mass.li.top)}, rechts y=${Math.round(mass.re.top)}`); + pruefe(bezeichnung + ": rechte Spalte ist schmaler als die linke", + mass.re.width < mass.li.width, `${Math.round(mass.li.width)} vs ${Math.round(mass.re.width)}`); + pruefe(bezeichnung + ": Spalten überlappen sich nicht", + mass.li.right <= mass.re.left + 1, `links endet ${Math.round(mass.li.right)}, rechts beginnt ${Math.round(mass.re.left)}`); + } else { + pruefe(bezeichnung + ": Spalten stehen UNTEReinander", + mass.re.top > mass.li.top, `links y=${Math.round(mass.li.top)}, rechts y=${Math.round(mass.re.top)}`); + } + + pruefe(bezeichnung + ": Fenster ragt nicht über den Bildschirm", + mass.blatt.left >= -1 && mass.blatt.right <= mass.fensterBreite + 1, + `${Math.round(mass.blatt.left)} … ${Math.round(mass.blatt.right)} bei ${mass.fensterBreite}`); + pruefe(bezeichnung + ": kein waagerechtes Schieben nötig", !mass.ueberlauf); + pruefe(bezeichnung + ": „Auf einen Blick“ zeigt vier Felder", mass.blickFelder === 4, "n=" + mass.blickFelder); + pruefe(bezeichnung + ": leere Angaben sind zusammengefasst", mass.leereZeilen >= 3, "n=" + mass.leereZeilen); + pruefe(bezeichnung + ": keine einzelne „keine Angabe“-Zeile mehr", mass.keineAngabe === 0, "n=" + mass.keineAngabe); + pruefe(bezeichnung + ": keine Fehler im Protokoll", fehlerImProtokoll.length === 0, fehlerImProtokoll.join(" | ")); + + await seite.screenshot({ path: `pruef-detail-${bezeichnung.toLowerCase()}.png`, fullPage: false }); + await seite.close(); +} + +/* Bildlaufleiste: dunkel statt weiss? */ +const s2 = await browser.newPage({ viewport: { width: 1200, height: 700 } }); +await s2.goto("http://127.0.0.1:4321/webdesign/verwaltung.html"); +await s2.waitForTimeout(400); +const bl = await s2.evaluate(() => { + const stil = getComputedStyle(document.body); + const d = document.createElement("div"); + d.style.cssText = "overflow:auto;height:40px"; + d.innerHTML = "
"; + document.body.appendChild(d); + return { farbschema: stil.colorScheme, hatRegel: !!Array.from(document.styleSheets) + .flatMap((s) => { try { return Array.from(s.cssRules); } catch { return []; } }) + .find((r) => r.selectorText && r.selectorText.includes("scrollbar-thumb")) }; +}); +console.log("\n=== Bildlaufleiste ==="); +pruefe("color-scheme steht auf dark (Firefox/Safari)", bl.farbschema === "dark", bl.farbschema); +pruefe("eigene Regel für den Griff vorhanden (Chrome/Edge)", bl.hatRegel); +await s2.close(); + +await browser.close(); +server.close(); +const fehler = ergebnisse.filter((e) => !e.ok).length; +console.log(`\n===== ${ergebnisse.length - fehler} bestanden, ${fehler} fehlgeschlagen =====`); +process.exitCode = fehler ? 1 : 0; diff --git a/webdesign/ablauf.html b/webdesign/ablauf.html index 2166b5dd..868b2b4b 100644 --- a/webdesign/ablauf.html +++ b/webdesign/ablauf.html @@ -13,8 +13,8 @@ - - + +