diff --git a/pruef-kalender-computer.png b/pruef-kalender-computer.png index 16d48593..0fe2a3ac 100644 Binary files a/pruef-kalender-computer.png and b/pruef-kalender-computer.png differ diff --git a/pruef-kalender-handy.png b/pruef-kalender-handy.png index 9a2a2048..f2077f4e 100644 Binary files a/pruef-kalender-handy.png and b/pruef-kalender-handy.png differ diff --git a/pruef-steckbrief.png b/pruef-steckbrief.png index 97203090..aff6306a 100644 Binary files a/pruef-steckbrief.png and b/pruef-steckbrief.png differ diff --git a/server/pruef-start-ansicht.mjs b/server/pruef-start-ansicht.mjs index d94f2fe6..953d05a5 100644 --- a/server/pruef-start-ansicht.mjs +++ b/server/pruef-start-ansicht.mjs @@ -405,6 +405,50 @@ console.log("\n=== Das Licht folgt dem Zeiger"); const kasten = await kachel.boundingBox(); const ruhe = await kachel.evaluate((k) => k.style.getPropertyValue("--mx") || "(Ruhelage)"); + + /* Seit dem 01.09.2026 gibt es zum Fleck auch einen RAND, der auf der + Kante mitwandert. Er entsteht aus einem Verlauf, von dem eine Maske + nur den Saum stehen laesst -- geht die Maske verloren, waere die + ganze Karte eingefaerbt und der Text unlesbar. Deshalb wird hier + nicht "sieht gut aus" geprueft, sondern an ECHTEN Bildpunkten + gemessen, ob die Kante heller ist als die Mitte. */ + { + await seite.mouse.move(kasten.x + kasten.width * 0.5, kasten.y + kasten.height * 0.5); + await seite.waitForTimeout(450); + const bild = await seite.screenshot({ type: "png" }); + const r = await seite.evaluate(async ({ b64, kasten }) => { + const bild = new Image(); + bild.src = "data:image/png;base64," + b64; + await bild.decode(); + const c = document.createElement("canvas"); + c.width = bild.width; c.height = bild.height; + const g = c.getContext("2d"); + g.drawImage(bild, 0, 0); + const skala = bild.width / window.innerWidth; + const hell = (x, y) => { + const p = g.getImageData(Math.round(x * skala), Math.round(y * skala), 1, 1).data; + return 0.2126 * p[0] + 0.7152 * p[1] + 0.0722 * p[2]; + }; + /* Der Zeiger steht in der Mitte. Gemessen wird die Kante direkt + darueber (dort muss der Rand leuchten) gegen dieselbe Kante an + der weit entfernten Ecke (dort nicht). */ + return { + licht: !!document.querySelector(".kachel .licht"), + kanteNah: hell(kasten.x + kasten.width * 0.5, kasten.y + 1.5), + kanteFern: hell(kasten.x + 3, kasten.y + kasten.height - 2), + }; + }, { b64: bild.toString("base64"), kasten }); + + ok(r.licht, "die Karten haben ein Licht-Element bekommen"); + /* Die nahe Kante muss spuerbar heller sein als die ferne. Ohne die + Maske waere die ganze Karte gleich hell -- der Unterschied ist + genau der Nachweis, dass der Rand wirklich der Maus folgt. */ + ok(r.kanteNah > r.kanteFern + 8, + `der Rand leuchtet dort, wo der Zeiger ist ` + + `(nah ${r.kanteNah.toFixed(0)} gegen fern ${r.kanteFern.toFixed(0)})`); + await seite.mouse.move(5, 5); + await seite.waitForTimeout(400); + } ok(ruhe === "(Ruhelage)", `ruhend keine eigene Position gesetzt (${ruhe})`); await seite.mouse.move(kasten.x + kasten.width * 0.2, kasten.y + kasten.height * 0.3); diff --git a/workspace/assets/css/start.css b/workspace/assets/css/start.css index 476e1202..80139bd9 100644 --- a/workspace/assets/css/start.css +++ b/workspace/assets/css/start.css @@ -1463,3 +1463,96 @@ body.start #protokoll { radial-gradient(90% 40% at 85% 98%, rgba(138, 118, 255, .07), transparent 72%); } } + +/* ===================================================================== + DAS LICHT FOLGT DEM ZEIGER — auf jeder Karte, im ganzen Workspace + + Zwei Schichten in einem eingefuegten (siehe + kopf.js, dort steht auch, warum ein eigenes Element und nicht + ::before am Kasten selbst): + + ::before der FLECK — ein weicher Schein unter dem Zeiger + ::after der RAND — eine helle Stelle, die auf der KANTE + mitwandert + + Der Rand ist der Teil, der es besonders macht. Er entsteht aus einem + Farbverlauf, von dem eine Maske nur den ein Pixel breiten Saum stehen + laesst: zwei Ebenen, eine ueber die Innenflaeche, eine ueber den + ganzen Kasten, und "exclude" laesst genau die Differenz uebrig — den + Rahmen. Dadurch leuchtet wirklich die Kante an der Stelle, an der der + Zeiger ist, statt eines Rechtecks, das so tut als ob. + + Die Farbe kommt aus --ton, also aus der Kachelfarbe des Bereichs. + Eine Aufgabenkarte leuchtet orange, eine Kalenderkarte tuerkis. + ===================================================================== */ + +.licht { + position: absolute; inset: 0; + border-radius: inherit; + pointer-events: none; + z-index: 0; + opacity: 0; + transition: opacity .28s ease; +} +[data-licht="an"] > .licht { opacity: 1; } + +/* Der Fleck. Bewusst gross und sehr weich — ein kleiner harter Kreis + sieht nach Taschenlampe aus, ein grosser weicher nach Beleuchtung. */ +.licht::before { + content: ""; + position: absolute; inset: 0; + border-radius: inherit; + background: radial-gradient(240px circle at var(--mx, 50%) var(--my, 0%), + color-mix(in srgb, var(--ton, var(--akzent)) 20%, transparent), + transparent 68%); +} + +/* Der Rand. Etwas kleiner im Radius als der Fleck, dafuer kraeftiger -- + so wirkt er wie eine Reflexion auf einer Kante, nicht wie ein zweiter + Schein. */ +.licht::after { + content: ""; + position: absolute; inset: 0; + border-radius: inherit; + padding: 1px; + background: radial-gradient(170px circle at var(--mx, 50%) var(--my, 0%), + color-mix(in srgb, var(--ton, var(--akzent)) 85%, #ffffff 15%), + color-mix(in srgb, var(--ton, var(--akzent)) 30%, transparent) 45%, + transparent 72%); + -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); + -webkit-mask-composite: xor; + mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); + mask-composite: exclude; +} + +/* Alles, was Licht bekommen soll, braucht einen eigenen Bezugspunkt -- + sonst legt sich der Schein ueber die halbe Seite statt ueber die + Karte. Die meisten haben ihn schon; diese Zeile ist die Versicherung + fuer die uebrigen. */ +.kachel, .karte, .spalte, .k-anlasskarte, .k-tag, .k-wochentag, +.dran__punkt, .zahl, .datei, .person, .eintrag, .thema, +.k-listentag, .steckbrief, .leer-hinweis, .block { + position: relative; +} + +/* Inhalt ueber das Licht. Die Karten setzen ihre Kinder meist nicht + ausdruecklich; bei gleichem Stapelwert gewinnt das spaetere Element, + und .licht wird als erstes Kind eingefuegt. Fuer die Faelle, in denen + ein Kind selbst positioniert ist, hier die Zusicherung. */ +.kachel__link, .kachel__text, .karte__titel, .spalte__kopf, +.k-anlasskarte__txt, .dran__link, .steckbrief__kopf { position: relative; z-index: 1; } + +/* Ohne feinen Zeiger oder bei "weniger Bewegung" wird gar kein + Licht-Element eingefuegt (kopf.js). Diese Regel ist die zweite + Sicherung, falls doch eines im Dokument steht. */ +@media (prefers-reduced-motion: reduce) { + .licht { display: none; } +} +@media (hover: none), (pointer: coarse) { + .licht { display: none; } +} +/* Im Kontrastmodus gibt es keine Farbverlaeufe -- ein Licht, das dort + als Flaeche erscheint, wuerde den Text verdecken. */ +@media (forced-colors: active) { + .licht { display: none; } +} diff --git a/workspace/assets/js/kopf.js b/workspace/assets/js/kopf.js index 5a9a491b..c708f4ca 100644 --- a/workspace/assets/js/kopf.js +++ b/workspace/assets/js/kopf.js @@ -128,6 +128,97 @@ return true; } + /* ---------- Das Licht folgt dem Zeiger --------------------------------- + + Auf der Startseite gab es das schon, aber nur dort und nur als + Lichtfleck. Jetzt auf JEDER Karte im ganzen Workspace -- und mit + zwei Schichten: + + der FLECK ein weicher Schein unter dem Zeiger, in der Farbe des + Bereichs + der RAND eine helle Stelle, die auf der Kante mitwandert + + Der Rand ist der Teil, der den Unterschied macht. Er entsteht aus + einem Farbverlauf, von dem eine Maske nur den ein Pixel breiten + Saum stehen laesst (mask-composite: exclude). Dadurch leuchtet + wirklich die KANTE an der Stelle, an der der Zeiger ist -- nicht + ein Rechteck, das so tut als ob. + + Beides steckt in einem eingefuegten . Ein + eigenes Element statt ::before/::after, weil die bei fast allen + Karten schon fuer etwas anderes gebraucht werden (Akzentstreifen, + Wasserzeichen) -- ein Pseudo-Element doppelt zu belegen geht nicht, + und der Fehler faellt erst auf, wenn eines der beiden verschwindet. + + Kosten: EIN Zuhoerer fuer das ganze Dokument, gedrosselt auf einen + Bildaufbau. Ohne feinen Zeiger (Handy) und bei "weniger Bewegung" + laeuft gar nichts -- dann wird auch kein Element eingefuegt. */ + + const LICHT_KARTEN = [ + '.kachel', '.karte', '.spalte', '.k-anlasskarte', '.k-tag', '.k-wochentag', + '.dran__punkt', '.zahl', '.datei', '.person', '.eintrag', '.thema', + '.listentag', '.k-listentag', '.steckbrief', '.leer-hinweis', '.block', + '.saeule', '.karte-gross', '.kandidat', '.punkt', '.call', '.report', + ].join(', '); + + window.lichtFolgen = function (wurzel = document) { + const feinerZeiger = window.matchMedia('(hover: hover) and (pointer: fine)').matches; + const ruhig = window.matchMedia('(prefers-reduced-motion: reduce)').matches; + if (!feinerZeiger || ruhig) return; + + /* Jede Karte bekommt ihr Licht-Element -- einmal. Karten kommen + nachtraeglich dazu (Listen werden neu aufgebaut), deshalb wird das + beim ersten Ueberfahren nachgeholt statt einmalig beim Laden. */ + const vorbereiten = (karte) => { + if (karte.dataset.lichtBereit) return; + karte.dataset.lichtBereit = 'ja'; + const l = document.createElement('span'); + l.className = 'licht'; + l.setAttribute('aria-hidden', 'true'); + /* Als ERSTES Kind: Bei gleichem Stapelwert gewinnt das spaetere + Element -- so liegt aller Inhalt ueber dem Licht. */ + karte.prepend(l); + }; + + let warten = false; + let letzte = null; + + wurzel.addEventListener('pointermove', (e) => { + const karte = e.target.closest?.(LICHT_KARTEN); + if (!karte) return; + letzte = { karte, x: e.clientX, y: e.clientY }; + if (warten) return; + warten = true; + requestAnimationFrame(() => { + warten = false; + if (!letzte) return; + const { karte: k, x, y } = letzte; + vorbereiten(k); + const r = k.getBoundingClientRect(); + k.style.setProperty('--mx', ((x - r.left) / r.width * 100).toFixed(1) + '%'); + k.style.setProperty('--my', ((y - r.top) / r.height * 100).toFixed(1) + '%'); + k.dataset.licht = 'an'; + }); + }, { passive: true }); + + /* Beim Verlassen zurueck in die Ruhelage. Ohne das bliebe der Fleck + dort stehen, wo der Zeiger die Karte verlassen hat -- und die + Karten saehen nach einer Weile alle unterschiedlich beleuchtet + aus, ohne dass jemand wuesste, warum. */ + wurzel.addEventListener('pointerout', (e) => { + const karte = e.target.closest?.(LICHT_KARTEN); + if (!karte) return; + /* pointerout feuert auch beim Wechsel zwischen Kindern INNERHALB + derselben Karte. Nur wenn der Zeiger die Karte wirklich + verlaesst, wird geloescht -- sonst flackerte das Licht bei jeder + Bewegung ueber ein Wort hinweg. */ + if (karte.contains(e.relatedTarget)) return; + karte.removeAttribute('data-licht'); + karte.style.removeProperty('--mx'); + karte.style.removeProperty('--my'); + }, { capture: true, passive: true }); + }; + /* ---------- Jede Seite trägt Zeichen und Farbe ihrer Kachel ------------- Auf der Startseite hat jeder Bereich sein eigenes Zeichen und seinen @@ -250,6 +341,10 @@ marke.dataset.rang = 'ja'; } + /* Das Licht auf dem ganzen Dokument scharfstellen -- ein Zuhoerer + fuer alle Karten, auch fuer die, die erst spaeter dazukommen. */ + window.lichtFolgen(document); + /* Die Startseite hat den Knopf gar nicht erst im Dokument -- dort gibt es kein "davor". Deshalb genügt hier die Prüfung, ob er existiert. */ const knopf = document.getElementById('zurueck'); diff --git a/workspace/assets/js/start.js b/workspace/assets/js/start.js index eaa1c456..92eadcc3 100644 --- a/workspace/assets/js/start.js +++ b/workspace/assets/js/start.js @@ -169,7 +169,11 @@ abschnitt.append(liste); ziel.append(abschnitt); } - lichtFolgen(ziel); + /* KEIN eigener Aufruf mehr: kopf.js stellt das Licht einmal fuer das + ganze Dokument scharf, und der Zuhoerer dort erfasst auch Kacheln, + die erst hier entstehen. Ein zweiter Aufruf an dieser Stelle + haette zwei Zuhoerer auf denselben Bewegungen bedeutet -- doppelte + Arbeit bei jedem Zeigerzucken. */ } /* ---------- Das Licht folgt dem Zeiger ----------------------------------- @@ -186,41 +190,12 @@ Auf Fingerbedienung und bei "weniger Bewegung" bleibt es ganz aus -- dort gibt es keinen Zeiger, dem etwas folgen könnte. */ - function lichtFolgen(wurzel) { - const feinerZeiger = window.matchMedia('(hover: hover) and (pointer: fine)').matches; - const ruhig = window.matchMedia('(prefers-reduced-motion: reduce)').matches; - if (!feinerZeiger || ruhig) return; + /* lichtFolgen steht seit dem 01.09.2026 in kopf.js und gilt fuer ALLE + Karten im Workspace, nicht nur fuer die Kacheln hier. Eine zweite + Fassung an dieser Stelle haette bedeutet: zwei Zuhoerer auf + denselben Bewegungen, und der Fehler waere erst aufgefallen, wenn + jemand die eine geaendert haette und sich nichts tat. */ - let warten = false; - let letzte = null; - - wurzel.addEventListener('pointermove', (e) => { - const kachel = e.target.closest?.('.kachel'); - if (!kachel) return; - letzte = { kachel, x: e.clientX, y: e.clientY }; - if (warten) return; - warten = true; - requestAnimationFrame(() => { - warten = false; - if (!letzte) return; - const { kachel: k, x, y } = letzte; - const r = k.getBoundingClientRect(); - k.style.setProperty('--mx', ((x - r.left) / r.width * 100).toFixed(1) + '%'); - k.style.setProperty('--my', ((y - r.top) / r.height * 100).toFixed(1) + '%'); - }); - }); - - /* Beim Verlassen zurück in die Ruhelage. Ohne das bliebe der Fleck - dort stehen, wo der Zeiger die Kachel verlassen hat -- und die - Kacheln sähen nach einer Weile alle unterschiedlich beleuchtet aus, - ohne dass jemand wüsste, warum. */ - wurzel.addEventListener('pointerleave', (e) => { - const kachel = e.target.closest?.('.kachel'); - if (!kachel) return; - kachel.style.removeProperty('--mx'); - kachel.style.removeProperty('--my'); - }, true); - } /* ---------- Die Begrüßung ------------------------------------------------ diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index bf68bb4a..fb720925 100644 --- a/workspace/aufgaben.html +++ b/workspace/aufgaben.html @@ -7,9 +7,9 @@ - - - + + + @@ -139,9 +139,9 @@ - - - - + + + + diff --git a/workspace/automation.html b/workspace/automation.html index 22e241d7..2d94ccb8 100644 --- a/workspace/automation.html +++ b/workspace/automation.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -132,9 +132,9 @@ - - - - + + + + diff --git a/workspace/bereich.html b/workspace/bereich.html index bf4fc29c..218b2b4e 100644 --- a/workspace/bereich.html +++ b/workspace/bereich.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -107,9 +107,9 @@ - - - - + + + + diff --git a/workspace/calls.html b/workspace/calls.html index d826ccb3..db172504 100644 --- a/workspace/calls.html +++ b/workspace/calls.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -54,10 +54,10 @@ - - - - - + + + + + diff --git a/workspace/content.html b/workspace/content.html index cc2fc20e..78c0d22c 100644 --- a/workspace/content.html +++ b/workspace/content.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -158,9 +158,9 @@ - - - - + + + + diff --git a/workspace/dateien.html b/workspace/dateien.html index 22e55308..a32f1685 100644 --- a/workspace/dateien.html +++ b/workspace/dateien.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -93,9 +93,9 @@ - - - - + + + + diff --git a/workspace/index.html b/workspace/index.html index 8707edb1..b8dd2eb8 100644 --- a/workspace/index.html +++ b/workspace/index.html @@ -25,7 +25,7 @@ type="image/webp" media="(max-width: 899px)" /> - + @@ -143,6 +143,6 @@

