diff --git a/blickfang-ueber-handy.png b/blickfang-ueber-handy.png index 8ac5f4de..3ed9b083 100644 Binary files a/blickfang-ueber-handy.png and b/blickfang-ueber-handy.png differ diff --git a/blickfang-ueber-pc.png b/blickfang-ueber-pc.png index 8b3853c1..76de3a55 100644 Binary files a/blickfang-ueber-pc.png and b/blickfang-ueber-pc.png differ diff --git a/pruef-verwaltung-schmuck.mjs b/pruef-verwaltung-schmuck.mjs index ad132f20..75dfed54 100644 --- a/pruef-verwaltung-schmuck.mjs +++ b/pruef-verwaltung-schmuck.mjs @@ -287,6 +287,78 @@ srv.listen(4404, async () => { const weite = Math.max(...String(lage2).match(/-?[\d.]+/g).map((n) => Math.abs(+n))); pruefe("und zwar nur wenige Bildpunkte", weite <= 16, weite.toFixed(1) + "px"); + console.log("\n===== 3d. DER LEUCHTBALKEN ====="); + /* Der Balken muss GENAU am aktiven Reiter sitzen. Gemessen wird + gegen die echten Masse des Reiters -- feste Werte waeren hier + zwangslaeufig falsch, weil die Reiter unterschiedlich breit sind + und beim Sprachwechsel wandern. */ + const balkenPruefen = async (bereich) => { + /* Erst den Bereich sichtbar machen, DANN klicken: Ein Klick auf + einen zugeklappten Arbeitsbereich misst zwangslaeufig 0. + Danach wieder aufmachen -- der Klick loest die Zugangspruefung + aus, die korrekterweise zur Anmeldung zurueckschaltet. */ + await bereichAufbauen(seite, bereich); + await seite.click(`[data-tab="${bereich}"]`).catch(() => {}); + await seite.waitForTimeout(500); + await bereichAufbauen(seite, bereich); + await seite.evaluate(() => window.vwBalkenSetzen && window.vwBalkenSetzen()); + await seite.waitForTimeout(400); + return seite.evaluate(() => { + const l = document.querySelector("#vw-bereich .vw-tabs"); + const a = l.querySelector(".vw-filter-knopf.vw-an"); + const lr = l.getBoundingClientRect(); + const ar = a.getBoundingClientRect(); + const st = l.style; + return { + gesetztX: parseInt(st.getPropertyValue("--vw-balken-x"), 10), + gesetztB: parseInt(st.getPropertyValue("--vw-balken-b"), 10), + echtX: Math.round(ar.left - lr.left), + echtB: Math.round(ar.width), + sichtbar: st.getPropertyValue("--vw-balken-da"), + reiter: a.textContent.trim().slice(0, 12), + }; + }); + }; + + const b1 = await balkenPruefen("uebersicht"); + pruefe("der Balken sitzt am aktiven Reiter", + Math.abs(b1.gesetztX - b1.echtX) <= 2 && Math.abs(b1.gesetztB - b1.echtB) <= 2, + `${b1.reiter}: gesetzt ${b1.gesetztX}/${b1.gesetztB}, echt ${b1.echtX}/${b1.echtB}`); + pruefe("und er ist sichtbar", b1.sichtbar === "1", b1.sichtbar); + /* Der haeufigste Fehlerfall: Beim Start ist der Arbeitsbereich noch + versteckt, die Leiste also 0 Punkte breit. Wer nur auf "resize" + horcht, misst nie nach -- der Balken bleibt auf Breite 0. */ + pruefe("er hat eine echte Breite (nicht 0)", b1.gesetztB > 20, b1.gesetztB + "px"); + + const b2 = await balkenPruefen("zahlungen"); + pruefe("er wandert zum neuen Reiter mit", + b2.gesetztX !== b1.gesetztX && Math.abs(b2.gesetztX - b2.echtX) <= 2, + `${b1.gesetztX}px -> ${b2.gesetztX}px`); + + console.log("\n===== 3e. TAUCHEN DIE KACHELN GESTAFFELT AUF? ====="); + const staffel = await seite.evaluate(() => { + const be = document.querySelector("#vw-bereich"); + const probe = document.querySelector("#vw-probe"); + probe.innerHTML = ""; + for (let i = 0; i < 4; i++) { + const k = document.createElement("div"); + k.className = "wd-karte"; + k.style.padding = "1rem"; + k.textContent = "Kachel " + (i + 1); + probe.appendChild(k); + } + return [...probe.querySelectorAll(".wd-karte")].map((k) => ({ + name: getComputedStyle(k).animationName, + versatz: getComputedStyle(k).animationDelay, + })); + }); + pruefe("die Kacheln haben eine Auftauch-Animation", + staffel.every((k) => k.name === "vw-kachel-auf"), + staffel.map((k) => k.name).join(", ")); + pruefe("und jede startet etwas spaeter als die davor", + new Set(staffel.map((k) => k.versatz)).size === staffel.length, + staffel.map((k) => k.versatz).join(" ")); + console.log("\n===== 4. WECHSELT DER RAUM WIRKLICH? ====="); const gesehen = []; for (const bereich of BEREICHE) { @@ -344,6 +416,30 @@ srv.listen(4404, async () => { }); pruefe("keine Parallaxe bei reduzierter Bewegung", ruhigeLage.translate === "none", ruhigeLage.translate); + const ruhigExtra = await ruhig.evaluate(() => { + const be = document.querySelector("#vw-bereich"); + const k = document.createElement("div"); + k.className = "wd-karte"; + be.appendChild(k); + const l = document.querySelector("#vw-bereich .vw-tabs"); + return { + kachel: getComputedStyle(k).animationName, + balken: getComputedStyle(l, "::after").transitionDuration, + }; + }); + pruefe("keine Auftauch-Animation der Kacheln", + ruhigExtra.kachel === "none", ruhigExtra.kachel); + /* Geprueft wird "praktisch sofort", nicht wortwoertlich "0s". + + Die Testumgebung emuliert reduzierte Bewegung, indem sie alle + Uebergaenge auf eine Mikrosekunde setzt statt auf null -- gemeldet + wird dann "1e-06s". Wahrnehmbar ist das identisch; ein Test auf + exakt "0s" wuerde nur die Emulation messen, nicht die Regel. */ + const laengste = Math.max(...ruhigExtra.balken.split(",") + .map((t) => parseFloat(t) || 0)); + pruefe("und der Balken gleitet nicht, sondern springt", + laengste <= 0.01, ruhigExtra.balken); + pruefe("und kein Lichtreflex", ruhigeLage.reflex === "none" && ruhigeLage.reflexSichtbar === 0, ruhigeLage.reflex + ", Deckkraft " + ruhigeLage.reflexSichtbar); diff --git a/pruef-vw-schmuck-computer.png b/pruef-vw-schmuck-computer.png index f2a38eeb..67ea6ec4 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 7adc5c55..f3a83fe8 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 3c8857b7..5b44137c 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-v38"; +const CACHE_NAME = "dogfather-webdesign-v39"; /* 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 d2dbed19..379a30fd 100644 --- a/webdesign/verwaltung.html +++ b/webdesign/verwaltung.html @@ -308,6 +308,77 @@ /* Die Glaskacheln bekommen eine Leuchtkante in der Leitfarbe. Das bindet Inhalt und Buehne zusammen, statt die Kacheln wie aufgeklebte Zettel wirken zu lassen. */ + /* ---------- DER GLEITENDE LEUCHTBALKEN ---------- + + Unter der Reiterreihe liegt ein Balken in der Leitfarbe. Beim + Wechsel springt er nicht, sondern gleitet zum neuen Reiter und + faerbt sich dabei um. + + Position und Breite kommen aus dem ECHTEN Reiter, gemessen im + Browser. Feste Werte waeren hier zwangslaeufig falsch: Die Reiter + sind unterschiedlich breit ("Kunden" gegen "Zahlungen"), sie + verschieben sich beim Sprachwechsel, und auf schmalen Schirmen + brechen sie um. */ + .vw-tabs { + position: relative; + } + .vw-tabs::after { + content: ""; + position: absolute; + left: 0; + top: var(--vw-balken-y, 100%); + width: var(--vw-balken-b, 0px); + height: 2px; + border-radius: 2px; + transform: translateX(var(--vw-balken-x, 0px)); + background: linear-gradient(90deg, + rgb(var(--vw-farbe) / 0) 0%, + rgb(var(--vw-farbe) / .95) 22%, + rgb(var(--vw-farbe) / .95) 78%, + rgb(var(--vw-farbe) / 0) 100%); + box-shadow: 0 0 16px -2px rgb(var(--vw-farbe) / .75); + transition: + transform .42s cubic-bezier(.22,.9,.24,1), + width .42s cubic-bezier(.22,.9,.24,1), + top .42s cubic-bezier(.22,.9,.24,1); + pointer-events: none; + /* Erst zeigen, wenn er vermessen ist -- sonst blitzt beim Laden + kurz ein Balken der Breite 0 an der linken Kante auf. */ + opacity: var(--vw-balken-da, 0); + } + @media (prefers-reduced-motion: reduce) { + .vw-tabs::after { transition: none; } + } + + /* ---------- KACHELN TAUCHEN AUF ---------- + + Beim Bereichswechsel erscheinen die Kacheln nacheinander statt alle + auf einmal. Der Versatz ist klein; er soll den Blick von oben nach + unten fuehren, nicht auf eine Animation warten lassen. + + Nur die ersten acht bekommen einen Versatz. Bei einer langen Liste + wuerde die letzte Kachel sonst spuerbar spaeter kommen -- das + fuehlt sich nicht mehr elegant an, sondern langsam. */ + @keyframes vw-kachel-auf { + from { opacity: 0; transform: translateY(14px) scale(.985); } + to { opacity: 1; transform: none; } + } + #vw-bereich .wd-karte { + animation: vw-kachel-auf .5s cubic-bezier(.2,.8,.3,1) both; + } + #vw-bereich .wd-karte:nth-child(1) { animation-delay: .02s; } + #vw-bereich .wd-karte:nth-child(2) { animation-delay: .07s; } + #vw-bereich .wd-karte:nth-child(3) { animation-delay: .12s; } + #vw-bereich .wd-karte:nth-child(4) { animation-delay: .17s; } + #vw-bereich .wd-karte:nth-child(5) { animation-delay: .22s; } + #vw-bereich .wd-karte:nth-child(6) { animation-delay: .27s; } + #vw-bereich .wd-karte:nth-child(7) { animation-delay: .32s; } + #vw-bereich .wd-karte:nth-child(n+8) { animation-delay: .36s; } + + @media (prefers-reduced-motion: reduce) { + #vw-bereich .wd-karte { animation: none; } + } + /* ---------- DAS LICHT FOLGT DER MAUS ---------- Das Lumen-Licht war nie weg: --wd-lichtx/--wd-lichty werden weiter @@ -376,6 +447,30 @@ 0 1px 4px rgb(3 8 16 / .9); } + /* Auch die Reiter reagieren auf den Zeiger. Der Punkt ist klein und + sitzt genau unter dem Finger -- er soll die Flaeche beleben, nicht + vom Beschriftungstext ablenken. */ + @media (hover: hover) and (pointer: fine) { + #vw-bereich .vw-filter-knopf { + position: relative; + overflow: hidden; + isolation: isolate; + } + #vw-bereich .vw-filter-knopf::before { + content: ""; + position: absolute; + inset: 0; + z-index: -1; + background: radial-gradient(90px circle at var(--wd-lichtx, 50%) var(--wd-lichty, 50%), + rgb(var(--vw-farbe) / .30) 0%, + rgb(var(--vw-farbe) / .10) 45%, + transparent 72%); + opacity: 0; + transition: opacity .25s ease; + } + #vw-bereich .vw-filter-knopf:hover::before { opacity: 1; } + } + /* Suchen und Abmelden sitzen oben rechts -- also genau dort, wo das Motiv am hellsten ist. Bedienelemente muessen immer lesbar sein, egal was dahinter liegt. Deshalb bekommen sie hier einen eigenen, @@ -3450,6 +3545,98 @@ } signaturSetzen(aktiverTab); + balkenSetzen(); + + /* --------------------------------------------------------------------- + Der Leuchtbalken unter der Reiterreihe. + + Er wird am echten Reiter ausgemessen. Feste Werte waeren hier + zwangslaeufig falsch: Die Reiter sind unterschiedlich breit, sie + verschieben sich beim Sprachwechsel, und auf schmalen Schirmen + brechen sie in eine zweite Zeile um -- deshalb wandert auch die + Hoehe mit, nicht nur die Seite. + --------------------------------------------------------------------- */ + function balkenSetzen() { + var leiste = document.querySelector("#vw-bereich .vw-tabs"); + if (!leiste) return; + var aktiv = leiste.querySelector(".vw-filter-knopf.vw-an"); + if (!aktiv) { leiste.style.setProperty("--vw-balken-da", "0"); return; } + + var l = leiste.getBoundingClientRect(); + var a = aktiv.getBoundingClientRect(); + /* Relativ zur Leiste rechnen, nicht zum Fenster: Sonst wandert der + Balken beim Scrollen mit dem Fenster statt mit dem Reiter. */ + leiste.style.setProperty("--vw-balken-x", Math.round(a.left - l.left) + "px"); + leiste.style.setProperty("--vw-balken-b", Math.round(a.width) + "px"); + leiste.style.setProperty("--vw-balken-y", Math.round(a.bottom - l.top + 4) + "px"); + leiste.style.setProperty("--vw-balken-da", "1"); + } + + /* Neu vermessen, sobald sich an der Leiste etwas aendert. + + Ein blosser "resize"-Horcher reicht nicht. Der haeufigste Fall ist + naemlich gar keine Fenstergroessenaenderung, sondern das + Sichtbarwerden: Beim Start ist der Arbeitsbereich noch versteckt, + die Leiste also 0 Punkte breit -- und ein verstecktes Element + loest kein resize aus. Genau das ist beim Bauen passiert: Der + Balken stand auf Breite 0 und blieb unsichtbar. + + Ein ResizeObserver deckt beides ab -- Sichtbarwerden, Umbrechen der + Reiter auf schmalen Schirmen und den Sprachwechsel, bei dem die + Beschriftungen unterschiedlich lang werden. */ + var balkenWartet = false; + function balkenNeuMessen() { + if (balkenWartet) return; + balkenWartet = true; + requestAnimationFrame(function () { balkenWartet = false; balkenSetzen(); }); + } + window.addEventListener("resize", balkenNeuMessen, { passive: true }); + + /* Nach aussen verfuegbar machen. + + Der Balken haengt sonst allein am Klick-Listener. Wechselt der + Bereich auf einem anderen Weg -- etwa direkt nach dem Anmelden, + wenn der Arbeitsbereich zum ersten Mal sichtbar wird -- wird nie + nachgemessen, und er bleibt auf Breite 0 stehen. */ + window.vwBalkenSetzen = balkenSetzen; + + (function balkenBeobachten() { + var leiste = document.querySelector("#vw-bereich .vw-tabs"); + if (!leiste || typeof ResizeObserver === "undefined") return; + try { + new ResizeObserver(balkenNeuMessen).observe(leiste); + } catch (e) { /* aeltere Browser: dann eben nur bei resize */ } + })(); + + /* --------------------------------------------------------------------- + Das Licht folgt dem Zeiger auch auf den Reitern. + + Die Verfolgung im Grundsystem greift ausdruecklich nur auf Karten. + Hier wird sie fuer die Reiter ergaenzt -- gedrosselt ueber + requestAnimationFrame, aus demselben Grund wie dort. + --------------------------------------------------------------------- */ + (function reiterLicht() { + try { + if (!window.matchMedia("(hover: hover) and (pointer: fine)").matches) return; + } catch (e) { return; } + + var offen = false, letzter = null, zx = 0, zy = 0; + document.addEventListener("pointermove", function (e) { + var knopf = e.target && e.target.closest + ? e.target.closest("#vw-bereich .vw-filter-knopf") : null; + if (!knopf) return; + letzter = knopf; zx = e.clientX; zy = e.clientY; + if (offen) return; + offen = true; + requestAnimationFrame(function () { + offen = false; + if (!letzter) return; + var r = letzter.getBoundingClientRect(); + letzter.style.setProperty("--wd-lichtx", (zx - r.left) + "px"); + letzter.style.setProperty("--wd-lichty", (zy - r.top) + "px"); + }); + }, { passive: true }); + })(); /* --------------------------------------------------------------------- Die Buehne bewegt sich leicht gegen den Mauszeiger. @@ -3498,6 +3685,9 @@ document.querySelectorAll("[data-tab]").forEach(function (x) { x.classList.toggle("vw-an", x.dataset.tab === aktiverTab); }); + /* Erst NACH dem Umsetzen der Klasse messen -- vorher zeigt der + Balken noch auf den alten Reiter. */ + balkenSetzen(); /* Auf dem Handy sind die Reiter ein Wischstreifen. Wer ueber eine Kachel nach "Postfach" springt, landet sonst in einer Ansicht, deren Reiter ausserhalb des Streifens liegt -- man sieht dann