Files
dogfather-universe/server/pruefe-webdesign-handy.mjs
T
DogFatherGitandClaude Opus 5 9f74167f84 Sprungmarken brechen um statt zu scrollen, keine Silbentrennung in Ueberschriften
Rueckmeldung 22.08.2026: "ich will nicht dass man die hin und her
schieben muss man soll die alle sehen aber nicht schieben muessen."

Richtig, und aus zwei Gruenden: Eine Wischleiste verbirgt, DASS es noch
mehr gibt -- was rechts aus dem Bild ragt, existiert fuer die meisten
Menschen schlicht nicht. Dazu kam ein haesslicher Scrollbalken quer ueber
die Seite. Jetzt brechen die Knoepfe um, alle fuenf sind auf einen Blick
da, auch bei 320px.

Der Text in den Knoepfen darf dabei mitbrechen (white-space: normal) --
ohne das sprengt ein langer Name wie "Buchhaltungs- &
Steuerverwaltungsseiten" auf schmalen Bildschirmen die Zeile und der
waagerechte Ueberlauf waere durch die Hintertuer zurueck.

Dabei mitgefunden: "hyphens: auto" auf Ueberschriften. Auf dem Handy
stand dadurch "Alles, was vorher ge-klaert sein sollte". Der Browser
trennt damit nach Silben, auch wenn ueberhaupt kein Platzproblem
besteht. In Fliesstext ist das ein Gewinn, in grossen Ueberschriften
sieht es billig aus -- und genau die sind das Erste, was jemand sieht.
overflow-wrap: break-word bleibt und faengt echte Ueberlaeufe weiterhin ab.

Pruefskript: die Sprungmarken-Leisten waren als "absichtlich scrollbar"
von der Ueberlaufpruefung ausgenommen. Diese Ausnahme ist raus -- sonst
wuerde ein zurueckkehrender Ueberlauf dort nie auffallen. 45/45 weiterhin
sauber.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-22 19:03:30 +02:00

240 lines
10 KiB
JavaScript

