diff --git a/blickfang-ueber-handy.png b/blickfang-ueber-handy.png index 3ed9b083..8ac5f4de 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 75dfed54..90dd26df 100644 --- a/pruef-verwaltung-schmuck.mjs +++ b/pruef-verwaltung-schmuck.mjs @@ -129,6 +129,12 @@ srv.listen(4404, async () => { if (!el) return null; const st = getComputedStyle(el); const r = el.getBoundingClientRect(); + /* Seit Buehne und Lampe in einem gemeinsamen Raum stecken, sitzen + Haltung, Stapelplatz und die Auszeichnung als Zierde am RAUM -- + die Buehne darin ist absolut positioniert. Geprueft wird deshalb + der Raum, sonst misst der Test am falschen Element. */ + const raum = el.parentElement; + const rs = getComputedStyle(raum); const treffer = st.backgroundImage.match(/img\/([a-z0-9-]+\.webp)/i); return { datei: treffer ? treffer[1] : "(keins)", @@ -137,9 +143,10 @@ srv.listen(4404, async () => { haltung: st.position, breite: Math.round(r.width), hoehe: Math.round(r.height), - z: parseInt(st.zIndex, 10), - klicks: st.pointerEvents, - versteckt: el.getAttribute("aria-hidden"), + haltungRaum: rs.position, + z: parseInt(rs.zIndex, 10), + klicks: rs.pointerEvents, + versteckt: raum.getAttribute("aria-hidden"), }; }); if (!buehne) { pruefe("Buehne vorhanden", false); } @@ -153,7 +160,7 @@ srv.listen(4404, async () => { pruefe("die Buehne fuellt den ganzen Bildschirm", buehne.breite >= 1430 && buehne.hoehe >= 890, `${buehne.breite}x${buehne.hoehe}`); pruefe("sie steht fest, der Inhalt scrollt darueber", - buehne.haltung === "fixed", buehne.haltung); + buehne.haltungRaum === "fixed", buehne.haltungRaum); pruefe("sie liegt hinter allem", buehne.z < 0, "z-index " + buehne.z); pruefe("sie faengt keine Klicks ab", buehne.klicks === "none", buehne.klicks); pruefe("sie ist als Zierde ausgezeichnet", buehne.versteckt === "true"); @@ -359,6 +366,62 @@ srv.listen(4404, async () => { new Set(staffel.map((k) => k.versatz)).size === staffel.length, staffel.map((k) => k.versatz).join(" ")); + console.log("\n===== 3f. ZEIGERLAMPE, PRISMA UND PARTIKEL ====="); + await bereichAufbauen(seite, "anfragen"); + await seite.mouse.move(300, 300, { steps: 8 }); + await seite.waitForTimeout(300); + await seite.mouse.move(1050, 380, { steps: 20 }); + await seite.waitForTimeout(700); + + const extra = await seite.evaluate(() => { + const raum = document.querySelector(".vw-raum"); + const lampe = document.querySelector(".vw-lampe"); + const flocken = document.querySelector(".vw-flocken"); + const karte = document.querySelector("#vw-probe .wd-karte"); + const ls = getComputedStyle(lampe); + return { + raumIsoliert: getComputedStyle(raum).isolation, + mischung: ls.mixBlendMode, + lampeAn: lampe.classList.contains("vw-lampe-an"), + mx: lampe.style.getPropertyValue("--vw-mx"), + my: lampe.style.getPropertyValue("--vw-my"), + flockenZahl: flocken ? flocken.children.length : 0, + flockeLaeuft: flocken && flocken.children[0] + ? getComputedStyle(flocken.children[0]).animationName : "", + kachelKante: karte ? getComputedStyle(karte).backgroundImage : "", + }; + }); + + /* Die Isolation ist der Kern. Ohne sie mischt sich die Lampe mit + ALLEM in ihrem Stapelkontext -- auch mit Kacheln, die eigentlich + darueber liegen. Beim Bauen lag dadurch ein gruenlicher Fleck + mitten auf einer Karte. */ + pruefe("Buehne und Lampe stecken in einem isolierten Raum", + extra.raumIsoliert === "isolate", extra.raumIsoliert); + /* soft-light rechnet mit dem, was darunter liegt: dunkle Stellen + bleiben dunkel, vorhandene Lichtkanten werden verstaerkt. "screen" + oder "overlay" wuerden die Eiskanten ausbrennen lassen. */ + pruefe("die Lampe beleuchtet, statt zu ueberdecken", + extra.mischung === "soft-light", extra.mischung); + pruefe("sie folgt dem Zeiger", extra.mx === "1050px" && extra.my === "380px", + extra.mx + " / " + extra.my); + /* Erst zeigen, wenn der Zeiger wirklich da war -- sonst haengt beim + Laden ein Lichtfleck in der Bildmitte, den niemand dorthin gelegt + hat. */ + pruefe("und war vor der ersten Zeigerbewegung aus", extra.lampeAn === true); + + pruefe("es schweben Partikel im Raum", extra.flockenZahl === 9, + extra.flockenZahl + " Stueck"); + pruefe("und sie bewegen sich", extra.flockeLaeuft === "vw-schweben", + extra.flockeLaeuft); + + /* Der Prisma-Schimmer faechert die Kante in Nachbartoene auf. Geprueft + wird, dass mehr als nur die Leitfarbe drinsteckt -- sonst waere es + wieder ein einfarbiger Rand. */ + const toene = (extra.kachelKante.match(/rgba?\(/g) || []).length; + pruefe("die Kachelkante bricht das Licht in mehrere Toene", toene >= 6, + toene + " Farbstufen"); + console.log("\n===== 4. WECHSELT DER RAUM WIRKLICH? ====="); const gesehen = []; for (const bereich of BEREICHE) { @@ -440,6 +503,15 @@ srv.listen(4404, async () => { pruefe("und der Balken gleitet nicht, sondern springt", laengste <= 0.01, ruhigExtra.balken); + const ruhigFlocken = await ruhig.evaluate(() => { + const f = document.querySelector(".vw-flocken"); + return f ? getComputedStyle(f).display : "(fehlt)"; + }); + /* Komplett weg, nicht nur angehalten: Ein eingefrorener Punkt mitten + im Bild waere ein Fleck ohne Sinn. */ + pruefe("keine Partikel bei reduzierter Bewegung", + ruhigFlocken === "none", ruhigFlocken); + 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 67ea6ec4..c0eabc14 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 f3a83fe8..2b4a9d0a 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 5b44137c..5ee0c6e1 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-v39"; +const CACHE_NAME = "dogfather-webdesign-v40"; /* 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 379a30fd..3b0e7e4e 100644 --- a/webdesign/verwaltung.html +++ b/webdesign/verwaltung.html @@ -151,11 +151,21 @@ steht Text NIE direkt auf dem Bild -- und das Bild muss nirgends mehr weichen. */ - .vw-buehne { + .vw-raum { position: fixed; inset: 0; z-index: -2; pointer-events: none; + overflow: hidden; + /* Hier endet die Mischung der Lampe. */ + isolation: isolate; + } + + .vw-buehne { + position: absolute; + inset: 0; + z-index: 0; + pointer-events: none; background: var(--vw-motiv, url("/assets/img/cryo-wappen.webp")) center center / cover no-repeat; /* Kein Zoom: "cover" zeigt bei einem 16:9-Motiv auf einem @@ -264,6 +274,113 @@ } } + /* ---------- DIE ZEIGERLAMPE ---------- + + Ein weicher Lichtkegel wandert ueber die Buehne und hellt die + Kristalle dort auf, wo der Zeiger steht. Damit wird das Bild zu + einer Flaeche, die auf einen reagiert, statt nur dazuzuliegen. + + Der Kniff steckt in mix-blend-mode: soft-light. Ein normaler heller + Verlauf wuerde das Bild ueberdecken und milchig machen. "soft-light" + rechnet stattdessen mit dem, was darunter liegt -- dunkle Stellen + bleiben dunkel, vorhandene Lichtkanten der Kristalle werden + verstaerkt. Das Bild wird nicht ueberstrahlt, es wird beleuchtet. + + Bewusst NICHT "screen" oder "overlay": Beide reissen die hellen + Stellen zu und lassen die Eiskanten ausbrennen. Genau die machen + aber den Reiz der Motive aus. */ + .vw-lampe { + position: absolute; + inset: 0; + z-index: 1; + pointer-events: none; + mix-blend-mode: soft-light; + /* Gross und weich, nicht klein und hell. + + Die Kacheln tragen eine Rueckseiten-Unschaerfe -- sie nehmen also + auf, was hinter ihnen liegt, und das ist unter anderem diese + Lampe. Das ist genau richtig so: Licht, das durch Milchglas + scheint. Bei einem engen Kegel war davon aber ein scharf + umrissener Fleck mitten auf der Karte uebrig. + + Deshalb ein weiter Radius mit vielen flachen Stufen: Der + Helligkeitsunterschied verteilt sich ueber die halbe Kachel und + liest sich als Schimmer statt als Kreis. */ + background: radial-gradient( + 1100px circle at var(--vw-mx, 50%) var(--vw-my, 40%), + rgb(var(--vw-farbe) / .34) 0%, + rgb(var(--vw-farbe) / .26) 22%, + rgb(var(--vw-farbe) / .16) 42%, + rgb(255 255 255 / .06) 62%, + rgb(var(--vw-farbe) / .04) 80%, + transparent 100%); + opacity: 0; + transition: opacity .5s ease; + } + /* Erst zeigen, wenn der Zeiger wirklich da war -- sonst haengt beim + Laden ein Lichtfleck in der Bildmitte, den niemand dorthin gelegt + hat. */ + .vw-lampe.vw-lampe-an { opacity: 1; } + + @media (prefers-reduced-motion: reduce) { + .vw-lampe { transition: none; } + } + /* Ohne feinen Zeiger gibt es kein Schweben -- dann ergibt eine + Zeigerlampe keinen Sinn. */ + @media not all and (hover: hover) { + .vw-lampe { display: none; } + } + + /* ---------- SCHWEBENDE EISPARTIKEL ---------- + + Wenige Lichtpunkte steigen sehr langsam auf, zwischen Buehne und + Inhalt. Rein aus CSS, ohne Zeichenflaeche -- eine Zeichenflaeche + wuerde dauerhaft Rechenzeit kosten, und das auf einer Seite, auf + der man arbeitet. + + Bewusst nur neun Stueck und sehr langsam. Es soll wirken wie + Staub im Lichtkegel, nicht wie Schneefall. */ + .vw-flocken { + position: fixed; + inset: 0; + z-index: -1; + pointer-events: none; + overflow: hidden; + } + .vw-flocken i { + position: absolute; + display: block; + width: 3px; height: 3px; + border-radius: 50%; + background: rgb(var(--vw-farbe) / .75); + box-shadow: 0 0 10px 1px rgb(var(--vw-farbe) / .45); + opacity: 0; + animation: vw-schweben linear infinite; + } + @keyframes vw-schweben { + 0% { transform: translateY(0) translateX(0); opacity: 0; } + 12% { opacity: .85; } + 88% { opacity: .85; } + 100% { transform: translateY(-118vh) translateX(28px); opacity: 0; } + } + /* Jede Flocke eigene Bahn, Dauer und Startzeit -- sonst sieht man + sofort, dass es immer dieselbe Bewegung ist. */ + .vw-flocken i:nth-child(1) { left: 6%; bottom: -6px; animation-duration: 34s; animation-delay: -2s; } + .vw-flocken i:nth-child(2) { left: 17%; bottom: -6px; animation-duration: 47s; animation-delay: -9s; width: 2px; height: 2px; } + .vw-flocken i:nth-child(3) { left: 29%; bottom: -6px; animation-duration: 39s; animation-delay: -21s; } + .vw-flocken i:nth-child(4) { left: 41%; bottom: -6px; animation-duration: 52s; animation-delay: -14s; width: 2px; height: 2px; } + .vw-flocken i:nth-child(5) { left: 55%; bottom: -6px; animation-duration: 36s; animation-delay: -30s; } + .vw-flocken i:nth-child(6) { left: 67%; bottom: -6px; animation-duration: 44s; animation-delay: -5s; width: 4px; height: 4px; } + .vw-flocken i:nth-child(7) { left: 78%; bottom: -6px; animation-duration: 41s; animation-delay: -25s; } + .vw-flocken i:nth-child(8) { left: 88%; bottom: -6px; animation-duration: 49s; animation-delay: -17s; width: 2px; height: 2px; } + .vw-flocken i:nth-child(9) { left: 96%; bottom: -6px; animation-duration: 37s; animation-delay: -11s; } + + @media (prefers-reduced-motion: reduce) { + /* Komplett weg, nicht nur angehalten: Ein eingefrorener Punkt + mitten im Bild waere ein Fleck ohne Sinn. */ + .vw-flocken { display: none; } + } + /* ---------- GLAS ---------- Alles, worauf gelesen wird, bekommt eine Rueckseiten-Unschaerfe. @@ -400,12 +517,29 @@ /* Die Kante: hier steckt der Zeiger drin. Der Verlauf sitzt an der Mausposition, dadurch wandert der helle Punkt am Rand entlang mit. */ + /* PRISMA-SCHIMMER. + + Am hellsten Punkt sitzt die Leitfarbe, daneben faechert die + Kante in Nachbartoene auf -- wie Licht, das sich in einer + Glaskante bricht. + + Bewusst KEIN Regenbogen: Volle Spektralfarben sehen nach + Seifenblase aus, nicht nach geschliffenem Eis. Deshalb bleibt + es innerhalb der kalten Haelfte der Palette (Aqua, Ion, + Violet), und die Nachbartoene liegen deutlich unter der + Leitfarbe. */ 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; + rgb(255 255 255 / .55) 0%, + rgb(var(--vw-farbe) / .95) 9%, + rgb(var(--wd-aqua-rgb) / .42) 24%, + rgb(var(--wd-ion-rgb) / .30) 40%, + rgb(var(--wd-lila-rgb) / .22) 62%, + rgb(var(--vw-farbe) / .12) 84%, + rgb(var(--wd-lila-rgb) / .14) 100%) border-box; + /* 1,5 px statt 1 px: Bei genau einem Punkt verschluckt das + Bildschirmraster die Farbaufaecherung fast vollstaendig -- man + sieht dann nur noch die Leitfarbe. */ + border: 1.5px solid transparent; box-shadow: 0 24px 60px -30px rgb(0 0 0 / .85), inset 0 1px 0 rgb(var(--vw-farbe) / .18); @@ -1609,7 +1743,27 @@ - + + +
@@ -3660,6 +3814,8 @@ var buehne = document.querySelector(".vw-buehne"); if (!buehne) return; + var lampe = document.querySelector(".vw-lampe"); + var lampeAn = false; var offen = false, letzteX = 0, letzteY = 0; document.addEventListener("pointermove", function (e) { @@ -3674,6 +3830,16 @@ var py = (letzteY / window.innerHeight) - 0.5; buehne.style.setProperty("--vw-px", px.toFixed(3)); buehne.style.setProperty("--vw-py", py.toFixed(3)); + + /* Die Zeigerlampe braucht echte Bildpunkte, nicht den + Anteilswert: Ein radialer Verlauf mit Prozentangaben + verschiebt sich mit der Elementgroesse und laege dann nicht + mehr unter dem Zeiger. */ + if (lampe) { + lampe.style.setProperty("--vw-mx", letzteX + "px"); + lampe.style.setProperty("--vw-my", letzteY + "px"); + if (!lampeAn) { lampe.classList.add("vw-lampe-an"); lampeAn = true; } + } }); }, { passive: true }); })();