diff --git a/pruef-verwaltung-schmuck.mjs b/pruef-verwaltung-schmuck.mjs
index 43a49ecc..ba0282d6 100644
--- a/pruef-verwaltung-schmuck.mjs
+++ b/pruef-verwaltung-schmuck.mjs
@@ -1,14 +1,21 @@
/* =====================================================================
- Prueft den Bildschmuck im Verwaltungsbereich.
+ Prueft die Buehne im Verwaltungsbereich.
- Die eigentliche Frage ist nicht "sieht man das Bild?", sondern:
- Kostet es Lesbarkeit? Die Verwaltung ist ein Arbeitsplatz -- ein
- Motiv, das hinter einer Zahlenspalte liegt, ist genau die Art von
- Schoenheit, die ein Werkzeug unbrauchbar macht.
+ Das Konzept hat sich umgedreht, und damit auch die Pruefung.
- Seit der Bereichs-Signatur kommt eine zweite Frage dazu: Sind die
- sechs Bereiche wirklich unterscheidbar, oder sieht nur der Code so
- aus? Deshalb wird jeder Reiter einzeln angeklickt und gemessen.
+ Vorher: ein schmaler Streifen, auf 190 % gezoomt, davon ein Ausschnitt
+ gewaehlt und die linke Haelfte voll zugedeckt -- damit Text darauf
+ lesbar bleibt. Gemessen wurde, ob das Bild leise genug ist. Uebrig
+ blieb ein Zipfel.
+
+ Jetzt: Das Motiv ist die Buehne -- bildschirmfuellend, ungeschnitten,
+ ungedimmt. Der Inhalt schwebt als Milchglas darueber. Text steht nie
+ direkt auf dem Bild.
+
+ Deshalb wird jetzt das Gegenteil geprueft:
+ 1. Ist das Bild wirklich GANZ da? (kein Zoom, kein Ausschnitt)
+ 2. Traegt jede Flaeche, auf der gelesen wird, auch Glas?
+ 3. Bleibt der Text darauf lesbar -- an echten Bildpunkten gemessen?
===================================================================== */
import { chromium } from "playwright";
import http from "http";
@@ -54,8 +61,39 @@ function verhaeltnis(a, b) {
const BEREICHE = ["uebersicht", "anfragen", "projekte", "kunden", "zahlungen", "postfach"];
-/* Misst, wie stark der dunkle Grund in einem Bereich schwankt. Hohe
- Schwankung heisst: Da liegt ein Bild unter der Schrift. */
+/* Setzt einen Bereich und legt Beispielinhalt ein. Ohne Inhalt liesse
+ sich nicht messen, ob Text ueber der Buehne lesbar bleibt -- und genau
+ das ist die Frage, an der das ganze Konzept haengt.
+
+ Der Aufbau passiert vor JEDER Messung neu: Der Datenabruf laeuft im
+ Hintergrund weiter und klappt den Arbeitsbereich verzoegert wieder zu
+ (Zugangspruefung, kein Fehler). */
+async function bereichAufbauen(seite, bereich) {
+ await seite.evaluate((b) => {
+ document.querySelector("#vw-anmeldung").setAttribute("hidden", "");
+ const bereichEl = document.querySelector("#vw-bereich");
+ bereichEl.removeAttribute("hidden");
+ document.documentElement.setAttribute("data-vw-bereich", b);
+ document.querySelectorAll("[data-tab]").forEach((x) =>
+ x.classList.toggle("vw-an", x.dataset.tab === b));
+
+ let probe = document.querySelector("#vw-probe");
+ if (!probe) {
+ probe = document.createElement("div");
+ probe.id = "vw-probe";
+ probe.innerHTML =
+ '
' +
+ '
Vanessa Geske
' +
+ '
vanessa@example.com
' +
+ '
Zwei Projekte, eines davon laufend.
';
+ bereichEl.appendChild(probe);
+ }
+ }, bereich);
+ await seite.waitForTimeout(700);
+}
+
+/* Misst die Schwankung des Untergrunds an echten Bildpunkten. Hohe
+ Schwankung heisst: Da liegt Bildstruktur unter der Schrift. */
async function unruheAn(seite, wahl) {
const bild = await seite.locator(wahl).first().screenshot();
return seite.evaluate(async (daten) => {
@@ -68,7 +106,7 @@ async function unruheAn(seite, wahl) {
const grund = [];
for (let i = 0; i < d.length; i += 4) {
const h = 0.2126 * d[i] + 0.7152 * d[i + 1] + 0.0722 * d[i + 2];
- if (h < 110) grund.push(h);
+ if (h < 120) grund.push(h);
}
if (grund.length < 50) return null;
const m = grund.reduce((a, b) => a + b, 0) / grund.length;
@@ -78,296 +116,177 @@ async function unruheAn(seite, wahl) {
srv.listen(4404, async () => {
const browser = await chromium.launch();
-
- /* =================================================================
- TEIL 1 — die Anmeldemaske
- ================================================================= */
- for (const [gr, vp] of [["Computer", { width: 1440, height: 950 }],
- ["Handy", { width: 390, height: 844 }]]) {
- console.log(`\n===== ANMELDUNG (${gr}) =====`);
- const seite = await browser.newPage({ viewport: vp });
- await seite.goto("http://127.0.0.1:4404/webdesign/verwaltung.html");
- await seite.waitForTimeout(1400);
-
- const login = await seite.evaluate(() => {
- const el = document.querySelector("#vw-anmeldung");
- if (!el) return null;
- el.removeAttribute("hidden");
- const vor = getComputedStyle(el, "::before");
- const treffer = vor.backgroundImage.match(/img\/([a-z0-9-]+\.webp)/i);
- return { datei: treffer ? treffer[1] : "(keins)", deckkraft: parseFloat(vor.opacity) };
- });
- pruefe(`${gr}: die Anmeldekarte hat ein Motiv`,
- login && login.datei.includes("wappen"), login && login.datei);
-
- await seite.waitForTimeout(500);
- const messen = async () => {
- const bild = await seite.locator("#vw-anmeldung p.wd-klein").first().screenshot();
- return seite.evaluate(async (daten) => {
- const bl = await fetch("data:image/png;base64," + daten).then((r) => r.blob());
- const bm = await createImageBitmap(bl);
- const c = new OffscreenCanvas(bm.width, bm.height);
- const x = c.getContext("2d");
- x.drawImage(bm, 0, 0);
- const d = x.getImageData(0, 0, bm.width, bm.height).data;
- const grund = [];
- for (let i = 0; i < d.length; i += 4) {
- const h = 0.2126 * d[i] + 0.7152 * d[i + 1] + 0.0722 * d[i + 2];
- if (h < 110) grund.push(h);
- }
- if (grund.length < 50) return null;
- const m = grund.reduce((a, b) => a + b, 0) / grund.length;
- return Math.round(Math.sqrt(grund.reduce((a, b) => a + (b - m) ** 2, 0) / grund.length));
- }, bild.toString("base64"));
- };
- const mitBild = await messen();
- /* Die Vergleichsmessung ist der Kern. Ein fester Grenzwert waere
- geraten: Schon die weichgezeichneten Buchstabenkanten allein
- erzeugen eine Schwankung um 12. Gefragt ist, wie viel Unruhe DAS
- MOTIV hinzufuegt. */
- await seite.evaluate(() =>
- document.querySelector("#vw-anmeldung").classList.remove("wd-motiv", "wd-motiv--wappen"));
- await seite.waitForTimeout(400);
- const ohneBild = await messen();
- await seite.waitForTimeout(200);
- const zusatz = (mitBild ?? 99) - (ohneBild ?? 0);
- pruefe(`${gr}: das Motiv macht den Textgrund kaum unruhiger`,
- mitBild !== null && ohneBild !== null && zusatz <= 5,
- `mit ${mitBild}, ohne ${ohneBild} → plus ${zusatz}`);
-
- await seite.close();
- }
-
- /* =================================================================
- TEIL 2 — die sechs Bereichs-Signaturen
- ================================================================= */
- const seite = await browser.newPage({ viewport: { width: 1440, height: 950 } });
+ const seite = await browser.newPage({ viewport: { width: 1440, height: 900 } });
const skriptfehler = [];
seite.on("pageerror", (e) => skriptfehler.push(String(e).slice(0, 140)));
await seite.goto("http://127.0.0.1:4404/webdesign/verwaltung.html");
- await seite.waitForTimeout(1400);
- await seite.evaluate(() => {
- document.querySelector("#vw-anmeldung").setAttribute("hidden", "");
- document.querySelector("#vw-bereich").removeAttribute("hidden");
+ await seite.waitForTimeout(1500);
+
+ console.log("===== 1. IST DAS BILD GANZ DA? =====");
+ await bereichAufbauen(seite, "kunden");
+ const buehne = await seite.evaluate(() => {
+ const el = document.querySelector(".vw-buehne");
+ if (!el) return null;
+ const st = getComputedStyle(el);
+ const r = el.getBoundingClientRect();
+ const treffer = st.backgroundImage.match(/img\/([a-z0-9-]+\.webp)/i);
+ return {
+ datei: treffer ? treffer[1] : "(keins)",
+ groesse: st.backgroundSize,
+ lage: st.backgroundPosition,
+ haltung: st.position,
+ breite: Math.round(r.width),
+ hoehe: Math.round(r.height),
+ z: parseInt(st.zIndex, 10),
+ klicks: st.pointerEvents,
+ versteckt: el.getAttribute("aria-hidden"),
+ };
});
- await seite.waitForTimeout(500);
+ if (!buehne) { pruefe("Buehne vorhanden", false); }
+ else {
+ /* Das ist der Kern der Korrektur. Beim ersten Anlauf stand hier
+ "190% auto" plus ein Ausschnitt wie "84% 46%" -- damit war der
+ groesste Teil des Bildes weggeschnitten. */
+ pruefe("das Bild wird NICHT gezoomt", buehne.groesse === "cover", buehne.groesse);
+ pruefe("und NICHT einseitig ausgeschnitten",
+ /50%\s+50%|center/.test(buehne.lage), buehne.lage);
+ pruefe("die Buehne fuellt den ganzen Bildschirm",
+ buehne.breite >= 1430 && buehne.hoehe >= 890, `${buehne.breite}x${buehne.hoehe}`);
+ pruefe("sie steht fest, der Inhalt scrollt darueber",
+ buehne.haltung === "fixed", buehne.haltung);
+ pruefe("sie liegt hinter allem", buehne.z < 0, "z-index " + buehne.z);
+ pruefe("sie faengt keine Klicks ab", buehne.klicks === "none", buehne.klicks);
+ pruefe("sie ist als Zierde ausgezeichnet", buehne.versteckt === "true");
+ }
- /* Erst EINMAL echt klicken, um zu beweisen, dass der Reiter die
- Signatur ueberhaupt umschaltet. Danach geht das nicht mehr: Beim
- Klick laedt die Seite Daten, merkt dass niemand angemeldet ist, und
- klappt den Arbeitsbereich korrekterweise wieder zu. Das ist die
- Zugangspruefung, kein Fehler -- aber es beendet den Rundgang.
-
- Deshalb wird der Bereich danach direkt gesetzt, so wie der
- Klick-Listener es auch tut. Geprueft wird dabei das Stilblatt, und
- das kennt nur das Kennzeichen am Wurzelelement. */
- await seite.click('[data-tab="anfragen"]');
- await seite.waitForTimeout(900);
- const nachKlick = await seite.evaluate(() =>
- document.documentElement.getAttribute("data-vw-bereich"));
- console.log("\n===== SCHALTET DER REITER UM? =====");
- pruefe("ein echter Reiterklick setzt den Bereich", nachKlick === "anfragen", nachKlick);
-
- /* Zurueck in den sichtbaren Zustand fuer die Messreihe. */
- await seite.evaluate(() => {
- document.querySelector("#vw-anmeldung").setAttribute("hidden", "");
- document.querySelector("#vw-bereich").removeAttribute("hidden");
+ console.log("\n===== 2. TRAEGT ALLES GLAS, WORAUF GELESEN WIRD? =====");
+ const glas = await seite.evaluate(() => {
+ const pruefen = [".wd-karte", ".vw-filter-knopf", ".vw-wer"];
+ const raus = {};
+ for (const w of pruefen) {
+ const el = document.querySelector("#vw-bereich " + w);
+ if (!el) { raus[w] = "(nicht gefunden)"; continue; }
+ const st = getComputedStyle(el);
+ raus[w] = st.backdropFilter || st.webkitBackdropFilter || "keins";
+ }
+ return raus;
});
- await seite.waitForTimeout(300);
+ for (const [w, wert] of Object.entries(glas)) {
+ if (wert === "(nicht gefunden)") continue;
+ pruefe(` ${w} hat Rueckseiten-Unschaerfe`, /blur/.test(wert), wert.slice(0, 34));
+ }
- const gesehen = [];
+ console.log("\n===== 3. BLEIBT DER TEXT LESBAR? =====");
for (const bereich of BEREICHE) {
- console.log(`\n===== BEREICH: ${bereich} =====`);
- /* Der Datenabruf laeuft im Hintergrund weiter und klappt den
- Arbeitsbereich verzoegert wieder zu (Zugangspruefung). Deshalb vor
- JEDER Messung erneut aufmachen, nicht nur einmal am Anfang. */
- await seite.evaluate((b) => {
- document.querySelector("#vw-anmeldung").setAttribute("hidden", "");
- document.querySelector("#vw-bereich").removeAttribute("hidden");
- document.documentElement.setAttribute("data-vw-bereich", b);
- document.querySelectorAll("[data-tab]").forEach((x) =>
- x.classList.toggle("vw-an", x.dataset.tab === b));
- }, bereich);
- await seite.waitForTimeout(500);
+ await bereichAufbauen(seite, bereich);
- const d = await seite.evaluate(() => {
- const wurzel = document.documentElement.getAttribute("data-vw-bereich");
- const s = document.querySelector(".vw-schmuck");
- const st = getComputedStyle(s);
- const treffer = st.backgroundImage.match(/img\/([a-z0-9-]+\.webp)/i);
- const reiter = document.querySelector(".vw-filter-knopf.vw-an");
- const rs = getComputedStyle(reiter);
- const titel = document.querySelector("#vw-titel");
- /* Untergrund der Seite suchen -- die Reiterflaeche ist
- halbdurchsichtig und muss darueber verrechnet werden. */
- const seitenGrund = getComputedStyle(document.body).backgroundColor
- .match(/[\d.]+/g).slice(0, 3).map(Number);
- return {
- wurzel,
- datei: treffer ? treffer[1] : "(keins)",
- deckkraft: parseFloat(st.opacity),
- blick: st.backgroundPosition,
- reiterText: reiter.textContent.trim().slice(0, 14),
- reiterFarbe: rs.color.match(/[\d.]+/g).slice(0, 3).map(Number),
- reiterFlaeche: rs.backgroundColor.match(/[\d.]+/g),
- titelFarbe: getComputedStyle(titel).color.match(/[\d.]+/g).slice(0, 3).map(Number),
- seitenGrund,
- lumen: getComputedStyle(document.querySelector("#vw-bereich")).getPropertyValue("--vw-farbe").trim(),
- };
- });
-
- pruefe(` Kennzeichen steht am Wurzelelement`, d.wurzel === bereich, d.wurzel);
- gesehen.push({ bereich, datei: d.datei, farbe: d.lumen, blick: d.blick });
-
- /* Der aktive Reiter muss lesbar bleiben -- auch im Bereich
- "Kunden", dessen Leitfarbe Prism Indigo als SCHRIFT durchfaellt.
- Genau deshalb faerbt die Leitfarbe dort nur die Flaeche. */
- const a = d.reiterFlaeche.length === 4 ? parseFloat(d.reiterFlaeche[3]) : 1;
- const flaeche = d.seitenGrund.map((u, i) =>
- Math.round(Number(d.reiterFlaeche[i]) * a + u * (1 - a)));
- const v = verhaeltnis(d.reiterFarbe, flaeche);
- pruefe(` der aktive Reiter bleibt lesbar (AA = 4,5:1)`, v >= 4.5, v.toFixed(2) + ":1");
-
- const vt = verhaeltnis(d.titelFarbe, d.seitenGrund);
- pruefe(` der Titel bleibt lesbar`, vt >= 4.5, vt.toFixed(2) + ":1");
- /* Frueher stand hier "Deckkraft unter 20 %". Dieser Massstab ist
- hinfaellig, seit das Motiv nach rechts gerueckt ist: Es darf jetzt
- kraeftig sein, WEIL es nicht mehr hinter der Schrift liegt. Eine
- Zahl allein sagt darueber nichts.
-
- Geprueft wird stattdessen das, worauf es ankommt -- wie ruhig der
- Grund unter der Reiterzeile ist, mit Motiv gegen ohne Motiv. */
- const offen = () => seite.evaluate(() => {
- document.querySelector("#vw-anmeldung").setAttribute("hidden", "");
- document.querySelector("#vw-bereich").removeAttribute("hidden");
- });
- await offen();
- const grundMit = await unruheAn(seite, ".vw-tabs");
+ const aufGlas = await unruheAn(seite, "#vw-probe .wd-karte");
await seite.evaluate(() => {
- document.querySelector(".vw-schmuck").style.visibility = "hidden";
+ document.querySelector(".vw-buehne").style.visibility = "hidden";
});
- await offen();
await seite.waitForTimeout(350);
- const grundOhne = await unruheAn(seite, ".vw-tabs");
+ await bereichAufbauen(seite, bereich);
+ const ohneBuehne = await unruheAn(seite, "#vw-probe .wd-karte");
await seite.evaluate(() => {
- document.querySelector(".vw-schmuck").style.visibility = "";
+ document.querySelector(".vw-buehne").style.visibility = "";
});
await seite.waitForTimeout(250);
- const mehr = (grundMit ?? 99) - (grundOhne ?? 0);
- pruefe(` der Grund unter den Reitern bleibt ruhig`,
- grundMit !== null && grundOhne !== null && mehr <= 6,
- `mit ${grundMit}, ohne ${grundOhne} → plus ${mehr}`);
+
+ const mehr = (aufGlas ?? 99) - (ohneBuehne ?? 0);
+ /* Etwas Struktur DARF durchkommen -- das ist ja der Reiz. Sie darf
+ nur nicht so stark werden, dass die Schrift darauf flimmert. */
+ pruefe(` ${bereich}: der Kachelgrund bleibt ruhig`,
+ aufGlas !== null && ohneBuehne !== null && mehr <= 8,
+ `mit Buehne ${aufGlas}, ohne ${ohneBuehne} → plus ${mehr}`);
+
+ await bereichAufbauen(seite, bereich);
+ /* Der tatsaechliche Kachelgrund wird aus dem BILD gelesen, nicht aus
+ dem Stilblatt: Die Kachel ist halbdurchsichtig, ihr rechnerischer
+ Wert sagt nichts darueber, was am Ende darunter liegt. */
+ const stelle = await seite.evaluate(() => {
+ const k = document.querySelector("#vw-probe .wd-karte");
+ const r = k.getBoundingClientRect();
+ return { x: Math.round(r.x + r.width * 0.72), y: Math.round(r.y + r.height * 0.62) };
+ });
+ const punkt = await seite.screenshot({
+ clip: { x: stelle.x, y: stelle.y, width: 10, height: 10 } });
+ const grundfarbe = await seite.evaluate(async (daten) => {
+ const bl = await fetch("data:image/png;base64," + daten).then((r) => r.blob());
+ const bm = await createImageBitmap(bl);
+ const c = new OffscreenCanvas(bm.width, bm.height);
+ const x = c.getContext("2d");
+ x.drawImage(bm, 0, 0);
+ const d = x.getImageData(0, 0, bm.width, bm.height).data;
+ let r = 0, g = 0, b = 0, n = 0;
+ for (let i = 0; i < d.length; i += 4) { r += d[i]; g += d[i + 1]; b += d[i + 2]; n++; }
+ return [Math.round(r / n), Math.round(g / n), Math.round(b / n)];
+ }, punkt.toString("base64"));
+
+ const schrift = await seite.evaluate(() =>
+ getComputedStyle(document.querySelector("#vw-probe-text"))
+ .color.match(/[\d.]+/g).slice(0, 3).map(Number));
+
+ const v = verhaeltnis(schrift, grundfarbe);
+ pruefe(` ${bereich}: Text auf der Kachel erfuellt AA (4,5:1)`,
+ v >= 4.5, v.toFixed(2) + ":1");
}
- /* ---- Die Kernfrage: sind die sechs wirklich unterscheidbar? ---- */
- console.log("\n===== SIND DIE BEREICHE UNTERSCHEIDBAR? =====");
- for (const g of gesehen) console.log(` ${g.bereich.padEnd(11)} ${g.datei.padEnd(22)} ${g.farbe}`);
-
- const farben = new Set(gesehen.map((g) => g.farbe));
- pruefe("jeder Bereich hat eine eigene Leitfarbe", farben.size === 6,
- farben.size + " von 6 verschieden");
-
- /* Motive duerfen sich wiederholen -- es gibt fuenf Bilder fuer sechs
- Bereiche. Aber dann MUSS der Ausschnitt ein anderer sein, sonst
- sehen zwei Bereiche gleich aus. */
- const doppelt = {};
- for (const g of gesehen) (doppelt[g.datei] ||= []).push(g);
- let ausschnittOk = true, hinweis = [];
- for (const [datei, liste] of Object.entries(doppelt)) {
- if (liste.length < 2) continue;
- const blicke = new Set(liste.map((l) => l.blick));
- if (blicke.size !== liste.length) { ausschnittOk = false; hinweis.push(datei); }
- else hinweis.push(`${datei}: ${liste.length}x, aber je anderer Ausschnitt`);
+ console.log("\n===== 4. WECHSELT DER RAUM WIRKLICH? =====");
+ const gesehen = [];
+ for (const bereich of BEREICHE) {
+ await bereichAufbauen(seite, bereich);
+ const d = await seite.evaluate(() => {
+ const st = getComputedStyle(document.querySelector(".vw-buehne"));
+ const t = st.backgroundImage.match(/img\/([a-z0-9-]+\.webp)/i);
+ return {
+ datei: t ? t[1] : "(keins)",
+ farbe: getComputedStyle(document.documentElement)
+ .getPropertyValue("--vw-farbe").trim(),
+ };
+ });
+ gesehen.push({ bereich, ...d });
+ console.log(` ${bereich.padEnd(11)} ${d.datei.padEnd(22)} ${d.farbe}`);
}
- pruefe("mehrfach genutzte Motive zeigen einen anderen Ausschnitt",
- ausschnittOk, hinweis.join("; ") || "kein Motiv doppelt");
+ pruefe("jeder Bereich hat eine eigene Leitfarbe",
+ new Set(gesehen.map((g) => g.farbe)).size === 6,
+ new Set(gesehen.map((g) => g.farbe)).size + " von 6");
+ pruefe("mindestens fuenf verschiedene Motive",
+ new Set(gesehen.map((g) => g.datei)).size >= 5,
+ new Set(gesehen.map((g) => g.datei)).size + " verschiedene");
- /* ---- Und die alte Regel gilt weiter ---- */
- const stoerung = await seite.evaluate(() => {
- const schlimm = [];
- for (const el of document.querySelectorAll("table, .wd-tabelle, .vw-liste")) {
- let p = el;
- while (p && p !== document.body) {
- if (p.classList && (p.classList.contains("wd-motiv") ||
- p.classList.contains("vw-schmuck"))) {
- schlimm.push(el.tagName);
- break;
- }
- p = p.parentElement;
- }
- }
- return schlimm;
- });
- pruefe("kein Motiv hinter Tabellen oder Listen", stoerung.length === 0,
- stoerung.join(", ") || "keine");
pruefe("kein Skriptfehler", skriptfehler.length === 0, skriptfehler[0] || "");
-
- await seite.evaluate(() => {
- document.documentElement.setAttribute("data-vw-bereich", "uebersicht");
- document.querySelectorAll("[data-tab]").forEach((x) =>
- x.classList.toggle("vw-an", x.dataset.tab === "uebersicht"));
- });
- await seite.waitForTimeout(700);
+ await bereichAufbauen(seite, "kunden");
await seite.screenshot({ path: "pruef-vw-schmuck-computer.png" });
await seite.close();
- /* ---- Bewegung aus, wenn der Nutzer das so eingestellt hat ---- */
- console.log("\n===== REDUZIERTE BEWEGUNG =====");
+ console.log("\n===== 5. REDUZIERTE BEWEGUNG =====");
const ruhig = await browser.newPage({
- viewport: { width: 1440, height: 950 }, reducedMotion: "reduce" });
+ viewport: { width: 1440, height: 900 }, reducedMotion: "reduce" });
await ruhig.goto("http://127.0.0.1:4404/webdesign/verwaltung.html");
- await ruhig.waitForTimeout(1200);
- await ruhig.evaluate(() => {
- document.querySelector("#vw-anmeldung").setAttribute("hidden", "");
- document.querySelector("#vw-bereich").removeAttribute("hidden");
- });
- await ruhig.click('[data-tab="projekte"]');
- await ruhig.waitForTimeout(700);
- /* Der Klick klappt den Bereich zu (Zugangspruefung) -- fuer die
- Messung wieder aufmachen. Das Kennzeichen hat der Klick gesetzt. */
- await ruhig.evaluate(() => {
- document.querySelector("#vw-anmeldung").setAttribute("hidden", "");
- document.querySelector("#vw-bereich").removeAttribute("hidden");
- });
- await ruhig.waitForTimeout(300);
+ await ruhig.waitForTimeout(1300);
+ await bereichAufbauen(ruhig, "projekte");
const bewegung = await ruhig.evaluate(() => {
- const s = document.querySelector(".vw-schmuck");
- const st = getComputedStyle(s);
- return {
- animation: st.animationName,
- sichtbar: parseFloat(st.opacity),
- bereich: document.documentElement.getAttribute("data-vw-bereich"),
- };
+ const st = getComputedStyle(document.querySelector(".vw-buehne"));
+ return { animation: st.animationName, sichtbar: st.visibility };
});
pruefe("keine Animation bei reduzierter Bewegung",
bewegung.animation === "none", bewegung.animation);
- /* Wichtig: abschalten heisst NICHT verschwinden. Das Motiv muss da
- sein, nur ohne Bewegung. */
- pruefe("das Motiv ist trotzdem sichtbar", bewegung.sichtbar > 0.05,
- Math.round(bewegung.sichtbar * 100) + " %");
- pruefe("der Bereichswechsel wirkt trotzdem", bewegung.bereich === "projekte",
- bewegung.bereich);
+ /* Abschalten heisst NICHT verschwinden. */
+ pruefe("die Buehne ist trotzdem da", bewegung.sichtbar === "visible", bewegung.sichtbar);
await ruhig.close();
- /* ---- Handy ---- */
- console.log("\n===== HANDY =====");
+ console.log("\n===== 6. HANDY =====");
const handy = await browser.newPage({ viewport: { width: 390, height: 844 } });
await handy.goto("http://127.0.0.1:4404/webdesign/verwaltung.html");
- await handy.waitForTimeout(1200);
- await handy.evaluate(() => {
- document.querySelector("#vw-anmeldung").setAttribute("hidden", "");
- document.querySelector("#vw-bereich").removeAttribute("hidden");
- });
- await handy.evaluate(() => {
- document.documentElement.setAttribute("data-vw-bereich", "kunden");
- });
- await handy.waitForTimeout(600);
+ await handy.waitForTimeout(1300);
+ await bereichAufbauen(handy, "zahlungen");
const hd = await handy.evaluate(() => {
- const st = getComputedStyle(document.querySelector(".vw-schmuck"));
+ const st = getComputedStyle(document.querySelector(".vw-buehne"));
const t = st.backgroundImage.match(/img\/([a-z0-9-]+\.webp)/i);
- return { datei: t ? t[1] : "(keins)", deckkraft: parseFloat(st.opacity) };
+ return { datei: t ? t[1] : "(keins)", groesse: st.backgroundSize };
});
pruefe("Handy: kleine Bildfassung", hd.datei.includes("-klein"), hd.datei);
+ pruefe("Handy: auch hier kein Zoom", hd.groesse === "cover", hd.groesse);
const ueberlauf = await handy.evaluate(() =>
document.documentElement.scrollWidth - document.documentElement.clientWidth);
pruefe("Handy: nichts laeuft seitlich ueber", ueberlauf <= 1, ueberlauf + "px");
diff --git a/pruef-vw-schmuck-computer.png b/pruef-vw-schmuck-computer.png
index 71ca0860..69b140f1 100644
Binary files a/pruef-vw-schmuck-computer.png and b/pruef-vw-schmuck-computer.png differ
diff --git a/pruef-vw-schmuck-handy.png b/pruef-vw-schmuck-handy.png
index 1aa35952..a1ce5733 100644
Binary files a/pruef-vw-schmuck-handy.png and b/pruef-vw-schmuck-handy.png differ
diff --git a/webdesign/sw.js b/webdesign/sw.js
index ed69a480..89d4d712 100644
--- a/webdesign/sw.js
+++ b/webdesign/sw.js
@@ -34,7 +34,7 @@
Start alle alten Zwischenspeicher weg. Muss bei jeder Änderung an den
Dateien unten hochgezählt werden, sonst hängen Nutzer auf einem alten
Stand fest. */
-const CACHE_NAME = "dogfather-webdesign-v36";
+const CACHE_NAME = "dogfather-webdesign-v37";
/* Bausteine, die die Oberfläche zum Anzeigen braucht. Bewusst KEINE
HTML-Datei in dieser Liste. */
diff --git a/webdesign/verwaltung.html b/webdesign/verwaltung.html
index 379f399e..14569756 100644
--- a/webdesign/verwaltung.html
+++ b/webdesign/verwaltung.html
@@ -133,130 +133,136 @@
--vw-blick: 12% 54%;
}
- /* Warum 190% statt cover: Bei "cover" skaliert der Browser das Bild
- auf die Breite des Streifens -- die volle Bildbreite ist dann immer
- sichtbar, und eine waagerechte Ausschnittsangabe bewirkt schlicht
- nichts. Genau das ist hier passiert: Alle Werte von 62% bis 84%
- zeigten dasselbe Bild. Erst mit einem Zoom ueber 100% entsteht
- ueberhaupt Spielraum, den --vw-blick verschieben kann.
+ /* ---------- DIE BUEHNE ----------
- 190% ist so gewaehlt, dass der Streifen auf jeder Bildschirmbreite
- gefuellt bleibt: Die Bildbreite ist immer 1,9-mal so breit wie der
- Streifen, die Hoehe waechst proportional mit.
+ Das Motiv fuellt den ganzen Bildschirm. Fest stehend, ungeschnitten,
+ in voller Leuchtkraft. Es wird NICHT gezoomt und NICHT einseitig
+ abgedunkelt.
- Zu den Ausschnitten (--vw-blick): Sie sind so gewaehlt, dass KEIN
- Logo angeschnitten im Streifen haengt. Ein halber Hundekopf mitten
- im Bild sieht nach Versehen aus, nicht nach Absicht -- und das Logo
- steht ohnehin schon oben links in der Kopfleiste. Gezeigt werden
- stattdessen die Kristallformationen, also das, was an den Motiven
- tatsaechlich spektakulaer ist. */
- .vw-schmuck {
- position: absolute;
- top: 0; left: 0; right: 0;
- /* Genau die Kopfzone: Titel plus Reiterreihe, mehr nicht. Der
- Streifen ist zu Ende, BEVOR die erste Kachel anfaengt.
+ Warum das der zweite Anlauf ist: Vorher war es ein schmaler Streifen
+ mit 190 % Zoom, aus dem ein Ausschnitt gewaehlt und dessen linke
+ Haelfte voll zugedeckt wurde -- damit der Text darauf lesbar bleibt.
+ Uebrig blieb ein Zipfel. Von einer Kristallwelt, die ueber das ganze
+ Bild geht, war praktisch nichts mehr zu sehen.
- Vorher war er fast doppelt so hoch und reichte in den
- Kachelbereich hinein. Dadurch musste er blass bleiben, sonst
- haette er durch die halbdurchsichtigen Kacheln geschienen. Kurz
- und kraeftig ist beides besser: mehr Wirkung dort, wo Platz ist,
- und null Stoerung dort, wo gearbeitet wird. */
- height: clamp(186px, 25vh, 250px);
- z-index: -1;
+ Der Denkfehler war, Bild und Text auf dieselbe Ebene zu zwingen und
+ dann das Bild zu opfern. Jetzt liegen sie auf zwei Ebenen: Das Bild
+ ist die Buehne, der Inhalt schwebt als Milchglas darueber. Dadurch
+ steht Text NIE direkt auf dem Bild -- und das Bild muss nirgends
+ mehr weichen. */
+
+ .vw-buehne {
+ position: fixed;
+ inset: 0;
+ z-index: -2;
pointer-events: none;
-
- /* ZWEI Ebenen, und die Reihenfolge ist der ganze Trick:
- oben eine Deckschicht, darunter das Motiv.
-
- Die Deckschicht ist LINKS fast undurchsichtig und wird nach
- rechts hin offen. Links stehen Titel und Reiter -- dort bleibt es
- ruhig. Rechts ist die Flaeche leer, dort darf das Bild richtig
- auftreten.
-
- Der erste Versuch war gleichmaessig bei 17 % Deckkraft. Ergebnis:
- Das Wappen war ueberall gleich schwach zu ahnen, ein Fleck statt
- eines Bildes -- und trotzdem lag es hinter der Schrift. Diese
- Loesung ist beides zugleich staerker und ruhiger, weil Bild und
- Text sich schlicht nicht mehr denselben Platz teilen. */
- background:
- /* Bis 46 % voll deckend -- das ist genau die Zone, in der Titel
- und Reiterreihe liegen. Der Wert ist gemessen, nicht geschaetzt:
- Bei 34 % schob das Thron-Motiv seine hellen Kristallfluegel bis
- unter die Reiter und machte den Grund dort merklich unruhiger
- als ohne Bild. */
- linear-gradient(100deg,
- rgb(3 8 16 / 1) 0%,
- rgb(3 8 16 / 1) 46%,
- rgb(3 8 16 / .52) 64%,
- rgb(3 8 16 / .05) 86%,
- rgb(3 8 16 / 0) 100%),
- var(--vw-motiv, url("/assets/img/cryo-wappen.webp"))
- var(--vw-blick, center top) / 190% auto no-repeat;
-
- opacity: .82;
-
- /* Unten ausblenden, damit es keine sichtbare Kante gibt, an der das
- Bild einfach aufhoert. */
- -webkit-mask-image:
- linear-gradient(180deg, #000 0%, #000 55%, rgb(0 0 0 / .45) 82%, transparent 100%);
- mask-image:
- linear-gradient(180deg, #000 0%, #000 55%, rgb(0 0 0 / .45) 82%, transparent 100%);
+ background: var(--vw-motiv, url("/assets/img/cryo-wappen.webp"))
+ center center / cover no-repeat;
+ /* Kein Zoom: "cover" zeigt bei einem 16:9-Motiv auf einem
+ ueblichen Bildschirm praktisch das ganze Bild. */
}
- /* Die Kristallkante als eigene Ebene: ein sehr schmaler Leuchtsaum in
- der Leitfarbe, der den Streifen nach unten abschliesst. */
- .vw-schmuck::after {
+ /* Eine ruhige Toenung darueber. Sie nimmt dem Bild nicht die Zeichnung,
+ sondern legt nur so viel Tiefe darunter, dass die Glasflaechen sich
+ abheben. Oben etwas dichter, weil dort die Kopfleiste sitzt. */
+ .vw-buehne::after {
content: "";
position: absolute;
- left: 0; right: 0; bottom: 0;
- height: 2px;
- background: linear-gradient(90deg,
- transparent 0%,
- rgb(var(--vw-farbe) / .55) 22%,
- rgb(var(--vw-farbe) / .18) 50%,
- rgb(var(--vw-farbe) / .55) 78%,
- transparent 100%);
- clip-path: polygon(
- 0% 0%, 6% 100%, 13% 0%, 21% 100%, 29% 0%, 37% 100%, 45% 0%,
- 53% 100%, 61% 0%, 69% 100%, 77% 0%, 85% 100%, 93% 0%, 100% 100%);
+ inset: 0;
+ background:
+ radial-gradient(125% 95% at 50% 8%,
+ rgb(3 8 16 / .18) 0%, rgb(3 8 16 / .38) 52%, rgb(3 8 16 / .60) 100%),
+ linear-gradient(180deg,
+ rgb(3 8 16 / .14) 0%, rgb(6 19 35 / .20) 55%, rgb(3 8 16 / .40) 100%);
}
- /* Beim Bereichswechsel blendet die Signatur kurz aus und wieder ein.
- Ohne das springt das Bild hart um -- mit sieht es aus, als wuerde
- der Raum wechseln. */
- @keyframes vw-signatur-ein {
- from { opacity: 0; transform: scale(1.05); }
- to { opacity: .82; transform: none; }
+ /* Beim Bereichswechsel wechselt der ganze Raum. */
+ @keyframes vw-buehne-ein {
+ from { opacity: 0; transform: scale(1.06); }
+ to { opacity: 1; transform: none; }
+ }
+ .vw-buehne { animation: vw-buehne-ein .7s cubic-bezier(.16,.8,.3,1) both; }
+
+ @media (prefers-reduced-motion: reduce) {
+ .vw-buehne { animation: none; }
}
- .vw-schmuck { animation: vw-signatur-ein .55s ease-out both; }
@media (max-width: 900px) {
- /* Auf dem Handy die kleine Fassung -- ein Bruchteil der Datenmenge
- fuer ein Bild, das ohnehin nur angedeutet ist. */
- /* Auf dem Handy ist links kein Platz, den man freihalten koennte --
- die Reiter laufen ueber die ganze Breite. Deshalb hier eine
- gleichmaessige Deckschicht und deutlich weniger Bild. */
- .vw-schmuck {
+ /* Auf dem Handy die kleine Bildfassung. Und "cover" schneidet bei
+ einem hochkant gehaltenen Telefon zwangslaeufig die Seiten weg --
+ deshalb hier der Bildmittelpunkt, wo bei allen Motiven das
+ Hauptgeschehen sitzt. */
+ .vw-buehne {
+ background-image: var(--vw-motiv-klein, url("/assets/img/cryo-wappen-klein.webp"));
+ background-position: center center;
+ }
+ /* Auf kleinen Schirmen liegt mehr Inhalt uebereinander, also etwas
+ mehr Tiefe unter dem Glas. */
+ .vw-buehne::after {
background:
linear-gradient(180deg,
- rgb(3 8 16 / .82) 0%,
- rgb(3 8 16 / .88) 60%,
- rgb(3 8 16 / .95) 100%),
- var(--vw-motiv-klein, url("/assets/img/cryo-wappen-klein.webp"))
- var(--vw-blick, center top) / 190% auto no-repeat;
- opacity: .62;
- height: clamp(200px, 30vh, 300px);
- }
- @keyframes vw-signatur-ein {
- from { opacity: 0; transform: scale(1.04); }
- to { opacity: .62; transform: none; }
+ rgb(3 8 16 / .52) 0%, rgb(6 19 35 / .58) 50%, rgb(3 8 16 / .72) 100%);
}
}
- /* Wer Bewegung reduziert haben will, bekommt den Wechsel ohne
- Animation -- das Motiv ist trotzdem da. */
- @media (prefers-reduced-motion: reduce) {
- .vw-schmuck { animation: none; }
+ /* ---------- GLAS ----------
+
+ Alles, worauf gelesen wird, bekommt eine Rueckseiten-Unschaerfe.
+ Das Bild bleibt sichtbar, verliert hinter dem Glas aber jede
+ Struktur -- und genau das macht Text darauf ruhig lesbar.
+
+ Wo der Browser backdrop-filter nicht kann, traegt die Farbe allein:
+ Deshalb ist der Hintergrund auch ohne Unschaerfe schon dicht genug.
+ Das ist kein Schoenheitsfehler, sondern der Rueckfall. */
+ #vw-bereich .wd-karte,
+ #vw-bereich .vw-liste,
+ #vw-bereich .wd-notiz,
+ #vw-bereich .vw-kachel,
+ #vw-bereich table {
+ -webkit-backdrop-filter: blur(22px) saturate(1.15);
+ backdrop-filter: blur(22px) saturate(1.15);
+ }
+
+ /* Die Reiter und die Bedienknoepfe schweben ebenfalls. */
+ #vw-bereich .vw-filter-knopf,
+ #vw-bereich .vw-wer {
+ -webkit-backdrop-filter: blur(16px) saturate(1.1);
+ backdrop-filter: blur(16px) saturate(1.1);
+ }
+
+ /* Meldungen wie "Wird geladen" oder "Nichts vorhanden" stehen frei
+ zwischen den Kacheln -- also direkt auf der Buehne. Sie bekommen
+ deshalb ihre eigene kleine Glasflaeche statt nur Text zu sein.
+ Sonst haengt ausgerechnet die Zeile, die etwas erklaeren soll,
+ ungeschuetzt vor einer Kristallwand. */
+ #vw-bereich .vw-laedt,
+ #vw-bereich .vw-leer {
+ display: inline-block;
+ padding: .55rem 1.1rem;
+ border-radius: 999px;
+ background: rgb(6 19 35 / .72);
+ border: 1px solid rgb(var(--vw-farbe) / .22);
+ -webkit-backdrop-filter: blur(16px);
+ backdrop-filter: blur(16px);
+ }
+
+ /* Die Glaskacheln bekommen eine Leuchtkante in der Leitfarbe. Das
+ bindet Inhalt und Buehne zusammen, statt die Kacheln wie
+ aufgeklebte Zettel wirken zu lassen. */
+ #vw-bereich .wd-karte {
+ border-color: rgb(var(--vw-farbe) / .30);
+ box-shadow:
+ 0 24px 60px -30px rgb(0 0 0 / .85),
+ inset 0 1px 0 rgb(var(--vw-farbe) / .16);
+ }
+
+ /* Der Titel steht als einziges frei auf der Buehne. Damit er das
+ aushaelt, bekommt er einen weichen dunklen Hof -- kein Kasten, der
+ die Buehne zerschneiden wuerde. */
+ #vw-bereich .vw-leiste h1 {
+ text-shadow:
+ 0 2px 18px rgb(3 8 16 / .95),
+ 0 1px 4px rgb(3 8 16 / .9);
}
/* Suchen und Abmelden sitzen oben rechts -- also genau dort, wo das
@@ -1397,7 +1403,7 @@
-
+
@@ -3320,7 +3326,7 @@
if (!ANSICHTEN[bereich]) return; // unbekannt: lieber nichts tun
document.documentElement.setAttribute("data-vw-bereich", bereich);
- var schmuck = document.querySelector(".vw-schmuck");
+ var schmuck = document.querySelector(".vw-buehne");
if (!schmuck) return;
/* Wer Bewegung reduziert haben will, bekommt keinen Neustart -- die
Animation ist fuer ihn ohnehin abgeschaltet. */