- + diff --git a/workspace/kalender.html b/workspace/kalender.html index ce53dda1..b4ed370d 100644 --- a/workspace/kalender.html +++ b/workspace/kalender.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -147,9 +147,9 @@ - - - - + + + + diff --git a/workspace/personen.html b/workspace/personen.html index 98da88f7..fdfbcbc8 100644 --- a/workspace/personen.html +++ b/workspace/personen.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -121,9 +121,9 @@ - - - - + + + + diff --git a/workspace/profil.html b/workspace/profil.html index 3472d057..d8cfc879 100644 --- a/workspace/profil.html +++ b/workspace/profil.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -170,9 +170,9 @@ - - - - + + + + diff --git a/workspace/report.html b/workspace/report.html index d641659f..e8ed61fd 100644 --- a/workspace/report.html +++ b/workspace/report.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -91,10 +91,10 @@ - - - - - + + + + + diff --git a/workspace/scouting.html b/workspace/scouting.html index 095efc68..f5cd51b8 100644 --- a/workspace/scouting.html +++ b/workspace/scouting.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -124,9 +124,9 @@ - - - - + + + + diff --git a/workspace/start.html b/workspace/start.html index 297e4112..1fd1181f 100644 --- a/workspace/start.html +++ b/workspace/start.html @@ -7,8 +7,8 @@ - - + + @@ -99,9 +99,9 @@

- - - - + + + + diff --git a/workspace/startcheck.html b/workspace/startcheck.html index 0b0ae2bb..16530869 100644 --- a/workspace/startcheck.html +++ b/workspace/startcheck.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -70,10 +70,10 @@ - - - - - + + + + + diff --git a/workspace/uebersicht.html b/workspace/uebersicht.html index b9ad0038..56b050dc 100644 --- a/workspace/uebersicht.html +++ b/workspace/uebersicht.html @@ -7,9 +7,9 @@ - - - + + + @@ -57,9 +57,9 @@ - + - - + + diff --git a/workspace/wissen.html b/workspace/wissen.html index 2b6a8e62..26f13449 100644 --- a/workspace/wissen.html +++ b/workspace/wissen.html @@ -7,10 +7,10 @@ - - - - + + + + @@ -175,9 +175,9 @@ - - - - + + + +