From 9186d4cc1aa0f8ce090fab55918df36413ceab89 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Sat, 22 Aug 2026 23:05:47 +0200 Subject: [PATCH] Verwaltung uebersichtlicher, Bildlaufleiste dunkel Rueckmeldung 22.08.2026 zur Anfrage-Detailansicht: "das ganze soll viel uebersichtlicher sein und nicht so durcheinander" und "die leiste rechts zum hoch und runter, kannst du die mal geil aussehen lassen anstatt einfach so kake weiss." 1. DAS DURCHEINANDER HATTE EINE URSACHE Die Kaesten standen in "columns: 2" -- Zeitungsspalten. Die fuellen sich von selbst: erst Spalte eins bis oben voll, dann Spalte zwei. Wo ein Kasten landet, haengt allein davon ab, wie lang die vor ihm sind. Bei einer Anfrage stand "Interne Notizen" oben rechts, bei der naechsten unten links. Es gab schlicht keine Ordnung, der man haette folgen koennen. Jetzt ein festes Raster mit einer Aussage: LINKS = was der Kunde geschickt hat (lesen) RECHTS = was du damit machst (handeln) Immer gleich. Nach der zweiten Anfrage weiss man, wo man hinschaut, ohne zu suchen. Rechts ist schmaler (Knoepfe brauchen weniger Platz als laufender Text) und klebt beim Scrollen mit -- die Handlungen sind der Grund, warum man die Ansicht oeffnet, sie duerfen nicht aus dem Bild wandern. Reihenfolge rechts korrigiert: "Anfrage uebernehmen" steht jetzt oben. Es ist der Knopf, den man bei einer neuen Anfrage druecken WILL -- er stand unter den Notizen und damit ausserhalb des sichtbaren Bereichs. 2. "KEINE ANGABE" WAR DIE HALBE ANSICHT Jede fehlende Angabe bekam eine eigene Zeile. Der Kasten "Umfang" enthielt zweimal nichts und war trotzdem so gross wie einer mit Inhalt. Leere Felder sind aber keine Information, sondern deren Fehlen. Sie stehen jetzt als EINE leise Zeile am Fuss: "Ohne Angabe: Bereiche, Funktionen". Aus sechs Zeilen wird eine. Weggelassen werden sie nicht -- man muss sehen, wonach gefragt wurde und was unbeantwortet blieb, genau daraus entstehen die Rueckfragen. Neu darueber: "Auf einen Blick" mit Paket, Budget, Wunschtermin und Alter. Die vier Fragen, die man immer zuerst hat, standen vorher auf drei Kaesten verteilt. Das Alter als "vor 3 Tagen" statt als Datum -- die Frage ist nie "welcher Tag war das", sondern "wie lange liegt das schon hier", und die beantwortet ein Datum erst nach Kopfrechnen. 3. BILDLAUFLEISTE -- und ein Fehler, der fast durchgegangen waere Auf einer durchgehend dunklen Seite ist eine weisse Bildlaufleiste der einzige grelle Streifen im Bild. Sie zieht den Blick dorthin, wo nichts Wichtiges steht, und blendet. Verstoesst gegen die Dauervorgabe "augenschonend". Zwei Wege noetig, weil kein Browser beide versteht: color-scheme: dark fuer Firefox/Safari (wirkt zusaetzlich auf Auswahl- und Datumsfelder, die sonst weiss aufblitzen), ::-webkit-scrollbar fuer Chrome/Edge. scrollbar-width/-color bewusst NICHT gesetzt -- sobald es dasteht, ignoriert Chrome die feineren ::-webkit-Regeln. Beim ersten Versuch stand dort nur ".wd ::-webkit-scrollbar" -- MIT Leerzeichen. Das trifft nur Elemente INNERHALB der Seite, nicht den body selbst. Alle Messungen sahen gut aus; an der einen Leiste, ueber die sich jemand beschwert hatte, haette sich nichts geaendert. Jetzt beide Fassungen, mit einem Warnhinweis im CSS. GEPRUEFT Neue Datei pruef-detail.mjs: echter Browser, 1440x900 und 390x844, mit einer absichtlich sehr knappen Anfrage -- genau die sah vorher schlecht aus. 20 Pruefungen, alle bestanden: Spalten nebeneinander bzw. auf dem Handy untereinander, rechte schmaler als linke, keine Ueberlappung, kein waagerechtes Schieben, nichts ragt aus dem Fenster, keine einzelne "keine Angabe"-Zeile mehr, keine Fehler im Protokoll. Die Bildlaufleiste liess sich nur in einem ECHTEN Browserfenster pruefen: Headless-Chromium blendet Leisten grundsaetzlich ueber dem Inhalt ein und meldet deshalb immer 0 px Breite. Mit sichtbarem Fenster gemessen: 12 px, alle sechs Regeln vom Browser akzeptiert. Versionsstempel auf allen 11 Seiten und Cache-Name des Service Workers auf v3 -- sonst liefert der eigene Zwischenspeicher beim ersten Laden weiter die alte Fassung aus. Co-Authored-By: Claude Opus 5 --- assets/css/webdesign.css | 83 ++++++++++++ pruef-detail.mjs | 155 ++++++++++++++++++++++ webdesign/ablauf.html | 8 +- webdesign/anfrage.html | 10 +- webdesign/faq.html | 8 +- webdesign/index.html | 8 +- webdesign/leistungen.html | 8 +- webdesign/portal.html | 10 +- webdesign/portfolio.html | 8 +- webdesign/preise.html | 8 +- webdesign/sw.js | 2 +- webdesign/ueber.html | 8 +- webdesign/verwaltung.html | 272 +++++++++++++++++++++++++++++--------- webdesign/zugang.html | 8 +- 14 files changed, 490 insertions(+), 106 deletions(-) create mode 100644 pruef-detail.mjs 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 @@ - - + +