diff --git a/blickfang-ueber-pc.png b/blickfang-ueber-pc.png index 76de3a55..8b3853c1 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 ba0282d6..ad132f20 100644 --- a/pruef-verwaltung-schmuck.mjs +++ b/pruef-verwaltung-schmuck.mjs @@ -231,6 +231,62 @@ srv.listen(4404, async () => { v >= 4.5, v.toFixed(2) + ":1"); } + console.log("\n===== 3b. FOLGT DAS LICHT DER MAUS? ====="); + await bereichAufbauen(seite, "kunden"); + const karte = seite.locator("#vw-probe .wd-karte"); + const kasten = await karte.boundingBox(); + + /* WICHTIG: mit steps bewegen. Ein Sprung ohne Zwischenschritte + erzeugt kein pointermove -- der Test wuerde "Licht folgt nicht" + melden, obwohl alles stimmt. Diese Falle hat hier schon einmal + einen Fehlalarm ausgeloest. */ + await seite.mouse.move(kasten.x + 30, kasten.y + 20, { steps: 8 }); + await seite.waitForTimeout(320); + const lichtA = await karte.evaluate((e) => ({ + x: getComputedStyle(e).getPropertyValue("--wd-lichtx").trim(), + y: getComputedStyle(e).getPropertyValue("--wd-lichty").trim(), + })); + await seite.mouse.move(kasten.x + kasten.width - 40, + kasten.y + kasten.height - 20, { steps: 16 }); + await seite.waitForTimeout(400); + const lichtB = await karte.evaluate((e) => { + const st = getComputedStyle(e); + const na = getComputedStyle(e, "::after"); + return { + x: st.getPropertyValue("--wd-lichtx").trim(), + y: st.getPropertyValue("--wd-lichty").trim(), + lumen: st.getPropertyValue("--wd-lumen").trim(), + kante: st.backgroundImage.includes("radial-gradient"), + kegel: na.backgroundImage, + }; + }); + pruefe("das Licht wandert mit dem Zeiger", + lichtA.x !== lichtB.x && lichtA.y !== lichtB.y, + lichtA.x + "/" + lichtA.y + " -> " + lichtB.x + "/" + lichtB.y); + pruefe("die Kachelkante leuchtet an der Zeigerstelle", lichtB.kante); + /* Der Lichtkegel muss die Leitfarbe des Bereichs tragen, nicht die + Grundfarbe -- sonst leuchtet im Kundenbereich Baby Blue statt + Indigo. */ + pruefe("der Lichtkegel traegt die Leitfarbe", + /97,\s*92,\s*255/.test(lichtB.kegel), lichtB.lumen); + + console.log("\n===== 3c. BEWEGT SICH DIE BUEHNE MIT? ====="); + await seite.mouse.move(160, 780, { steps: 12 }); + await seite.waitForTimeout(650); + const lage1 = await seite.evaluate(() => + getComputedStyle(document.querySelector(".vw-buehne")).translate); + await seite.mouse.move(1320, 130, { steps: 20 }); + await seite.waitForTimeout(750); + const lage2 = await seite.evaluate(() => + getComputedStyle(document.querySelector(".vw-buehne")).translate); + pruefe("die Buehne verschiebt sich gegen den Zeiger", + lage1 !== lage2 && lage1 !== "none", lage1 + " -> " + lage2); + + /* Der Ausschlag muss klein bleiben. Waere er gross, wuerde beim Lesen + staendig etwas im Augenwinkel wandern. */ + 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===== 4. WECHSELT DER RAUM WIRKLICH? ====="); const gesehen = []; for (const bereich of BEREICHE) { @@ -273,6 +329,24 @@ srv.listen(4404, async () => { bewegung.animation === "none", bewegung.animation); /* Abschalten heisst NICHT verschwinden. */ pruefe("die Buehne ist trotzdem da", bewegung.sichtbar === "visible", bewegung.sichtbar); + await ruhig.mouse.move(200, 700, { steps: 10 }); + await ruhig.waitForTimeout(400); + await ruhig.mouse.move(1200, 200, { steps: 16 }); + await ruhig.waitForTimeout(600); + const ruhigeLage = await ruhig.evaluate(() => { + const b = document.querySelector(".vw-buehne"); + const vor = getComputedStyle(b, "::before"); + return { + translate: getComputedStyle(b).translate, + reflex: vor.animationName, + reflexSichtbar: parseFloat(vor.opacity), + }; + }); + pruefe("keine Parallaxe bei reduzierter Bewegung", + ruhigeLage.translate === "none", ruhigeLage.translate); + pruefe("und kein Lichtreflex", + ruhigeLage.reflex === "none" && ruhigeLage.reflexSichtbar === 0, + ruhigeLage.reflex + ", Deckkraft " + ruhigeLage.reflexSichtbar); await ruhig.close(); console.log("\n===== 6. HANDY ====="); diff --git a/pruef-vw-schmuck-computer.png b/pruef-vw-schmuck-computer.png index 69b140f1..f2a38eeb 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 a1ce5733..7adc5c55 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 89d4d712..3c8857b7 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-v37"; +const CACHE_NAME = "dogfather-webdesign-v38"; /* 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 14569756..d2dbed19 100644 --- a/webdesign/verwaltung.html +++ b/webdesign/verwaltung.html @@ -176,15 +176,74 @@ rgb(3 8 16 / .14) 0%, rgb(6 19 35 / .20) 55%, rgb(3 8 16 / .40) 100%); } + /* ---------- DIE BUEHNE ATMET MIT ---------- + + Eine sehr leichte Gegenbewegung zum Mauszeiger. Der Ausschlag liegt + bei wenigen Bildpunkten -- gerade genug, dass sich der Raum echt + anfuehlt statt wie eine Tapete, aber nicht so viel, dass beim Lesen + etwas wackelt. + + Die Buehne ist dafuer minimal groesser als der Bildschirm skaliert. + Ohne diesen Vorhalt wuerde beim Verschieben an einer Kante ein + schwarzer Streifen auftauchen. */ + .vw-buehne { + /* Die Verschiebung steht bewusst in "translate" und NICHT in + "transform". + + Grund: Die Einblend-Animation weiter unten animiert "transform" + und haelt ihren Endwert fest (fill-mode both). Eine Animation + schlaegt jede normale Regel -- die Parallaxe waere damit + wirkungslos gewesen. Genau das ist beim Bauen passiert: Die Werte + kamen sauber an, das Bild stand trotzdem still. + + "translate" ist eine eigene Eigenschaft und wird VOR "transform" + angewendet. Beide koennen sich so nicht mehr in die Quere kommen. */ + translate: calc(var(--vw-px, 0) * -14px) calc(var(--vw-py, 0) * -10px); + transform: scale(1.045); + transition: translate .45s cubic-bezier(.2,.7,.3,1); + will-change: translate; + } + /* Beim Bereichswechsel wechselt der ganze Raum. */ @keyframes vw-buehne-ein { - from { opacity: 0; transform: scale(1.06); } - to { opacity: 1; transform: none; } + from { opacity: 0; transform: scale(1.10); } + to { opacity: 1; transform: scale(1.045); } + } + .vw-buehne { animation: vw-buehne-ein .75s cubic-bezier(.16,.8,.3,1) both; } + + /* ---------- DER LICHTREFLEX BEIM WECHSEL ---------- + + Ein einzelner heller Streifen zieht schraeg ueber die Buehne, wenn + der Bereich wechselt. Er laeuft genau einmal und ist danach weg -- + kein Dauerflackern, sondern ein Moment. */ + .vw-buehne::before { + content: ""; + position: absolute; + inset: -20%; + background: linear-gradient(108deg, + transparent 38%, + rgb(var(--vw-farbe) / .16) 47%, + rgb(255 255 255 / .22) 50%, + rgb(var(--vw-farbe) / .16) 53%, + transparent 62%); + opacity: 0; + animation: vw-reflex 1.05s cubic-bezier(.3,.6,.2,1) .12s both; + } + @keyframes vw-reflex { + 0% { opacity: 0; transform: translateX(-62%); } + 22% { opacity: 1; } + 78% { opacity: 1; } + 100% { opacity: 0; transform: translateX(62%); } } - .vw-buehne { animation: vw-buehne-ein .7s cubic-bezier(.16,.8,.3,1) both; } @media (prefers-reduced-motion: reduce) { - .vw-buehne { animation: none; } + /* Alles Bewegte aus. Die Buehne bleibt, der Reflex entfaellt -- + er ist reine Zugabe und traegt keine Information. */ + .vw-buehne { + animation: none; transition: none; + transform: scale(1.045); translate: none; + } + .vw-buehne::before { animation: none; opacity: 0; } } @media (max-width: 900px) { @@ -249,11 +308,63 @@ /* Die Glaskacheln bekommen eine Leuchtkante in der Leitfarbe. Das bindet Inhalt und Buehne zusammen, statt die Kacheln wie aufgeklebte Zettel wirken zu lassen. */ + /* ---------- DAS LICHT FOLGT DER MAUS ---------- + + Das Lumen-Licht war nie weg: --wd-lichtx/--wd-lichty werden weiter + gesetzt, der Lichtkegel steht korrekt. Er war nur unsichtbar + geworden. Die 28 % Deckkraft aus dem Grundsystem waren fuer eine + dunkle, undurchsichtige Kachel gedacht -- auf einer Glasflaeche, durch + die eine beleuchtete Kristallwelt schimmert, geht das unter. + + Hier bekommt es deshalb Kraft, und zwar auf ZWEI Ebenen: + 1. Der Lichtkegel auf der Flaeche (das ::after aus dem System). + 2. Die KANTE der Kachel leuchtet dort auf, wo der Zeiger steht. + Zusammen sieht es aus, als laege eine echte Lichtquelle ueber dem + Glas -- und nicht, als waere ein Fleck aufgemalt. */ #vw-bereich .wd-karte { - border-color: rgb(var(--vw-farbe) / .30); + background: + /* Die Flaeche: dicht genug, dass Text darauf ruhig steht. */ + linear-gradient(180deg, + rgb(var(--wd-glas-rgb) / .52), rgb(6 19 35 / .68)) padding-box, + /* Die Kante: hier steckt der Zeiger drin. Der Verlauf sitzt an + der Mausposition, dadurch wandert der helle Punkt am Rand + entlang mit. */ + radial-gradient(340px circle at var(--wd-lichtx, 50%) var(--wd-lichty, 0%), + rgb(var(--vw-farbe) / .95) 0%, + rgb(var(--vw-farbe) / .40) 38%, + rgb(var(--vw-farbe) / .12) 70%, + rgb(var(--wd-lila-rgb) / .16) 100%) border-box; + border: 1px solid transparent; box-shadow: 0 24px 60px -30px rgb(0 0 0 / .85), - inset 0 1px 0 rgb(var(--vw-farbe) / .16); + inset 0 1px 0 rgb(var(--vw-farbe) / .18); + } + + /* Der Lichtkegel auf der Flaeche, kraeftiger als im Grundsystem. */ + @media (hover: hover) and (pointer: fine) { + #vw-bereich .wd-karte::after { + background: radial-gradient(300px circle at var(--wd-lichtx) var(--wd-lichty), + rgb(var(--vw-farbe) / .30) 0%, + rgb(var(--vw-farbe) / .12) 42%, + transparent 72%); + } + /* Beim Ueberfahren hebt sich die Kachel minimal an. Sehr wenig -- + es soll sich anfuehlen, als reagiere sie, nicht als huepfe sie. */ + #vw-bereich .wd-karte { + transition: transform var(--wd-zeit, .22s) ease, box-shadow var(--wd-zeit, .22s) ease; + } + #vw-bereich .wd-karte:hover { + transform: translateY(-2px); + box-shadow: + 0 30px 70px -28px rgb(0 0 0 / .9), + 0 0 40px -14px rgb(var(--vw-farbe) / .40), + inset 0 1px 0 rgb(var(--vw-farbe) / .26); + } + } + + @media (prefers-reduced-motion: reduce) { + #vw-bereich .wd-karte { transition: none; } + #vw-bereich .wd-karte:hover { transform: none; } } /* Der Titel steht als einziges frei auf der Buehne. Damit er das @@ -3340,6 +3451,46 @@ signaturSetzen(aktiverTab); + /* --------------------------------------------------------------------- + Die Buehne bewegt sich leicht gegen den Mauszeiger. + + Nur auf Geraeten mit echtem Zeiger: Auf einem Telefon gibt es kein + Schweben, dort waere der Effekt entweder wirkungslos oder wuerde bei + jeder Beruehrung zucken. + + Gedrosselt ueber requestAnimationFrame -- ohne das rechnet der + Browser bei jeder einzelnen Zeigerbewegung neu, und das merkt man + ausgerechnet beim Scrollen durch lange Listen. + --------------------------------------------------------------------- */ + (function buehneAtmet() { + var feinerZeiger = false; + try { + feinerZeiger = window.matchMedia && + window.matchMedia("(hover: hover) and (pointer: fine)").matches; + if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; + } catch (e) { return; } + if (!feinerZeiger) return; + + var buehne = document.querySelector(".vw-buehne"); + if (!buehne) return; + + var offen = false, letzteX = 0, letzteY = 0; + document.addEventListener("pointermove", function (e) { + letzteX = e.clientX; + letzteY = e.clientY; + if (offen) return; + offen = true; + requestAnimationFrame(function () { + offen = false; + /* -0,5 bis +0,5 -- der Nullpunkt liegt in der Bildschirmmitte. */ + var px = (letzteX / window.innerWidth) - 0.5; + var py = (letzteY / window.innerHeight) - 0.5; + buehne.style.setProperty("--vw-px", px.toFixed(3)); + buehne.style.setProperty("--vw-py", py.toFixed(3)); + }); + }, { passive: true }); + })(); + document.querySelectorAll("[data-tab]").forEach(function (b) { b.addEventListener("click", function () { aktiverTab = b.dataset.tab;