/* ===================================================================== Prueft, dass Kacheln und Bilder auf ALLEN Seiten auf den Zeiger reagieren -- nicht nur im Verwaltungsbereich. Warum als eigener Durchlauf: Die Effekte liegen im Grundsystem (webdesign.css und wd-core.js) und gelten damit fuer jede Seite. Eine Pruefung, die nur eine Seite ansieht, wuerde nicht auffallen lassen, wenn eine andere Seite den Effekt still ueberschreibt. Genau das ist passiert: ".wd-bereit .wd-auf.wd-sichtbar" setzt "transform: none" und hat mit hoeherer Spezifitaet die Neigung geloescht -- sichtbar nur auf Seiten, deren Kacheln eine Einblendung tragen. ===================================================================== */ import { chromium } from "playwright"; import http from "http"; import fs from "fs"; import path from "path"; const TYP = { ".html": "text/html", ".css": "text/css", ".js": "text/javascript", ".png": "image/png", ".svg": "image/svg+xml", ".webp": "image/webp", ".json": "application/json", ".ico": "image/x-icon", ".jpg": "image/jpeg", }; const srv = http.createServer((q, r) => { const p = path.join(process.cwd(), decodeURIComponent(q.url.split("?")[0])); if (fs.existsSync(p) && !p.endsWith("/")) { r.writeHead(200, { "content-type": TYP[path.extname(p)] || "text/plain" }); return r.end(fs.readFileSync(p)); } r.writeHead(404); r.end(""); }); let ok = 0, fehl = 0; const pruefe = (name, gut, info) => { if (gut) { ok++; console.log(" ok " + name + (info ? " -> " + info : "")); } else { fehl++; console.log(" FEHL " + name + (info ? " -> " + info : "")); } }; const SEITEN = ["index", "leistungen", "ablauf", "portfolio", "ueber", "portal"]; srv.listen(4405, async () => { const browser = await chromium.launch(); const gemessen = []; console.log("1. NEIGEN SICH DIE KACHELN AUF JEDER SEITE?"); for (const name of SEITEN) { const seite = await browser.newPage({ viewport: { width: 1440, height: 900 } }); const skriptfehler = []; seite.on("pageerror", (e) => skriptfehler.push(String(e).slice(0, 110))); await seite.goto(`http://127.0.0.1:4405/webdesign/${name}.html`); /* Die Startseite legt beim Laden einen Vorhang ueber alles. Wer zu frueh misst, trifft den Vorhang statt der Kachel. */ await seite.waitForTimeout(4500); const karte = seite.locator(".wd-karte").first(); if (await karte.count() === 0) { pruefe(`${name}: Kachel vorhanden`, false, "keine .wd-karte"); await seite.close(); continue; } await karte.scrollIntoViewIfNeeded(); await seite.waitForTimeout(700); /* Einen Punkt suchen, der WIRKLICH auf der Kachel liegt und im Fenster sichtbar ist. Eine Portfolio-Kachel ist ueber 1400 Punkte hoch -- ein fester Anteil ihrer Hoehe landet ausserhalb des Bildschirms, und dann misst der Test das Nichts. */ const ziel = await seite.evaluate(() => { const k = document.querySelector(".wd-karte"); const r = k.getBoundingClientRect(); const x = Math.round(r.left + Math.min(r.width * 0.25, 200)); const y = Math.round(Math.max(80, Math.min(r.top + 60, window.innerHeight - 80))); const e = document.elementFromPoint(x, y); return { x, y, trifft: !!(e && e.closest(".wd-karte")) }; }); if (!ziel.trifft) { pruefe(`${name}: Messpunkt liegt auf der Kachel`, false, JSON.stringify(ziel)); await seite.close(); continue; } /* Mit steps bewegen, sonst entsteht kein pointermove. */ await seite.mouse.move(ziel.x + 80, ziel.y + 30, { steps: 8 }); await seite.mouse.move(ziel.x, ziel.y, { steps: 14 }); await seite.waitForTimeout(600); const d = await karte.evaluate((e) => { const r = e.getBoundingClientRect(); return { nx: parseFloat(e.style.getPropertyValue("--wd-nx")), matrix: getComputedStyle(e).transform, lumen: getComputedStyle(e).getPropertyValue("--wd-lumen").trim(), kipp: parseFloat(e.style.getPropertyValue("--wd-kipp")), laengsteSeite: Math.round(Math.max(r.width, r.height)), }; }); pruefe(`${name}: die Kachel neigt sich`, d.matrix.startsWith("matrix3d") && !Number.isNaN(d.nx), `nx ${d.nx}`); /* Klein halten: Alles ueber neun Grad verzerrt die Schrift. */ if (d.matrix.startsWith("matrix3d")) { const zahlen = d.matrix.slice(d.matrix.indexOf("(") + 1, -1).split(",").map(Number); const winkel = Math.abs(Math.asin(Math.max(-1, Math.min(1, zahlen[2]))) * 180 / Math.PI); pruefe(`${name}: und bleibt flach genug`, winkel <= 9, winkel.toFixed(1) + " Grad"); gemessen.push({ seite: name, flaeche: d.laengsteSeite, faktor: d.kipp, winkel }); } /* Beim Verlassen zuruecksetzen. */ await seite.mouse.move(5, 5, { steps: 12 }); await seite.waitForTimeout(600); const zurueck = await karte.evaluate((e) => e.style.getPropertyValue("--wd-nx")); pruefe(`${name}: stellt sich beim Verlassen zurueck`, zurueck === "0", zurueck); pruefe(`${name}: kein Skriptfehler`, skriptfehler.length === 0, skriptfehler[0] || ""); await seite.close(); } console.log("\n1b. GROSSE KACHELN KIPPEN WENIGER ALS KLEINE"); for (const g of gemessen) { console.log(" " + g.seite.padEnd(11) + " laengste Seite " + String(g.flaeche).padStart(4) + "px Faktor " + g.faktor + " " + g.winkel.toFixed(2) + " Grad"); } /* Bei gleichem Winkel legt eine grosse Kachel an ihren Ecken viel mehr Weg zurueck als eine kleine. Eine Portfolio-Kachel ist ueber 1400 Punkte hoch -- dort sah derselbe Winkel aus wie das Kippen des halben Bildschirms. Deshalb skaliert der Faktor mit der Groesse. */ const gross = gemessen.filter((g) => g.flaeche > 900); const klein = gemessen.filter((g) => g.flaeche < 500); if (gross.length && klein.length) { const groessterGross = Math.max(...gross.map((g) => g.winkel)); const kleinsterKlein = Math.min(...klein.map((g) => g.winkel)); pruefe("die grosse Kachel kippt weniger als die kleine", groessterGross < kleinsterKlein, groessterGross.toFixed(2) + " Grad gegen " + kleinsterKlein.toFixed(2) + " Grad"); } else { pruefe("grosse und kleine Kacheln zum Vergleichen gefunden", false, gross.length + " grosse, " + klein.length + " kleine"); } /* Nach unten begrenzt, damit der Effekt bei sehr grossen Kacheln nicht einfach ausfaellt. */ pruefe("aber auch die groesste reagiert noch sichtbar", gemessen.every((g) => g.faktor >= 1.4), "kleinster Faktor " + Math.min(...gemessen.map((g) => g.faktor))); console.log("\n2. DIE EINBLENDUNG DARF DIE NEIGUNG NICHT LOESCHEN"); const s2 = await browser.newPage({ viewport: { width: 1440, height: 900 } }); await s2.goto("http://127.0.0.1:4405/webdesign/leistungen.html"); await s2.waitForTimeout(2500); const einblendung = await s2.evaluate(() => { const k = document.querySelector(".wd-auf.wd-sichtbar") || document.querySelector(".wd-auf"); if (!k) return null; const st = getComputedStyle(k); return { translate: st.translate, transform: st.transform }; }); /* Der Kern: Die Einblendung muss ueber "translate" laufen. Nutzt sie "transform", loescht ihr hoeher gewichtetes "transform: none" die Neigung -- und zwar still, ohne Fehlermeldung. */ pruefe("die Einblendung nutzt translate, nicht transform", einblendung && einblendung.translate !== undefined, einblendung ? `translate: ${einblendung.translate}` : "kein .wd-auf gefunden"); await s2.close(); console.log("\n2b. DIE FARBIGE OBERKANTE DARF NICHT VERSCHWINDEN"); const s3 = await browser.newPage({ viewport: { width: 1440, height: 900 } }); await s3.goto("http://127.0.0.1:4405/webdesign/index.html"); await s3.waitForTimeout(4500); const kappen = await s3.evaluate(() => [...document.querySelectorAll(".wd-karte--kappe")].slice(0, 8).map((k) => { const v = getComputedStyle(k, "::before"); return { lila: k.classList.contains("wd-karte--lila"), hoehe: parseFloat(v.height), oben: parseFloat(v.top), farbig: /gradient/.test(v.backgroundImage), }; })); pruefe("es gibt Kacheln mit Oberkante", kappen.length > 0, kappen.length + " Stueck"); /* Das ist der Kern: Ein Element hat nur ZWEI Pseudoelemente. Der Glanzstreifen lag zuerst ebenfalls im ::before und hat die farbige Oberkante stillschweigend geloescht -- sichtbar als "bei einer Kachel Farbe oben, bei den anderen keine". Die Lila-Fassung hat eine Klasse mehr und damit mehr Gewicht, ihre Farbe blieb; die blaue verschwand. Deshalb wird hier ausdruecklich BEIDES geprueft. */ pruefe("jede davon hat eine 5 Punkte hohe Kante ganz oben", kappen.every((k) => k.hoehe === 5 && k.oben === 0), kappen.map((k) => k.hoehe + "px").join(" ")); pruefe("und jede traegt Farbe", kappen.every((k) => k.farbig), kappen.filter((k) => k.farbig).length + " von " + kappen.length); pruefe("blaue UND lila Kacheln sind dabei", kappen.some((k) => k.lila) && kappen.some((k) => !k.lila), kappen.filter((k) => k.lila).length + " lila, " + kappen.filter((k) => !k.lila).length + " blau"); /* Der Glanz muss trotzdem noch da sein -- er ist jetzt die erste von zwei Hintergrundebenen im ::after. */ const glanz = await s3.evaluate(() => { const k = document.querySelector(".wd-karte"); return getComputedStyle(k, "::after").backgroundImage; }); pruefe("der Glanzstreifen liegt jetzt beim Lichtkegel", /linear-gradient/.test(glanz) && /radial-gradient/.test(glanz), (glanz.match(/gradient/g) || []).length + " Verlaeufe"); await s3.close(); console.log("\n3. BEI REDUZIERTER BEWEGUNG IST ALLES AUS"); const ruhig = await browser.newPage({ viewport: { width: 1440, height: 900 }, reducedMotion: "reduce" }); await ruhig.goto("http://127.0.0.1:4405/webdesign/leistungen.html"); await ruhig.waitForTimeout(2000); const still = await ruhig.evaluate(() => { const k = document.querySelector(".wd-karte"); return { transform: getComputedStyle(k).transform, sichtbar: getComputedStyle(k).opacity, }; }); pruefe("keine Neigung", still.transform === "none", still.transform); /* Abschalten heisst NICHT verschwinden: Die Kachel muss sichtbar bleiben, auch wenn die Einblendung nicht laeuft. */ pruefe("die Kachel ist trotzdem sichtbar", parseFloat(still.sichtbar) > 0.9, still.sichtbar); await ruhig.close(); console.log(`\n===== ${ok} bestanden, ${fehl} fehlgeschlagen =====`); await browser.close(); srv.close(); process.exit(fehl ? 1 : 0); });