Begruessung, Schriftzug, hellerer Hintergrund - und "Wissen" fuer Creator
Vier Wuensche auf einmal, alle auf der Startseite und der Kopfleiste. BEGRUESSUNG. Dort stand "Angemeldet" ueber dem Namen -- eine Zeile, die nichts hinzufuegt, weil oben rechts ohnehin Name und Rolle stehen. Jetzt beantwortet der Kopf die drei Fragen, die man beim Reinkommen hat: WANN (Wochentag, Datum, Kalenderwoche nach ISO 8601, selbst gerechnet), WER (Plakette mit dem Anfangsbuchstaben in der Farbe der Rolle -- der einzige runde Koerper der Seite, dadurch sofort als Person lesbar) und WIE es steht (ein Satz aus denselben Hinweisen, die darunter stehen -- keine zweite Zaehlung). An Tagen mit Charakter kommt ein Wort dazu: Neue Woche, Endspurt, Wochenende. Der Gruss ist feiner gestuft und der Name eigenstaendig ausgezeichnet: das Grusswort leise, der Name kraeftig. SCHRIFTZUG oben links. Vorher alles gleich laut, der Trenner ein Satzzeichen wie jedes andere. Jetzt hat er Rang: der Name vorn und kraeftig, der Ort dahinter und leiser, dazwischen eine kleine Raute in der Hausfarbe. Aufgeteilt zentral in kopf.js statt in sechzehn HTML- Dateien, und nur die Textknoten -- Verweise bleiben unberuehrt. HINTERGRUND heller, wie gewuenscht: Der Schleier liegt bei .86/.58/.44 statt .95/.72/.60, dazu zwei sehr weiche Farbschimmer, die dem Bild das reine Schwarz nehmen. Der Kontrast wurde danach an echten Bildpunkten nachgemessen und haelt ueberall (schlechtester Wert 4,78:1). "WISSEN" statt "Team & System" -- aber nur fuer Creator. Von der Gruppe bleibt fuer ihn genau eine Kachel uebrig; eine Ueberschrift, die Team und System verspricht und nur die Bibliothek zeigt, verspricht etwas Falsches. DREI FUNDE DURCH DIE PRUEFUNGEN: 1. Die Kontrastmessung war kaputt, seit Text neben Text steht. Sie tastete stur 4 px rechts vom Text ab -- und traf dort den NACHBARTEXT statt den Untergrund (1,10:1 gemeldet, ohne dass etwas schlecht lesbar war). Jetzt werden sechs Stellen rings um den Text angeboten und jede zuerst gefragt, ob dort wirklich Untergrund liegt. Strenger, nicht lockerer. Texte ohne freie Stelle werden GEZAEHLT und ausgegeben, nicht still uebersprungen. 2. bereich.html hatte als einzige Seite kein span.marke__text. Damit griffen dort weder die Ueberlauf-Kuerzung noch der neue Rang. Stand seit Monaten so, aufgefallen erst, als die Seitenpruefung die Marke auf allen dreizehn Seiten nachgesehen hat. 3. Die Datumspruefung haette im Maerz stillschweigend versagt: \w kennt ohne Unicode-Schalter kein "ae". Ein Fehler, der sieben Monate wartet. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
+48
-6
@@ -114,17 +114,52 @@ for (const [breite, hoehe, wie] of [[1440, 950, "Computer"], [390, 780, "Handy"]
|
||||
g.drawImage(bild, 0, 0);
|
||||
const skala = bild.width / window.innerWidth;
|
||||
|
||||
let schlecht = null, anzahl = 0;
|
||||
/* Wo darf man den Untergrund abtasten?
|
||||
|
||||
Vorher wurde stur 4 px rechts neben dem Text gemessen. Das ging
|
||||
gut, solange dort nichts stand -- sobald aber zwei Textstücke
|
||||
nebeneinanderliegen (Grußwort und Name, Marke und Ortsangabe),
|
||||
traf der Punkt DEN NACHBARTEXT und die Prüfung verglich Schrift
|
||||
mit Schrift. Ergebnis: 1,1:1 und ein Alarm, der nichts mit der
|
||||
Lesbarkeit zu tun hatte.
|
||||
|
||||
Jetzt werden mehrere Stellen rings um den Text angeboten und
|
||||
jede zuerst gefragt: Liegt dort wirklich Untergrund? Ein Punkt
|
||||
gilt nur, wenn an seiner Stelle KEIN fremdes Textelement liegt.
|
||||
Das macht die Prüfung strenger, nicht lockerer -- sie misst
|
||||
jetzt tatsächlich das, was sie behauptet zu messen. */
|
||||
const istFremderText = (el2, e) =>
|
||||
el2 && el2 !== e && !el2.contains(e) && !e.contains(el2)
|
||||
&& el2.children.length === 0 && el2.textContent.trim().length > 0;
|
||||
|
||||
let schlecht = null, anzahl = 0, ohneStelle = 0;
|
||||
const alle = [...document.querySelectorAll("p, h1, h2, h3, span, a, li")]
|
||||
.filter((e) => e.children.length === 0 && e.textContent.trim().length > 2);
|
||||
for (const e of alle) {
|
||||
const r = e.getBoundingClientRect();
|
||||
if (r.width < 8 || r.height < 8) continue;
|
||||
if (r.top < 0 || r.bottom > window.innerHeight) continue;
|
||||
const x = Math.round((r.left + r.width + 4) * skala);
|
||||
const y = Math.round((r.top + r.height / 2) * skala);
|
||||
if (x < 0 || x >= c.width || y < 0 || y >= c.height) continue;
|
||||
const p = g.getImageData(x, y, 1, 1).data;
|
||||
|
||||
const mitte = r.top + r.height / 2;
|
||||
const kandidaten = [
|
||||
[r.right + 4, mitte], [r.right + 14, mitte],
|
||||
[r.left - 4, mitte], [r.left - 14, mitte],
|
||||
[r.left + r.width / 2, r.top - 3], [r.left + r.width / 2, r.bottom + 3],
|
||||
];
|
||||
let p = null;
|
||||
for (const [kx, ky] of kandidaten) {
|
||||
if (kx < 0 || ky < 0 || kx >= window.innerWidth || ky >= window.innerHeight) continue;
|
||||
if (istFremderText(document.elementFromPoint(kx, ky), e)) continue;
|
||||
const bx = Math.round(kx * skala), by = Math.round(ky * skala);
|
||||
if (bx < 0 || bx >= c.width || by < 0 || by >= c.height) continue;
|
||||
p = g.getImageData(bx, by, 1, 1).data;
|
||||
break;
|
||||
}
|
||||
/* Kein freier Punkt gefunden -- das wird GEZÄHLT und ausgegeben.
|
||||
Ein stillschweigend übersprungener Text wäre eine Prüfung, die
|
||||
behauptet, etwas geprüft zu haben. */
|
||||
if (!p) { ohneStelle++; continue; }
|
||||
|
||||
const st = getComputedStyle(e);
|
||||
const farbe = st.color.match(/\d+/g).slice(0, 3).map(Number);
|
||||
const gr = parseFloat(st.fontSize);
|
||||
@@ -136,13 +171,20 @@ for (const [breite, hoehe, wie] of [[1440, 950, "Computer"], [390, 780, "Handy"]
|
||||
schlecht = { v, grenze, text: e.textContent.trim().slice(0, 26), grund: [p[0], p[1], p[2]] };
|
||||
}
|
||||
}
|
||||
return { ...schlecht, anzahl };
|
||||
return { ...schlecht, anzahl, ohneStelle, gesamt: alle.length };
|
||||
}, bild.toString("base64"));
|
||||
|
||||
ok(schlimmster.v >= schlimmster.grenze,
|
||||
`${s}: schlechtester Kontrast ${schlimmster.v.toFixed(2)}:1 (nötig ${schlimmster.grenze}:1) `
|
||||
+ `bei "${schlimmster.text}" auf rgb(${schlimmster.grund.join(",")}) · `
|
||||
+ `${schlimmster.anzahl} Stellen gemessen`);
|
||||
/* Wenn rings um einen Text nirgends freier Untergrund liegt, ist er
|
||||
ungeprüft geblieben. Das darf vorkommen (dicht gesetzte Zeilen),
|
||||
aber nicht unbemerkt und nicht bei vielen -- sonst meldet die
|
||||
Prüfung "bestanden" für eine Seite, die sie kaum angesehen hat. */
|
||||
ok(schlimmster.ohneStelle <= 2,
|
||||
`${s}: höchstens zwei Texte ohne freien Untergrund `
|
||||
+ `(${schlimmster.ohneStelle} von ${schlimmster.gesamt})`);
|
||||
}
|
||||
|
||||
if (breite === 1440) {
|
||||
|
||||
@@ -75,6 +75,26 @@ async function anmelden(kontext, rolle, code) {
|
||||
|
||||
const lesen = (seite) => seite.evaluate(() => ({
|
||||
gruss: document.getElementById("gruss")?.textContent,
|
||||
/* Die Begrüßung ist ein Kopf aus vier Teilen geworden: Datum mit
|
||||
Kalenderwoche, ein Wort zum Tag, die Plakette in Rollenfarbe und
|
||||
ein Satz zur Lage. Jeder Teil wird einzeln nachgesehen -- "sieht
|
||||
gut aus" ist kein Nachweis. */
|
||||
datum: document.getElementById("datum")?.textContent || "",
|
||||
grussWort: document.querySelector(".gruss__wort")?.textContent || "",
|
||||
grussName: document.querySelector(".gruss__name")?.textContent || "",
|
||||
plakette: document.getElementById("plakette")?.textContent || "",
|
||||
plaketteRolle: document.getElementById("plakette")?.dataset.rolle || "",
|
||||
plaketteRund: getComputedStyle(document.getElementById("plakette") || document.body).borderRadius,
|
||||
marker: document.getElementById("tagesmarker")?.hidden === false
|
||||
? document.getElementById("tagesmarker").textContent : null,
|
||||
lage: document.getElementById("lage")?.hidden === false
|
||||
? document.getElementById("lage").textContent : null,
|
||||
lageStufe: document.getElementById("lage")?.dataset.stufe || null,
|
||||
/* Der Schriftzug oben links, aufgeteilt in Rang. */
|
||||
markeHaupt: document.querySelector(".kopfleiste .marke__haupt")?.textContent || "",
|
||||
markeLeise: document.querySelector(".kopfleiste .marke__leise")?.textContent || "",
|
||||
markeRauten: document.querySelectorAll(".kopfleiste .marke__raute").length,
|
||||
gruppenNamen: [...document.querySelectorAll(".gruppe__name")].map((g) => g.textContent),
|
||||
gruppen: [...document.querySelectorAll(".gruppe")].map((g) => ({
|
||||
name: g.querySelector(".gruppe__name")?.textContent,
|
||||
kacheln: g.querySelectorAll(".kachel").length,
|
||||
@@ -143,8 +163,61 @@ for (const [breite, wie] of [[1440, "Computer"], [390, "Handy"]]) {
|
||||
|
||||
ok(r.gruppen.length === 3, `drei Gruppen (${r.gruppen.length})`);
|
||||
ok(r.kacheln.length === 17, `siebzehn Kacheln (${r.kacheln.length})`);
|
||||
ok(/^(Guten Morgen|Guten Tag|Guten Abend|Noch wach), Filipe$/.test(r.gruss || ""),
|
||||
`Begrüßung nach Tageszeit ("${r.gruss}")`);
|
||||
ok(/^(Guten Morgen|Guten Mittag|Guten Tag|Guten Abend|Späten Abend|Noch wach), Filipe$/
|
||||
.test(r.gruss || ""), `Begrüßung nach Tageszeit ("${r.gruss}")`);
|
||||
/* Der Name muss eigenständig ausgezeichnet sein -- er ist das Wichtigste
|
||||
an der Zeile und wird kräftiger gesetzt als der Gruß davor. */
|
||||
ok(r.grussName === "Filipe", `der Name steht für sich (${r.grussName})`);
|
||||
ok(r.grussWort.trim().endsWith(","), `und das Grußwort davor (${r.grussWort.trim()})`);
|
||||
|
||||
/* Datumszeile: Wochentag, Datum, Kalenderwoche. Die KW wird selbst
|
||||
gerechnet (ISO 8601) -- eine falsche KW wäre schlimmer als keine. */
|
||||
console.log(" Datum: ", r.datum, r.marker ? `· ${r.marker}` : "");
|
||||
/* Kein \w im Monatsnamen: "März" enthält einen Umlaut, den \w ohne
|
||||
Unicode-Schalter nicht kennt. Diese Prüfung wäre im März stillschweigend
|
||||
rot geworden -- ein Fehler, der sieben Monate lang unsichtbar wartet. */
|
||||
ok(/^(Montag|Dienstag|Mittwoch|Donnerstag|Freitag|Samstag|Sonntag), \d{1,2}\. [A-Za-zÄÖÜäöü]+ · KW \d{1,2}$/
|
||||
.test(r.datum), `Datum mit Kalenderwoche ("${r.datum}")`);
|
||||
const kw = Number((r.datum.match(/KW (\d+)/) || [])[1]);
|
||||
ok(kw >= 1 && kw <= 53, `die Kalenderwoche ist plausibel (${kw})`);
|
||||
/* An Tagen mit Charakter steht ein Wort dabei -- an den übrigen nicht.
|
||||
Beides ist richtig, aber es muss zum Wochentag passen. */
|
||||
const tag = r.datum.split(",")[0];
|
||||
const erwartet = { Montag: "Neue Woche", Freitag: "Endspurt",
|
||||
Samstag: "Wochenende", Sonntag: "Wochenende" }[tag] || null;
|
||||
ok(r.marker === erwartet,
|
||||
`das Wort zum Tag passt zum ${tag} (${r.marker || "keins"}, erwartet ${erwartet || "keins"})`);
|
||||
|
||||
/* Die Plakette: Anfangsbuchstabe, rund, in der Farbe der Rolle. */
|
||||
ok(r.plakette === "F", `die Plakette trägt den Anfangsbuchstaben (${r.plakette})`);
|
||||
ok(r.plaketteRolle === "admin", `und die Rolle für ihre Farbe (${r.plaketteRolle})`);
|
||||
ok(/50%|9999px/.test(r.plaketteRund), `und ist rund (${r.plaketteRund})`);
|
||||
|
||||
/* Der Satz zur Lage kommt aus denselben Hinweisen wie die Liste
|
||||
darunter -- keine zweite Zählung. Hier liegt Überfälliges vor,
|
||||
also muss er das sagen und rot sein. */
|
||||
console.log(" Lage: ", r.lage);
|
||||
ok(r.lageStufe === "warnung", `die Lage meldet Überfälliges (${r.lageStufe})`);
|
||||
ok(/^\d+ Sachen? (ist|sind) überfällig/.test(r.lage || ""), `und sagt es im Klartext ("${r.lage}")`);
|
||||
/* Sie muss zu den Hinweisen passen, sonst stünden zwei Wahrheiten
|
||||
übereinander auf einem Bildschirm. */
|
||||
const dringend = r.dranZeilen.filter((z) => z.stufe === "warnung")
|
||||
.reduce((s, z) => s + Number(z.zahl), 0);
|
||||
ok(Number((r.lage.match(/^\d+/) || [])[0]) === dringend,
|
||||
`und stimmt mit den Hinweisen überein (${r.lage.match(/^\d+/)} = ${dringend})`);
|
||||
|
||||
/* Der Schriftzug oben links hat jetzt einen Rang statt einer Farbe.
|
||||
Die geschützten Leerzeichen aus dem Quelltext bleiben erhalten (der
|
||||
Schriftzug darf nicht umbrechen) -- beim Vergleich werden sie
|
||||
deshalb gleichgesetzt, sonst prüft man Unsichtbares. */
|
||||
const ohneNbsp = (t) => t.replace(/ /g, " ");
|
||||
ok(ohneNbsp(r.markeHaupt) === "Dogfather Universe", `der Name steht vorn ("${r.markeHaupt}")`);
|
||||
ok(ohneNbsp(r.markeLeise) === "Creator Workspace", `der Ort dahinter ("${r.markeLeise}")`);
|
||||
ok(r.markeRauten === 1, `und dazwischen genau eine Raute (${r.markeRauten})`);
|
||||
/* Der geschützte Leerraum muss WIRKLICH noch da sein -- ginge er beim
|
||||
Aufteilen verloren, bräche "Creator Workspace" auf dem Handy um. */
|
||||
ok(r.markeHaupt.includes(" ") && r.markeLeise.includes(" "),
|
||||
"und der Schriftzug kann weiterhin nicht umbrechen");
|
||||
|
||||
/* Jede Kachel muss ein Zeichen haben -- eine ohne waere ein leeres
|
||||
Kaestchen und faellt sofort auf. */
|
||||
@@ -251,6 +324,24 @@ console.log("\n=== Creator");
|
||||
ok(!namen.includes("Automationen"), "keine Automationen");
|
||||
ok(r.gruppen.length === 3, `trotzdem drei Gruppen (${r.gruppen.length})`);
|
||||
ok(r.kacheln.every((k) => k.pfade > 0), "alle Zeichen da");
|
||||
/* Von "Team & System" bleibt für ihn genau eine Kachel übrig. Eine
|
||||
Überschrift, die Team und System verspricht und nur die Bibliothek
|
||||
zeigt, verspricht etwas Falsches -- deshalb heißt sie hier "Wissen". */
|
||||
ok(r.gruppenNamen.includes("Wissen"), `die Gruppe heißt "Wissen" (${r.gruppenNamen.join(" · ")})`);
|
||||
ok(!r.gruppenNamen.includes("Team & System"), "und nicht mehr \"Team & System\"");
|
||||
/* Seine Begrüßung: eigene Rollenfarbe, und da nichts anliegt, der
|
||||
ruhige Satz statt eines erfundenen Alarms. */
|
||||
ok(r.plaketteRolle === "creator", `die Plakette kennt seine Rolle (${r.plaketteRolle})`);
|
||||
/* Tili hat selbst überfällige Aufgaben -- die Lage MUSS das sagen.
|
||||
Entscheidend ist nicht, welche Stufe herauskommt, sondern dass sie
|
||||
zu dem passt, was direkt darunter steht. Zwei verschiedene Zahlen
|
||||
auf einem Bildschirm wären der eigentliche Fehler. */
|
||||
const cDringend = r.dranZeilen.filter((z) => z.stufe === "warnung")
|
||||
.reduce((s, z) => s + Number(z.zahl), 0);
|
||||
ok(cDringend > 0 ? r.lageStufe === "warnung" : r.lageStufe !== "warnung",
|
||||
`die Lage passt zu seinen Hinweisen (${r.lageStufe}: "${r.lage}")`);
|
||||
ok(cDringend === 0 || Number((r.lage.match(/^\d+/) || [])[0]) === cDringend,
|
||||
`und zur Zahl darunter (${cDringend})`);
|
||||
await seite.screenshot({ path: "pruef-start-creator.png", fullPage: true });
|
||||
await kontext.close();
|
||||
}
|
||||
|
||||
@@ -84,13 +84,33 @@ for (const [breite, hoehe, wie] of [[1440, 1000, "Computer"], [390, 844, "Handy"
|
||||
const a = document.querySelector(".abmelden")?.getBoundingClientRect();
|
||||
return a ? a.right <= window.innerWidth && a.left >= 0 : null;
|
||||
})(),
|
||||
/* Der Schriftzug oben links wird von kopf.js in Rang aufgeteilt.
|
||||
Auf den Unterseiten ist der vordere Teil ein VERWEIS zurück zur
|
||||
Startseite -- der muss die Aufteilung überleben, sonst ist der
|
||||
Rückweg weg. Genau das wäre beim Zerlegen von Textknoten leicht
|
||||
zu übersehen. */
|
||||
markeHaupt: document.querySelector(".kopfleiste .marke__haupt")?.textContent
|
||||
.replace(/ /g, " ") || null,
|
||||
markeLeise: document.querySelector(".kopfleiste .marke__leise")?.textContent
|
||||
.replace(/ /g, " ") || null,
|
||||
markeRaute: document.querySelectorAll(".kopfleiste .marke__raute").length,
|
||||
/* Ist der vordere Teil auf einer Unterseite noch ein klickbarer
|
||||
Verweis auf die Startseite? */
|
||||
rueckweg: document.querySelector(".kopfleiste .marke__haupt")?.tagName === "A"
|
||||
? document.querySelector(".kopfleiste .marke__haupt").getAttribute("href") : null,
|
||||
}));
|
||||
const istStart = s === "start.html";
|
||||
const markeGut = r.markeHaupt && r.markeLeise && r.markeRaute === 1
|
||||
&& (istStart || r.rueckweg === "start.html");
|
||||
const gut = r.ueber <= 0 && konsole.length === 0 && r.abmelden !== false
|
||||
&& markeGut
|
||||
&& (r.breit === null || r.breit === "1240px")
|
||||
&& (r.kopfzeile === null || r.kopfzeile === "flex");
|
||||
if (!gut) fehler++;
|
||||
console.log(` ${gut ? "ok " : "FEHL"} ${String(s).padEnd(20)}`
|
||||
+ `über=${String(r.ueber).padStart(3)}px abmelden=${r.abmelden}`
|
||||
+ ` marke=${r.markeHaupt ? `"${r.markeHaupt}"◆"${r.markeLeise}"` : "FEHLT"}`
|
||||
+ `${istStart ? "" : ` zurück=${r.rueckweg || "FEHLT"}`}`
|
||||
+ ` breit=${r.breit} kopfzeile=${r.kopfzeile}`
|
||||
+ (konsole.length ? " KONSOLE: " + konsole.join(" | ") : "")
|
||||
+ (r.schuld.length ? " -> " + r.schuld.join(" | ") : ""));
|
||||
|
||||
Reference in New Issue
Block a user