diff --git a/assets/css/webdesign.css b/assets/css/webdesign.css index 2cfee4d4..ea663488 100644 --- a/assets/css/webdesign.css +++ b/assets/css/webdesign.css @@ -624,6 +624,62 @@ a.wd-karte:hover, .wd-karte--klickbar:hover { .wd-tag--indigo { background: rgb(var(--wd-indigo-rgb) / .16); border-color: rgb(var(--wd-indigo-rgb) / .40); color: rgb(var(--wd-chrome-rgb)); } .wd-tags { display: flex; flex-wrap: wrap; gap: .45rem; } +/* ===================================================================== + BILDER, DIE SICH BEWEGEN + + Faehrt der Zeiger ueber ein Bild, tritt es leicht naeher und wandert + ein Stueck gegen die Zeigerrichtung -- als schaue man durch ein + Fenster und lehne sich zur Seite. + + Warum GEGEN die Richtung: Bewegt sich das Bild mit dem Zeiger, wirkt + es wie ein Aufkleber, der verrutscht. Bewegt es sich dagegen, liest + das Auge es als Tiefe hinter dem Rahmen. Derselbe Grund wie bei der + Buehne im Verwaltungsbereich. + + Der Ausschlag ist klein (hoechstens 14 Punkte) und der Zoom knapp + ueber eins. Beides zusammen ergibt Bewegung, ohne dass ein Bild beim + Ueberfahren seinen Ausschnitt merklich aendert -- sonst waere jedes + Vorbeifahren mit der Maus ein Bildsprung. + ===================================================================== */ +@media (hover: hover) and (pointer: fine) { + + /* Der Rahmen muss den Ueberstand abschneiden, sonst schiebt sich das + vergroesserte Bild ueber die Kachelkante hinaus. */ + .wd-bildrahmen, + .pfo-projekt, + .wd-karte:has(> img:first-child) { + overflow: hidden; + } + + .pfo-bild, + .wd-projekt-bild, + .wd-bildrahmen > img, + .wd-karte > img { + transform: + scale(var(--wd-bildzoom, 1)) + translate3d(calc(var(--wd-bx, 0) * -14px), calc(var(--wd-by, 0) * -10px), 0); + transition: transform .55s cubic-bezier(.2,.75,.25,1); + will-change: transform; + } + + /* Der Zoom kommt erst beim Ueberfahren dazu. Als Dauerzustand waere + jedes Bild permanent leicht beschnitten. */ + .pfo-projekt:hover .pfo-bild, + .wd-bildrahmen:hover > img, + .wd-karte:hover > img, + .pfo-bild:hover, + .wd-projekt-bild:hover { + --wd-bildzoom: 1.055; + } +} + +@media (prefers-reduced-motion: reduce) { + .pfo-bild, .wd-projekt-bild, .wd-bildrahmen > img, .wd-karte > img { + transform: none; + transition: none; + } +} + /* --------------------------------------------------------------------- 7. PREISKARTEN (Masterplan S.8) Preis, Dauer und Personalisierungshinweis gehören sichtbar zusammen — diff --git a/assets/js/wd-core.js b/assets/js/wd-core.js index 510a6c6a..ddffb47c 100644 --- a/assets/js/wd-core.js +++ b/assets/js/wd-core.js @@ -946,6 +946,72 @@ k.style.setProperty("--wd-lichty", (letzte.y - r.top) + "px"); }); }, { passive: true }); + + bilderBewegen(); + } + + /* =================================================================== + BILDER, DIE SICH BEWEGEN + + Faehrt der Zeiger ueber ein Bild, wandert es ein Stueck GEGEN die + Zeigerrichtung. Bewegte es sich mit, wirkte es wie ein Aufkleber, + der verrutscht; dagegen liest das Auge es als Tiefe hinter dem + Rahmen. + + Gesetzt werden Werte von -1 bis +1, umgerechnet auf die Groesse des + Bildes. Ein fester Wert in Bildpunkten waere falsch: Ein + Vorschaubild von 1200 Punkten Breite und ein Kundenlogo von 80 + wuerden sonst gleich weit wandern, was beim kleinen wie ein Ruck + aussieht. + =================================================================== */ + function bilderBewegen() { + var WAHL = ".pfo-bild, .wd-projekt-bild, .wd-bildrahmen > img, .wd-karte > img"; + var offen = false, letztes = null, zx = 0, zy = 0; + + document.addEventListener("pointermove", function (e) { + var bild = null; + if (e.target && e.target.matches && e.target.matches(WAHL)) { + bild = e.target; + } else if (e.target && e.target.closest) { + /* Auch dann greifen, wenn der Zeiger ueber einem Element LIEGT, + das das Bild ueberdeckt -- etwa einem Textblock in derselben + Kachel. Sonst bliebe das Bild stehen, sobald man den Rahmen + verlaesst, aber die Kachel noch nicht. */ + var rahmen = e.target.closest(".wd-bildrahmen, .pfo-projekt, .wd-karte"); + if (rahmen) bild = rahmen.querySelector(WAHL); + } + if (!bild) return; + letztes = bild; zx = e.clientX; zy = e.clientY; + if (offen) return; + offen = true; + requestAnimationFrame(function () { + offen = false; + if (!letztes) return; + var r = letztes.getBoundingClientRect(); + if (!r.width || !r.height) return; + var bx = ((zx - r.left) / r.width - 0.5) * 2; + var by = ((zy - r.top) / r.height - 0.5) * 2; + /* Auf -1..+1 begrenzen: Steht der Zeiger neben dem Bild, aber + noch in der Kachel, kaemen sonst Werte weit darueber heraus + und das Bild schoesse aus dem Rahmen. */ + letztes.style.setProperty("--wd-bx", Math.max(-1, Math.min(1, bx)).toFixed(3)); + letztes.style.setProperty("--wd-by", Math.max(-1, Math.min(1, by)).toFixed(3)); + }); + }, { passive: true }); + + /* Beim Verlassen zuruecksetzen, sonst bleibt das Bild verschoben + stehen, nachdem der Zeiger laengst weg ist. */ + document.addEventListener("pointerout", function (e) { + if (!e.target || !e.target.closest) return; + var rahmen = e.target.closest(".wd-bildrahmen, .pfo-projekt, .wd-karte"); + if (!rahmen) return; + if (e.relatedTarget && rahmen.contains(e.relatedTarget)) return; + var bild = rahmen.querySelector(WAHL); + if (!bild) return; + bild.style.setProperty("--wd-bx", "0"); + bild.style.setProperty("--wd-by", "0"); + if (letztes === bild) letztes = null; + }, { passive: true }); } /* Nach außen sichtbare Schnittstelle für seiteneigene Skripte. */ diff --git a/blickfang-portfolio-handy.png b/blickfang-portfolio-handy.png index cabd250f..885e5873 100644 Binary files a/blickfang-portfolio-handy.png and b/blickfang-portfolio-handy.png differ 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/pruef-portfolio-computer.png b/pruef-portfolio-computer.png index 2d0d22ca..43089747 100644 Binary files a/pruef-portfolio-computer.png and b/pruef-portfolio-computer.png differ diff --git a/pruef-portfolio-handy.png b/pruef-portfolio-handy.png index 910a1805..799bfdc7 100644 Binary files a/pruef-portfolio-handy.png and b/pruef-portfolio-handy.png differ diff --git a/pruef-portfolio.mjs b/pruef-portfolio.mjs index 2680c694..09ecc9a3 100644 --- a/pruef-portfolio.mjs +++ b/pruef-portfolio.mjs @@ -189,7 +189,58 @@ srv.listen(4403, async () => { pruefe(`Schild "${d.text}" ist gut lesbar (AA = 4,5:1)`, v >= 4.5, v.toFixed(2) + ":1"); } - console.log("\n5. KEINE FEHLER, KEINE FEHLENDEN DATEIEN"); + console.log("\n5. BEWEGEN SICH DIE BILDER?"); + const bild = seite.locator(".pfo-bild").first(); + await bild.scrollIntoViewIfNeeded(); + await seite.waitForTimeout(800); + const rahmen = await bild.boundingBox(); + + /* Mit steps bewegen, sonst entsteht kein pointermove. */ + await seite.mouse.move(rahmen.x + 30, rahmen.y + 30, { steps: 10 }); + await seite.waitForTimeout(600); + const linksOben = await bild.evaluate((e) => ({ + bx: parseFloat(e.style.getPropertyValue("--wd-bx")), + versatz: getComputedStyle(e).transform, + })); + await seite.mouse.move(rahmen.x + rahmen.width - 30, + rahmen.y + rahmen.height - 30, { steps: 18 }); + await seite.waitForTimeout(650); + const rechtsUnten = await bild.evaluate((e) => ({ + bx: parseFloat(e.style.getPropertyValue("--wd-bx")), + versatz: getComputedStyle(e).transform, + })); + + pruefe("das Bild reagiert auf den Zeiger", + linksOben.bx < -0.4 && rechtsUnten.bx > 0.4, + linksOben.bx + " -> " + rechtsUnten.bx); + + /* Die Richtung ist der Kern: Bewegt sich das Bild MIT dem Zeiger, + wirkt es wie ein verrutschter Aufkleber. Nur die Gegenbewegung + liest das Auge als Tiefe hinter dem Rahmen. */ + const holeX = (m) => parseFloat(m.slice(m.indexOf("(") + 1, -1).split(",")[4]); + const xLinks = holeX(linksOben.versatz); + const xRechts = holeX(rechtsUnten.versatz); + pruefe("und wandert GEGEN die Zeigerrichtung", + xLinks > 0 && xRechts < 0, + xLinks.toFixed(1) + "px / " + xRechts.toFixed(1) + "px"); + + /* Klein halten: Ein grosser Ausschnittswechsel beim blossen + Vorbeifahren waere ein Bildsprung, kein Effekt. */ + pruefe("der Ausschlag bleibt klein", + Math.abs(xLinks) <= 18 && Math.abs(xRechts) <= 18, + Math.max(Math.abs(xLinks), Math.abs(xRechts)).toFixed(1) + "px"); + + const zoom = parseFloat(rechtsUnten.versatz.slice( + rechtsUnten.versatz.indexOf("(") + 1, -1).split(",")[0]); + pruefe("und das Bild tritt leicht naeher", zoom > 1.01 && zoom < 1.12, + zoom.toFixed(3) + "-fach"); + + await seite.mouse.move(10, 10, { steps: 12 }); + await seite.waitForTimeout(700); + const zurueck = await bild.evaluate((e) => e.style.getPropertyValue("--wd-bx")); + pruefe("beim Verlassen stellt es sich zurueck", zurueck === "0", zurueck); + + console.log("\n6. KEINE FEHLER, KEINE FEHLENDEN DATEIEN"); pruefe("kein Skriptfehler", skriptfehler.length === 0, skriptfehler[0] || ""); const verpasst = fehlt.filter((f) => /\.(jpg|webp|png|css|js|svg)$/.test(f)); pruefe("keine fehlende Datei", verpasst.length === 0, verpasst.join(", ") || "keine"); diff --git a/pruef-vw-schmuck-computer.png b/pruef-vw-schmuck-computer.png index 23ef204a..de3fc880 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 442aea55..6f4fcf4a 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 9e10f585..69cb8c5c 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-v42"; +const CACHE_NAME = "dogfather-webdesign-v43"; /* Bausteine, die die Oberfläche zum Anzeigen braucht. Bewusst KEINE HTML-Datei in dieser Liste. */