diff --git a/blickfang-ueber-handy.png b/blickfang-ueber-handy.png index 8ac5f4de..b419b546 100644 Binary files a/blickfang-ueber-handy.png and b/blickfang-ueber-handy.png differ diff --git a/pruef-verwaltung-schmuck.mjs b/pruef-verwaltung-schmuck.mjs index 90dd26df..50950fb1 100644 --- a/pruef-verwaltung-schmuck.mjs +++ b/pruef-verwaltung-schmuck.mjs @@ -345,7 +345,16 @@ srv.listen(4404, async () => { 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"); + /* EIGENER Behaelter. Frueher wurde hier die Probekachel geleert -- + danach fehlten ihr Ueberschrift und Text, und der Neigungstest + weiter unten stuerzte ab, weil er auf ein nicht vorhandenes + Element zugriff. */ + let probe = document.querySelector("#vw-staffel"); + if (!probe) { + probe = document.createElement("div"); + probe.id = "vw-staffel"; + be.appendChild(probe); + } probe.innerHTML = ""; for (let i = 0; i < 4; i++) { const k = document.createElement("div"); @@ -422,6 +431,92 @@ srv.listen(4404, async () => { pruefe("die Kachelkante bricht das Licht in mehrere Toene", toene >= 6, toene + " Farbstufen"); + console.log("\n===== 3g. NEIGEN SICH DIE KACHELN? ====="); + await bereichAufbauen(seite, "kunden"); + const platte = seite.locator("#vw-probe .wd-karte").first(); + const pk = await platte.boundingBox(); + + /* Mit steps bewegen, sonst entsteht kein pointermove. */ + await seite.mouse.move(pk.x + 25, pk.y + 20, { steps: 10 }); + await seite.waitForTimeout(420); + const eckeA = await platte.evaluate((e) => ({ + nx: parseFloat(e.style.getPropertyValue("--vw-nx")), + ny: parseFloat(e.style.getPropertyValue("--vw-ny")), + matrix: getComputedStyle(e).transform, + })); + await seite.mouse.move(pk.x + pk.width - 25, pk.y + pk.height - 20, { steps: 18 }); + await seite.waitForTimeout(420); + const eckeB = await platte.evaluate((e) => ({ + nx: parseFloat(e.style.getPropertyValue("--vw-nx")), + ny: parseFloat(e.style.getPropertyValue("--vw-ny")), + matrix: getComputedStyle(e).transform, + })); + + /* Die Werte muessen unabhaengig von der Kachelgroesse sein: -1 am + linken Rand, +1 am rechten. Sonst kippt eine breite Kachel + staerker als eine schmale, obwohl der Zeiger relativ gleich steht. */ + /* Geprueft wird der VORZEICHENWECHSEL, nicht ein fester Betrag. + Eine flache Kachel ist nur gut 100 Punkte hoch -- 20 Punkte vom + Rand liegen dort schon fast in der Mitte, der senkrechte Wert + bleibt also klein. Ein Schwellwert von 0,5 haette nur gemessen, + wie hoch die Testkachel zufaellig ist. */ + pruefe("die Neigung kippt in die Gegenrichtung", + eckeA.nx < -0.4 && eckeB.nx > 0.4 && + eckeA.ny < 0 && eckeB.ny > 0, + `${eckeA.nx}/${eckeA.ny} -> ${eckeB.nx}/${eckeB.ny}`); + pruefe("und wird als echte Raumdrehung umgesetzt", + eckeA.matrix.startsWith("matrix3d") && eckeA.matrix !== eckeB.matrix, + eckeA.matrix.slice(0, 26) + "..."); + + /* Der Ausschlag muss klein bleiben, sonst verzerrt die Schrift. Aus + der Matrix laesst sich der Winkel ueber den Eintrag [0][2] + zurueckrechnen: sin(Drehung um die Hochachse). */ + /* NUR den Inhalt der Klammern auswerten. Ein Suchmuster ueber die + ganze Zeichenkette liest die "3" aus "matrix3d" als erste Zahl mit + und verschiebt damit jeden Eintrag um eine Stelle -- der Winkel kam + dadurch als 0,4 Grad heraus statt als 7. */ + const inhalt = eckeB.matrix.slice(eckeB.matrix.indexOf("(") + 1, -1); + const zahlen = inhalt.split(",").map((n) => parseFloat(n)); + const winkel = Math.abs(Math.asin(Math.max(-1, Math.min(1, zahlen[2]))) * 180 / Math.PI); + pruefe("aber flach genug, dass die Schrift scharf bleibt", winkel <= 9, + winkel.toFixed(1) + " Grad"); + + /* Beim Verlassen zuruecksetzen -- sonst bleibt die Kachel schraeg + stehen, nachdem der Zeiger laengst weg ist. */ + await seite.mouse.move(40, 40, { steps: 14 }); + await seite.waitForTimeout(520); + const zurueck = await platte.evaluate((e) => ({ + nx: e.style.getPropertyValue("--vw-nx"), + ny: e.style.getPropertyValue("--vw-ny"), + })); + pruefe("und sie stellt sich beim Verlassen zurueck", + zurueck.nx === "0" && zurueck.ny === "0", `${zurueck.nx}/${zurueck.ny}`); + + const tiefe = await seite.evaluate(() => { + const k = document.querySelector("#vw-probe .wd-karte"); + const h = k.querySelector("h2"); + const p = k.querySelector("p"); + return { + ueberschrift: getComputedStyle(h).transform, + absatz: getComputedStyle(p).transform, + ecken: getComputedStyle(k).borderRadius, + glanz: getComputedStyle(k, "::before").backgroundImage.slice(0, 30), + }; + }); + /* Der Inhalt steht vor der Flaeche, Ueberschriften weiter vorn als + Fliesstext -- dadurch entsteht beim Neigen echte Staffelung. */ + pruefe("der Inhalt steht vor der Flaeche", + tiefe.ueberschrift !== "none" && tiefe.absatz !== "none" && + tiefe.ueberschrift !== tiefe.absatz, + "Ueberschrift und Text auf verschiedenen Ebenen"); + /* Ungleiche Ecken: Eine gleichmaessig gerundete Kachel liest sich als + Knopf, die ungleiche nimmt die Facetten der Motive auf. */ + const radien = tiefe.ecken.split(/\s+/).map(parseFloat); + pruefe("die Ecken sind ungleich geschliffen", + new Set(radien).size > 1, tiefe.ecken); + pruefe("und ein Glanzstreifen liegt auf der Platte", + /gradient/.test(tiefe.glanz), tiefe.glanz); + console.log("\n===== 4. WECHSELT DER RAUM WIRKLICH? ====="); const gesehen = []; for (const bereich of BEREICHE) { @@ -490,6 +585,15 @@ srv.listen(4404, async () => { balken: getComputedStyle(l, "::after").transitionDuration, }; }); + const ruhigNeigung = await ruhig.evaluate(() => { + const be = document.querySelector("#vw-bereich"); + let k = be.querySelector(".wd-karte"); + if (!k) { k = document.createElement("div"); k.className = "wd-karte"; be.appendChild(k); } + return getComputedStyle(k).transform; + }); + pruefe("keine Neigung bei reduzierter Bewegung", + ruhigNeigung === "none", ruhigNeigung); + pruefe("keine Auftauch-Animation der Kacheln", ruhigExtra.kachel === "none", ruhigExtra.kachel); /* Geprueft wird "praktisch sofort", nicht wortwoertlich "0s". diff --git a/pruef-vw-schmuck-computer.png b/pruef-vw-schmuck-computer.png index c0eabc14..8f78eed6 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 2b4a9d0a..bed81fd9 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 5ee0c6e1..7270b3c0 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-v40"; +const CACHE_NAME = "dogfather-webdesign-v41"; /* 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 3b0e7e4e..274f4b11 100644 --- a/webdesign/verwaltung.html +++ b/webdesign/verwaltung.html @@ -476,9 +476,16 @@ 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. */ + /* Die Auftauch-Animation nutzt "translate" und "scale" als EIGENE + Eigenschaften, nicht "transform". + + Grund: Die raeumliche Neigung weiter unten braucht "transform" fuer + sich. Eine Animation haelt ihren Endwert fest und schlaegt jede + normale Regel -- die Neigung waere sonst wirkungslos. Dieselbe + Falle wie bei der Buehne, nur eine Ebene tiefer. */ @keyframes vw-kachel-auf { - from { opacity: 0; transform: translateY(14px) scale(.985); } - to { opacity: 1; transform: none; } + from { opacity: 0; translate: 0 14px; scale: .985; } + to { opacity: 1; translate: none; scale: 1; } } #vw-bereich .wd-karte { animation: vw-kachel-auf .5s cubic-bezier(.2,.8,.3,1) both; @@ -553,23 +560,100 @@ 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. */ + } + + /* ---------- DIE KACHEL ALS GESCHLIFFENE GLASPLATTE ---------- + + Sie liegt nicht flach auf der Seite, sondern im Raum. Faehrt der + Zeiger darueber, neigt sie sich ihm entgegen -- als wuerde man eine + echte Glasplatte kippen. + + Der Ausschlag ist bewusst sehr klein (hoechstens 7 Grad). Alles + darueber verzerrt die Schrift sichtbar, und auf diesen Kacheln wird + gearbeitet, nicht nur geschaut. Genau deshalb kippt auch nur die + Kachel, waehrend der Text darin durch translateZ wieder nach vorn + geholt wird: Er steht dadurch fast senkrecht zum Auge und bleibt + scharf, obwohl die Flaeche unter ihm schraeg liegt. */ + @media (hover: hover) and (pointer: fine) { + #vw-bereich { + /* Ohne Perspektive gaebe es keine Tiefe -- die Neigung saehe aus + wie ein platt gedruecktes Parallelogramm. */ + perspective: 1400px; + } #vw-bereich .wd-karte { - transition: transform var(--wd-zeit, .22s) ease, box-shadow var(--wd-zeit, .22s) ease; + transform-style: preserve-3d; + transform: + rotateX(calc(var(--vw-ny, 0) * -7deg)) + rotateY(calc(var(--vw-nx, 0) * 7deg)) + translateZ(0); + transition: + transform .32s cubic-bezier(.2,.8,.3,1), + box-shadow .32s ease; + will-change: transform; } + /* Beim Ueberfahren kommt die Kachel dem Betrachter entgegen und + wirft einen laengeren Schatten -- das erst macht aus der Neigung + ein Objekt im Raum statt eines schraegen Bildes. */ #vw-bereich .wd-karte:hover { - transform: translateY(-2px); + transform: + rotateX(calc(var(--vw-ny, 0) * -7deg)) + rotateY(calc(var(--vw-nx, 0) * 7deg)) + translateZ(32px); 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); + 0 44px 90px -34px rgb(0 0 0 / .95), + 0 0 60px -18px rgb(var(--vw-farbe) / .45), + inset 0 1px 0 rgb(var(--vw-farbe) / .30); } + + /* Der Inhalt steht vor der Flaeche. Ueberschriften weiter vorn als + Fliesstext -- dadurch entsteht beim Neigen echte Staffelung + statt einer flachen Ebene, die sich mitdreht. */ + #vw-bereich .wd-karte > * { transform: translateZ(18px); } + #vw-bereich .wd-karte h2, + #vw-bereich .wd-karte h3 { transform: translateZ(34px); } + + /* DER GLANZSTREIFEN. + + Ein schmales Licht, das ueber die Platte laeuft und mit der + Neigung wandert. Es sitzt im ::before, weil ::after schon den + Lichtkegel traegt. */ + #vw-bereich .wd-karte::before { + content: ""; + position: absolute; + inset: 0; + z-index: 0; + border-radius: inherit; + pointer-events: none; + background: linear-gradient( + calc(112deg + var(--vw-nx, 0) * 26deg), + transparent 34%, + rgb(255 255 255 / .07) 46%, + rgb(var(--vw-farbe) / .16) 50%, + rgb(255 255 255 / .07) 54%, + transparent 66%); + opacity: 0; + transition: opacity .3s ease; + } + #vw-bereich .wd-karte:hover::before { opacity: 1; } + } + + /* Die Ecken sind ungleich gerundet -- diagonal weit, diagonal knapp. + Eine gleichmaessig gerundete Kachel liest sich als Knopf; die + ungleiche Rundung nimmt die Facetten der Motive auf. */ + #vw-bereich .wd-karte { + border-radius: 36px 6px 36px 6px; } @media (prefers-reduced-motion: reduce) { - #vw-bereich .wd-karte { transition: none; } + #vw-bereich .wd-karte { + transition: none; + transform: none; + } #vw-bereich .wd-karte:hover { transform: none; } + #vw-bereich .wd-karte > *, + #vw-bereich .wd-karte h2, + #vw-bereich .wd-karte h3 { transform: none; } + #vw-bereich .wd-karte::before { display: none; } } /* Der Titel steht als einziges frei auf der Buehne. Damit er das @@ -3762,6 +3846,62 @@ } catch (e) { /* aeltere Browser: dann eben nur bei resize */ } })(); + /* --------------------------------------------------------------------- + Die Kacheln neigen sich zum Zeiger. + + Das Grundsystem setzt bereits --wd-lichtx/--wd-lichty in + Bildpunkten, gemessen ab der Kachelecke. Fuer eine Neigung braucht + es aber einen Wert, der von der Kachelgroesse unabhaengig ist: + -1 am linken Rand, +1 am rechten. Sonst wuerde eine breite Kachel + staerker kippen als eine schmale, obwohl der Zeiger relativ an + derselben Stelle steht. + + Beim Verlassen wird auf 0 zurueckgesetzt -- sonst bliebe die + Kachel schraeg stehen, nachdem der Zeiger laengst weg ist. + --------------------------------------------------------------------- */ + (function kachelnNeigen() { + try { + if (!window.matchMedia("(hover: hover) and (pointer: fine)").matches) return; + if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; + } catch (e) { return; } + + var offen = false, letzte = null, zx = 0, zy = 0; + + document.addEventListener("pointermove", function (e) { + var karte = e.target && e.target.closest + ? e.target.closest("#vw-bereich .wd-karte") : null; + if (!karte) return; + letzte = karte; zx = e.clientX; zy = e.clientY; + if (offen) return; + offen = true; + requestAnimationFrame(function () { + offen = false; + if (!letzte) return; + var r = letzte.getBoundingClientRect(); + if (!r.width || !r.height) return; + /* -1 bis +1, Nullpunkt in der Kachelmitte. */ + var nx = ((zx - r.left) / r.width - 0.5) * 2; + var ny = ((zy - r.top) / r.height - 0.5) * 2; + letzte.style.setProperty("--vw-nx", nx.toFixed(3)); + letzte.style.setProperty("--vw-ny", ny.toFixed(3)); + }); + }, { passive: true }); + + /* pointerout statt mouseleave: Es meldet zuverlaessig auch dann, + wenn der Zeiger die Kachel ueber ein Kindelement verlaesst. */ + document.addEventListener("pointerout", function (e) { + var karte = e.target && e.target.closest + ? e.target.closest("#vw-bereich .wd-karte") : null; + if (!karte) return; + /* Nur zuruecksetzen, wenn der Zeiger die Kachel WIRKLICH verlaesst + und nicht bloss von einem Kind zum naechsten wandert. */ + if (e.relatedTarget && karte.contains(e.relatedTarget)) return; + karte.style.setProperty("--vw-nx", "0"); + karte.style.setProperty("--vw-ny", "0"); + if (letzte === karte) letzte = null; + }, { passive: true }); + })(); + /* --------------------------------------------------------------------- Das Licht folgt dem Zeiger auch auf den Reitern.