/* =====================================================================
pruefe-webdesign-handy.mjs — Handy-Abnahme für alle Webdesign-Seiten.
Prüft je Seite und je Bildschirmbreite (320 / 390 / 430 / 768 / 1440):
- kein waagerechter Überlauf (der häufigste Handy-Fehler überhaupt)
- kein Element ragt über den rechten Rand hinaus
- alle Knöpfe und Links sind mindestens 44px hoch (Touch-Ziel)
- keine Konsolenfehler
- in allen fünf Sprachen: keine unübersetzten Schlüssel sichtbar
320px ist bewusst dabei: das ist ein iPhone SE der ersten Generationen
und immer noch die Breite, bei der Layouts zuerst brechen.
Aufruf (lokaler Testserver muss laufen):
node pruefe-webdesign-handy.mjs
===================================================================== */
import { chromium } from "playwright";
const BASIS = process.env.WD_BASIS || "http://127.0.0.1:4199";
const CODE = process.env.WD_CODE || "DOGI-TEST-1";
const SEITEN = [
"/webdesign/",
"/webdesign/leistungen.html",
"/webdesign/preise.html",
"/webdesign/portfolio.html",
"/webdesign/ablauf.html",
"/webdesign/ueber.html",
"/webdesign/faq.html",
"/webdesign/anfrage.html",
];
const BREITEN = [320, 390, 430, 768, 1440];
const SPRACHEN = ["de", "de-CH", "en", "fr", "pt"];
let ok = 0;
const probleme = [];
const browser = await chromium.launch();
const kontext = await browser.newContext();
const seite = await kontext.newPage();
const konsole = [];
seite.on("console", (m) => { if (m.type() === "error") konsole.push(m.text()); });
seite.on("pageerror", (e) => konsole.push("pageerror: " + e.message));
// --- Anmelden ---
await seite.goto(BASIS + "/webdesign/zugang.html", { waitUntil: "networkidle" });
await seite.fill("#zg-code-dogfather", CODE);
await Promise.all([
seite.waitForURL((u) => !u.pathname.endsWith("zugang.html"), { timeout: 15000 }),
seite.click("#zg-form-dogfather button[type=submit]"),
]);
console.log("\n=== Handy-Abnahme ===\n");
for (const pfad of SEITEN) {
console.log(pfad);
for (const breite of BREITEN) {
await seite.setViewportSize({ width: breite, height: 900 });
await seite.goto(BASIS + pfad, { waitUntil: "networkidle" });
// Marken-Intro wegklicken, es liegt sonst über allem.
const skip = seite.locator("#wd-intro-skip");
if (await skip.isVisible().catch(() => false)) await skip.click();
await seite.waitForTimeout(250);
const befund = await seite.evaluate((vp) => {
const raus = [];
// Waagerechter Überlauf des ganzen Dokuments
const ueberlauf = document.documentElement.scrollWidth > vp + 1;
// Einzelne Elemente, die über den rechten Rand ragen
for (const el of document.querySelectorAll("body *")) {
const s = getComputedStyle(el);
if (s.display === "none" || s.visibility === "hidden") continue;
/* Nur noch die Tabellen sind absichtlich waagerecht scrollbar.
Die Sprungmarken-Leisten (.ls-sprung, .fq-sprung) waren es
frueher auch und standen deshalb hier — seit sie umbrechen
(Rueckmeldung 22.08.2026) muessen sie mitgeprueft werden,
sonst faellt ein zurueckkehrender Ueberlauf nie auf. */
if (el.closest(".wd-tabelle-rahmen")) continue;
const r = el.getBoundingClientRect();
if (r.width === 0 || r.height === 0) continue;
if (r.right > vp + 1.5) {
raus.push((el.tagName.toLowerCase() + "." + (el.className || "").toString().split(" ")[0])
.slice(0, 50) + " bis " + Math.round(r.right) + "px");
}
}
// Zu kleine Touch-Ziele
const klein = [];
for (const el of document.querySelectorAll("a, button, summary, input, select")) {
const s = getComputedStyle(el);
if (s.display === "none" || s.visibility === "hidden") continue;
const r = el.getBoundingClientRect();
if (r.height === 0 || r.width === 0) continue;
/* Absichtlich unsichtbare Eingabefelder ueberspringen.
Bei Auswahlkacheln (.wd-wahl-kachel) liegt das echte Ankreuzfeld
unsichtbar hinter der Kachel -- angetippt wird die KACHEL, und
die ist gross genug. Ebenso der Honigtopf im Anfrageformular,
der fuer Menschen gar nicht existieren soll.
Ohne diese Ausnahme meldet die Pruefung fuenf Fehler, die keine
sind -- und Fehlalarme sind das Ende jeder Pruefung, weil man
sie irgendwann pauschal ignoriert. */
if (el.tagName === "INPUT") {
if (parseFloat(s.opacity) === 0) continue; // unsichtbar hinter der Kachel
if (r.right < 0 || r.left > vp) continue; // aus dem Bild geschoben
const label = el.closest("label");
if (label && label.getBoundingClientRect().height >= 44) continue; // Label ist das Ziel
}
if (r.height < 44) {
klein.push((el.tagName.toLowerCase() + " „" + (el.textContent || "").trim().slice(0, 24) + "“ ")
+ Math.round(r.height) + "px");
}
}
// Unübersetzte Schlüssel: erscheinen als roher Schlüsselname im Text
const roh = [];
for (const el of document.querySelectorAll("[data-i18n]")) {
const txt = (el.textContent || "").trim();
if (txt === el.getAttribute("data-i18n")) roh.push(txt);
}
/* Verzerrte Bilder.
Am 22.08.2026 gemeldet: zwei Projektbilder standen 576px breit,
aber 750px bzw. 696px hoch da -- hochkant gestreckt und ungleich,
obwohl im CSS 16:10 stand. Ursache waren die height-Attribute im
HTML, die wie eine CSS-Hoehe wirken und aspect-ratio schlagen.
Diese Pruefung vergleicht das im CSS gewuenschte Verhaeltnis mit
dem tatsaechlich gerenderten -- damit faellt so etwas sofort auf
und nicht erst per Bildschirmfoto. */
const verzerrt = [];
for (const bild of document.querySelectorAll("img")) {
const s = getComputedStyle(bild);
if (s.display === "none") continue;
const r = bild.getBoundingClientRect();
if (r.width < 2 || r.height < 2) continue;
const gewuenscht = s.aspectRatio;
if (!gewuenscht || gewuenscht === "auto") continue;
const teile = String(gewuenscht).split("/").map((t) => parseFloat(t));
if (teile.length !== 2 || !teile[0] || !teile[1]) continue;
const soll = teile[0] / teile[1];
const ist = r.width / r.height;
// 2 % Toleranz für Rundung auf ganze Pixel
if (Math.abs(soll - ist) / soll > 0.02) {
verzerrt.push(
(bild.className || bild.src.split("/").pop()).toString().slice(0, 40) +
` soll ${soll.toFixed(2)}, ist ${ist.toFixed(2)} (${Math.round(r.width)}x${Math.round(r.height)})`
);
}
}
/* Gleich große Karten in einer Reihe. "die sollen alle gleich
aussehen und nicht einer größer oder kleiner" -- ungleiche Höhen
in EINER Zeile sind der haeufigste Grund, warum ein Raster
unaufgeraeumt wirkt. */
const ungleich = [];
for (const raster of document.querySelectorAll(".wd-grid")) {
const kinder = [...raster.children].filter((k) => k.getBoundingClientRect().height > 0);
if (kinder.length < 2) continue;
// Nur Elemente vergleichen, die tatsächlich nebeneinander stehen
const reihen = new Map();
for (const k of kinder) {
const oben = Math.round(k.getBoundingClientRect().top);
const schluessel = Math.round(oben / 8) * 8;
if (!reihen.has(schluessel)) reihen.set(schluessel, []);
reihen.get(schluessel).push(Math.round(k.getBoundingClientRect().height));
}
for (const [, hoehen] of reihen) {
if (hoehen.length < 2) continue;
if (Math.max(...hoehen) - Math.min(...hoehen) > 2) {
ungleich.push(`${raster.className.slice(0, 30)}: ${hoehen.join(", ")}px`);
}
}
}
return {
ueberlauf,
raus: raus.slice(0, 5),
klein: klein.slice(0, 5),
roh: roh.slice(0, 5),
verzerrt: verzerrt.slice(0, 5),
ungleich: ungleich.slice(0, 5),
};
}, breite);
const zeile = ` ${String(breite).padStart(4)}px`;
const fehler = [];
if (befund.ueberlauf) fehler.push("waagerechter Ueberlauf");
if (befund.raus.length) fehler.push("ragt heraus: " + befund.raus.join(", "));
if (befund.klein.length) fehler.push("Touch-Ziel < 44px: " + befund.klein.join(", "));
if (befund.roh.length) fehler.push("unuebersetzt: " + befund.roh.join(", "));
if (befund.verzerrt.length) fehler.push("Bild verzerrt: " + befund.verzerrt.join(" | "));
if (befund.ungleich.length) fehler.push("ungleich hohe Karten: " + befund.ungleich.join(" | "));
if (fehler.length) {
probleme.push(`${pfad} @ ${breite}px -> ${fehler.join(" | ")}`);
console.log(`${zeile} FEHLER: ${fehler.join(" | ")}`);
} else {
ok++;
console.log(`${zeile} ok`);
}
}
}
// --- Sprachen: einmal je Sprache die Startseite prüfen ---
console.log("\nSprachen (Startseite, 390px)");
await seite.setViewportSize({ width: 390, height: 900 });
for (const sprache of SPRACHEN) {
await seite.goto(BASIS + "/webdesign/", { waitUntil: "networkidle" });
const skip = seite.locator("#wd-intro-skip");
if (await skip.isVisible().catch(() => false)) await skip.click();
await seite.evaluate((s) => window.WD.setSprache(s), sprache);
await seite.waitForTimeout(350);
const befund = await seite.evaluate((vp) => {
const roh = [];
for (const el of document.querySelectorAll("[data-i18n]")) {
const txt = (el.textContent || "").trim();
if (txt === el.getAttribute("data-i18n")) roh.push(txt);
}
return { roh: roh.slice(0, 5), ueberlauf: document.documentElement.scrollWidth > vp + 1, lang: document.documentElement.lang };
}, 390);
const fehler = [];
if (befund.lang !== sprache) fehler.push("lang-Attribut ist " + befund.lang);
if (befund.roh.length) fehler.push("unuebersetzt: " + befund.roh.join(", "));
if (befund.ueberlauf) fehler.push("waagerechter Ueberlauf");
if (fehler.length) { probleme.push(`Sprache ${sprache} -> ${fehler.join(" | ")}`); console.log(` ${sprache.padEnd(6)} FEHLER: ${fehler.join(" | ")}`); }
else { ok++; console.log(` ${sprache.padEnd(6)} ok`); }
}
await browser.close();
console.log(konsole.length ? "\nKONSOLENFEHLER:\n" + [...new Set(konsole)].join("\n") : "\nKeine Konsolenfehler.");
console.log(`\n=== ${ok} ok, ${probleme.length} Probleme ===`);
if (probleme.length) probleme.forEach((p) => console.log(" " + p));
process.exit(probleme.length === 0 && konsole.length === 0 ? 0 : 1);