diff --git a/pruef-start-computer.png b/pruef-start-computer.png index 129e7496..0c08eca1 100644 Binary files a/pruef-start-computer.png and b/pruef-start-computer.png differ diff --git a/pruef-start-creator.png b/pruef-start-creator.png index 15051e78..3f12157e 100644 Binary files a/pruef-start-creator.png and b/pruef-start-creator.png differ diff --git a/pruef-start-handy.png b/pruef-start-handy.png index 3ff145a8..ff1d459c 100644 Binary files a/pruef-start-handy.png and b/pruef-start-handy.png differ diff --git a/server/pruef-start-ansicht.mjs b/server/pruef-start-ansicht.mjs index a240bcb4..626dfb05 100644 --- a/server/pruef-start-ansicht.mjs +++ b/server/pruef-start-ansicht.mjs @@ -98,6 +98,9 @@ const lesen = (seite) => seite.evaluate(() => ({ hinweise: [...document.querySelectorAll("#dran .dran__punkt")] .filter((z) => z.offsetParent !== null).length, hinweiseGesamt: document.querySelectorAll("#dran .dran__punkt").length, + zeichenGroesse: Math.round(document.querySelector(".kachel__zeichen")?.getBoundingClientRect().width || 0), + namenGroesse: Math.round(parseFloat(getComputedStyle(document.querySelector(".kachel__name")).fontSize) || 0), + kachelHoehe: Math.round(document.querySelectorAll(".kachel")[1]?.getBoundingClientRect().height || 0), zahlenLeer: !document.getElementById("zahlen-leer")?.hidden, zahlen: [...document.querySelectorAll(".zahl")].length, ueber: document.documentElement.scrollWidth - document.documentElement.clientWidth, @@ -148,6 +151,13 @@ for (const [breite, wie] of [[1440, "Computer"], [390, "Handy"]]) { ok(auf("LIVE-Analyse")?.zahl === "1", `der dringende Bereichspunkt (${auf("LIVE-Analyse")?.zahl})`); ok(!auf("Wissens-Bibliothek")?.zahl, "wo nichts liegt, steht keine Zahl"); + /* Die Kacheln sollen spürbar größer sein als vorher (Zeichenfeld 38px, + Name .93rem). Gemessen statt geglaubt -- "sieht größer aus" ist kein + Nachweis. */ + ok(r.zeichenGroesse >= 46, `Zeichenfeld mindestens 46px (${r.zeichenGroesse}px)`); + ok(r.namenGroesse >= 16, `Name mindestens 16px (${r.namenGroesse}px)`); + ok(r.kachelHoehe >= 80, `Kachel mindestens 80px hoch (${r.kachelHoehe}px)`); + ok(r.abgeschnitten.length === 0, `kein Text wird abgeschnitten (${r.abgeschnitten.join(" | ") || "keiner"})`); ok(r.hinweise <= 4, `höchstens vier Hinweise sichtbar (${r.hinweise} von ${r.hinweiseGesamt})`); @@ -206,6 +216,51 @@ console.log("\n=== Weniger Bewegung: keine Animation"); await kontext.close(); } +console.log("\n=== Das Licht folgt dem Zeiger"); +{ + const kontext = await browser.newContext({ viewport: { width: 1440, height: 1200 } }); + const seite = await anmelden(kontext, "admin", "CODE-DOGI-0001"); + const kachel = seite.locator(".kachel").nth(1); + const kasten = await kachel.boundingBox(); + + const ruhe = await kachel.evaluate((k) => k.style.getPropertyValue("--mx") || "(Ruhelage)"); + 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); + await seite.waitForTimeout(250); + const links = await kachel.evaluate((k) => k.style.getPropertyValue("--mx")); + + await seite.mouse.move(kasten.x + kasten.width * 0.85, kasten.y + kasten.height * 0.6); + await seite.waitForTimeout(250); + const rechts = await kachel.evaluate((k) => k.style.getPropertyValue("--mx")); + + console.log(` links: ${links} · rechts: ${rechts}`); + ok(parseFloat(links) < 40, `links steht der Fleck links (${links})`); + ok(parseFloat(rechts) > 60, `rechts steht er rechts (${rechts})`); + + /* Beim Verlassen zurück in die Ruhelage. Ohne das blieben die Kacheln + nach einer Weile alle unterschiedlich beleuchtet stehen, ohne dass + jemand wüsste, warum. */ + await seite.mouse.move(kasten.x - 250, kasten.y - 250); + await seite.waitForTimeout(250); + const danach = await kachel.evaluate((k) => k.style.getPropertyValue("--mx") || "(Ruhelage)"); + ok(danach === "(Ruhelage)", `nach dem Verlassen wieder in Ruhe (${danach})`); + await kontext.close(); +} + +console.log("\n=== Ohne feinen Zeiger bleibt es aus"); +{ + const kontext = await browser.newContext({ + viewport: { width: 390, height: 844 }, hasTouch: true, isMobile: true }); + const seite = await anmelden(kontext, "admin", "CODE-DOGI-0001"); + await seite.touchscreen.tap(200, 700).catch(() => {}); + await seite.waitForTimeout(200); + const gesetzt = await seite.evaluate(() => + [...document.querySelectorAll(".kachel")].some((k) => k.style.getPropertyValue("--mx"))); + ok(!gesetzt, "auf dem Handy wird nichts gesetzt"); + await kontext.close(); +} + await browser.close(); console.log(`\n${fehler === 0 ? "Alles in Ordnung." : fehler + " Prüfung(en) fehlgeschlagen."}`); try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ } diff --git a/workspace/assets/css/start.css b/workspace/assets/css/start.css index d47ce8b5..4a4cfeef 100644 --- a/workspace/assets/css/start.css +++ b/workspace/assets/css/start.css @@ -245,9 +245,14 @@ body.start { .gruppe:last-child { margin-bottom: 0; } .gruppe__kopf { + position: relative; display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; - margin-bottom: 12px; padding-bottom: 9px; - border-bottom: 1px solid var(--rand); + margin-bottom: 14px; padding-bottom: 10px; + /* Die Trennlinie läuft nach rechts aus, statt hart abzubrechen -- + eine durchgezogene Linie über die ganze Breite zerschneidet die + Seite, eine auslaufende gliedert sie nur. */ + border-bottom: 1px solid transparent; + border-image: linear-gradient(90deg, var(--rand-hell), transparent 70%) 1; } .gruppe__name { margin: 0; font-size: .76rem; font-weight: 700; @@ -257,7 +262,7 @@ body.start { .kacheln { list-style: none; margin: 0; padding: 0; - display: grid; gap: 10px; + display: grid; gap: 12px; grid-template-columns: repeat(3, minmax(0, 1fr)); } @@ -274,15 +279,23 @@ body.start { .kachel { --ton: var(--akzent); + /* Wo der Zeiger steht. Wird von start.js gesetzt; die Mitte ist der + Rückfallwert, damit ohne Zeiger nichts springt. */ + --mx: 50%; + --my: 0%; position: relative; - border-radius: 15px; + border-radius: 18px; background: linear-gradient(155deg, - color-mix(in srgb, var(--ton) 9%, transparent), - rgba(255, 255, 255, .022) 45%); + color-mix(in srgb, var(--ton) 10%, transparent), + rgba(255, 255, 255, .022) 48%); border: 1px solid var(--rand); + /* Die feine helle Linie an der Oberkante. Ein Pixel, kaum sichtbar -- + aber die Kachel wirkt dadurch von oben beleuchtet statt aufgeklebt. + Das ist der Unterschied zwischen "Kasten" und "Objekt". */ + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .055); overflow: hidden; - transition: border-color var(--tempo), transform var(--tempo); + transition: border-color var(--tempo), transform var(--tempo), box-shadow var(--tempo); } .kachel[data-gross="ja"] { grid-column: span 2; } @@ -290,98 +303,126 @@ body.start { zeigt -- drei Pixel reichen, um eine Kachel wiederzuerkennen. */ .kachel::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; - background: linear-gradient(180deg, var(--ton), transparent 90%); - opacity: .5; - transition: opacity var(--tempo); + background: linear-gradient(180deg, var(--ton), transparent 92%); + opacity: .55; + transition: opacity var(--tempo), width var(--tempo); } -/* Der Schimmer beim Überfahren. Liegt still auf 0 und wird nur - eingeblendet -- nichts bewegt sich, es wird nur heller. */ +/* Das Licht. Es liegt schwach auf der Kachel und FOLGT beim Überfahren + dem Zeiger -- das ist die Kleinigkeit, die man nicht sieht, sondern + erst beim Benutzen merkt. Kein Blinken, keine Bewegung des Inhalts: + Es wird nur an einer anderen Stelle heller. + + Ruhend sitzt es in der oberen linken Ecke (--my: 0%), damit die + Kacheln auch ohne Zeiger von einer Seite beleuchtet wirken. */ .kachel::after { content: ""; position: absolute; inset: 0; pointer-events: none; - background: radial-gradient(120% 130% at 0% 0%, - color-mix(in srgb, var(--ton) 16%, transparent), transparent 62%); - opacity: 0; + background: radial-gradient(130% 150% at var(--mx) var(--my), + color-mix(in srgb, var(--ton) 20%, transparent), transparent 60%); + opacity: .38; transition: opacity var(--tempo); } .kachel:hover { - border-color: color-mix(in srgb, var(--ton) 42%, var(--rand)); - transform: translateY(-1px); + border-color: color-mix(in srgb, var(--ton) 45%, var(--rand)); + transform: translateY(-2px); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, .09), + 0 10px 26px -14px color-mix(in srgb, var(--ton) 55%, transparent); } -.kachel:hover::before { opacity: 1; } +.kachel:hover::before { opacity: 1; width: 4px; } .kachel:hover::after { opacity: 1; } .kachel:has(:focus-visible) { border-color: var(--ton); } .kachel__link { position: relative; z-index: 1; - display: flex; align-items: center; gap: 12px; - padding: 14px 15px 14px 17px; + display: flex; align-items: center; gap: 14px; + padding: 18px 18px 18px 21px; color: inherit; text-decoration: none; } .kachel__link:focus-visible { - outline: 2px solid var(--ton); outline-offset: -3px; border-radius: 13px; + outline: 2px solid var(--ton); outline-offset: -4px; border-radius: 15px; } -/* Das Zeichen sitzt in einem eigenen kleinen Feld -- dadurch bekommt der - Ton eine Fläche, ohne dass die ganze Kachel farbig wird. */ +/* Das Zeichen sitzt in einem eigenen Feld -- dadurch bekommt der Ton + eine Fläche, ohne dass die ganze Kachel farbig wird. Der Verlauf und + der innere Lichtrand darin lassen es geprägt statt gemalt wirken. */ .kachel__zeichen { flex: none; display: grid; place-items: center; - width: 38px; height: 38px; border-radius: 11px; - background: color-mix(in srgb, var(--ton) 14%, transparent); - border: 1px solid color-mix(in srgb, var(--ton) 28%, transparent); - transition: background var(--tempo), border-color var(--tempo); + width: 46px; height: 46px; border-radius: 14px; + background: + linear-gradient(160deg, + color-mix(in srgb, var(--ton) 26%, transparent), + color-mix(in srgb, var(--ton) 9%, transparent)); + border: 1px solid color-mix(in srgb, var(--ton) 30%, transparent); + box-shadow: inset 0 1px 0 color-mix(in srgb, var(--ton) 32%, transparent); + transition: background var(--tempo), border-color var(--tempo), transform var(--tempo); } .kachel:hover .kachel__zeichen { - background: color-mix(in srgb, var(--ton) 24%, transparent); - border-color: color-mix(in srgb, var(--ton) 52%, transparent); + background: + linear-gradient(160deg, + color-mix(in srgb, var(--ton) 40%, transparent), + color-mix(in srgb, var(--ton) 14%, transparent)); + border-color: color-mix(in srgb, var(--ton) 58%, transparent); + transform: scale(1.04); } -.kachel[data-gross="ja"] .kachel__zeichen { width: 44px; height: 44px; border-radius: 13px; } +.kachel[data-gross="ja"] .kachel__zeichen { width: 56px; height: 56px; border-radius: 17px; } .kachel__svg { - width: 20px; height: 20px; + width: 23px; height: 23px; fill: none; stroke: var(--ton); stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; } -.kachel[data-gross="ja"] .kachel__svg { width: 23px; height: 23px; } +.kachel[data-gross="ja"] .kachel__svg { width: 28px; height: 28px; } -.kachel__text { display: grid; gap: 1px; min-width: 0; } +.kachel__text { display: grid; gap: 3px; min-width: 0; } .kachel__name { - font-size: .93rem; font-weight: 650; letter-spacing: -.005em; + font-size: 1.02rem; font-weight: 650; letter-spacing: -.012em; line-height: 1.25; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.kachel[data-gross="ja"] .kachel__name { font-size: 1.02rem; } +.kachel[data-gross="ja"] .kachel__name { font-size: 1.18rem; } .kachel__unter { - font-size: .74rem; color: var(--text-still); + font-size: .78rem; color: var(--text-still); line-height: 1.3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.kachel[data-gross="ja"] .kachel__unter { font-size: .83rem; } /* Die Zahl auf der Kachel. Sie ersetzt den Pfeil, wenn dort Arbeit liegt -- eine Kachel, die "3" sagt, ist zehnmal mehr wert als eine, die "öffnen" sagt. */ .kachel__zahl { flex: none; margin-left: auto; - min-width: 26px; padding: 3px 9px; - text-align: center; font-size: .82rem; font-weight: 700; + min-width: 32px; padding: 5px 11px; + text-align: center; font-size: .9rem; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ton); - background: color-mix(in srgb, var(--ton) 14%, transparent); - border: 1px solid color-mix(in srgb, var(--ton) 34%, transparent); + background: color-mix(in srgb, var(--ton) 15%, transparent); + border: 1px solid color-mix(in srgb, var(--ton) 36%, transparent); + box-shadow: inset 0 1px 0 color-mix(in srgb, var(--ton) 26%, transparent); border-radius: 999px; + transition: background var(--tempo), border-color var(--tempo); +} +.kachel:hover .kachel__zahl { + background: color-mix(in srgb, var(--ton) 24%, transparent); + border-color: color-mix(in srgb, var(--ton) 55%, transparent); } .kachel__zahl[data-warn="ja"] { color: var(--warn); - background: rgba(255, 154, 162, .12); - border-color: rgba(255, 154, 162, .42); + background: rgba(255, 154, 162, .13); + border-color: rgba(255, 154, 162, .44); + box-shadow: inset 0 1px 0 rgba(255, 154, 162, .28); +} +.kachel:hover .kachel__zahl[data-warn="ja"] { + background: rgba(255, 154, 162, .2); border-color: rgba(255, 154, 162, .62); } .kachel__pfeil { flex: none; margin-left: auto; - color: var(--text-still); font-size: .9rem; + color: var(--text-still); font-size: 1rem; transition: color var(--tempo), transform var(--tempo); } -.kachel:hover .kachel__pfeil { color: var(--ton); transform: translateX(3px); } +.kachel:hover .kachel__pfeil { color: var(--ton); transform: translateX(4px); } /* Gestaffelter Einlauf. Die Verzögerung kommt aus --nr, das start.js je Kachel setzt. Steht in einer no-preference-Abfrage, damit bei @@ -453,6 +494,11 @@ body.start { Steht vor den Zahlen. Zahlen sagen, WIE VIEL anliegt -- diese Liste sagt, WAS zu tun ist. Nur drei Stufen, und nur die oberste ist farbig: Waere alles hervorgehoben, waere nichts hervorgehoben. */ +/* Die Seite ist breit, damit die Kacheln Platz haben. Eine Zeile Text + ueber 1240 px zu ziehen macht sie aber nicht lesbarer, sondern + anstrengender -- Hinweise und Begruessung behalten deshalb ihr Mass. */ +.willkommen, .dran__liste, .inhalt .fuss { max-width: 940px; } + .dran { margin-bottom: 30px; } .dran__liste { display: grid; gap: 7px; margin: 0; padding: 0; list-style: none; } diff --git a/workspace/assets/js/start.js b/workspace/assets/js/start.js index 7960a5b2..ade92a9a 100644 --- a/workspace/assets/js/start.js +++ b/workspace/assets/js/start.js @@ -257,6 +257,57 @@ abschnitt.append(liste); ziel.append(abschnitt); } + lichtFolgen(ziel); + } + + /* ---------- Das Licht folgt dem Zeiger ----------------------------------- + + Die Kachel trägt einen weichen Lichtfleck. Er sitzt ruhend oben links + und wandert unter dem Zeiger mit. Kein Blinken, keine Bewegung des + Inhalts -- es wird nur an einer anderen Stelle heller. Genau das ist + der Unterschied zwischen "sieht gut aus" und "fühlt sich gut an". + + EIN Zuhörer für alle Kacheln statt siebzehn einzelner. Und in einem + Bild je Rahmen: pointermove feuert dutzendfach je Sekunde; wer bei + jedem Ereignis in den Stil schreibt, lässt den Browser das Bild + mehrfach umsonst neu berechnen. + + 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; + + 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); } /* ---------- Laden -------------------------------------------------------- */ diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index 7be3b23a..ba08b45e 100644 --- a/workspace/aufgaben.html +++ b/workspace/aufgaben.html @@ -7,9 +7,9 @@ - - - + + +
@@ -134,8 +134,8 @@ - - - + + +