diff --git a/server/pruef-kachelraster.mjs b/server/pruef-kachelraster.mjs new file mode 100644 index 00000000..852cb33f --- /dev/null +++ b/server/pruef-kachelraster.mjs @@ -0,0 +1,297 @@ +/* ===================================================================== + DAS KACHELRASTER HAT KEIN LOCH (17.09.2026) + + Filipe, mit einem Bildschirmfoto der Community: "das wie es jetzt + ist ist es einfach total scheisse ... gerade einfach nur dahin + geknallt und drauf geschissen". + + Er hatte recht, und die Ursache war eine Zeile Reihenfolge. Das + Raster hat DREI Spalten, "Der Treff" ist doppelt breit -- stand aber + an DRITTER Stelle. Nach zwei normalen Kacheln war noch EINE Spalte + frei, er passte nicht und rutschte eine Reihe tiefer. Zurueck blieb + eine Luecke mitten im Raster. + + --------------------------------------------------------------------- + WIE MAN EIN LOCH MISST, OHNE ES ANZUSEHEN + + Nicht ueber Pixel und nicht ueber "sieht schief aus". Ueber eine + Rechnung, die immer stimmt: + + Belegte Zellen = Anzahl Kacheln + 1 je doppelt breiter Kachel + Kleinstmoegliche Reihenzahl = aufgerundet (Zellen / Spalten) + + Braucht das Raster MEHR Reihen als das, liegt irgendwo eine Zelle + leer, die nicht leer sein muesste -- und zwar vor der letzten Reihe. + Das ist genau die Luecke, die kaputt aussieht. Eine Luecke am ENDE + ist dagegen in Ordnung und faellt aus dieser Rechnung heraus. + + GEPRUEFT IN ZWEI LAGEN, denn die Kachelzahl haengt an der Rolle: + ein Modi moderiert und sieht dieselben acht wie DogFather, ein reiner + Zuschauer sieben. Eine Reihenfolge, die nur fuer eine Zahl aufgeht, + geht nicht auf. + + MIT GEGENPROBE: Dieselbe Rechnung wird auf eine absichtlich falsche + Reihenfolge angewandt (breite Kachel an dritter Stelle). Sie MUSS + ein Loch melden -- sonst misst sie nichts. + + Aufrufen mit: node server/pruef-kachelraster.mjs + Bild: SCHIRM=1 node server/pruef-kachelraster.mjs + ===================================================================== */ + +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { portMussFreiSein } from "./helfer-port.mjs"; + +const PORT = await portMussFreiSein(4479, "pruef-kachelraster"); +const ordner = mkdtempSync(join(tmpdir(), "ws-raster-")); +process.env.WORKSPACE_DB = join(ordner, "workspace.db"); +process.env.PORT = String(PORT); +process.env.SITE_ACCESS_SECRET = "lokaler-test"; +process.env.SITE_ACCESS_CODE_DOGI = "DOGI-TEST-1"; +process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; + +const express = (await import("express")).default; +const ec = express.response.cookie; +express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; +await import("./index.js"); +notbremse(180_000, "pruef-kachelraster"); +await new Promise((r) => setTimeout(r, 700)); +const BASIS = `http://127.0.0.1:${PORT}`; + +process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); }); +process.on("unhandledRejection", (f) => { console.error("ABSTURZ (Versprechen):", f); process.exit(7); }); + +let fehler = 0, geprueft = 0; +const melde = (t) => console.log(t); +const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; + +const { DatabaseSync } = await import("node:sqlite"); +const { scryptSync, randomBytes, createHmac } = await import("node:crypto"); +const d = new DatabaseSync(process.env.WORKSPACE_DB); +const jetzt = new Date().toISOString(); +const schluessel = randomBytes(32).toString("hex"); +d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)" + + " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING") + .run("code_kennung_schluessel", schluessel, jetzt); +function anlegen(name, rolle, code) { + const salt = randomBytes(16).toString("hex"); + const hash = scryptSync(code, salt, 64, + { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex"); + d.prepare("INSERT INTO personen" + + " (name, rolle, code_hash, code_salt, code_n, code_kennung, aktiv, erstellt)" + + " VALUES (?,?,?,?,?,?,1,?)") + .run(name, rolle, hash, salt, 32768, + createHmac("sha256", schluessel).update(code).digest("hex"), jetzt); +} +anlegen("Filipe", "admin", "CODE-DOGI-0001"); +anlegen("Marina", "modi", "CODE-MODI-0001"); +d.close(); + +async function anmelden(kachel, code) { + const a = await fetch(BASIS + "/workspace/api/anmelden", { + method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ rolle: kachel, code }), + }); + return (a.headers.getSetCookie?.() || []).map((z) => z.split(";")[0]).join("; "); +} + +/* Die Rechnung steht hier und nicht im Browser: So laesst sie sich mit + erfundenen Zahlen gegenpruefen, ohne eine Seite zu bauen. */ +function loecher({ spalten, kacheln, reihen }) { + const zellen = kacheln.reduce((n, k) => n + (k.breit ? 2 : 1), 0); + const minimum = Math.ceil(zellen / spalten); + return { zellen, minimum, reihen, loch: reihen > minimum }; +} + +const { chromium } = await import( + "file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); +const browser = await chromium.launch(); + +async function messen(kachel, code, wer) { + const keks = await anmelden(kachel, code); + const kontext = await browser.newContext({ viewport: { width: 1280, height: 1000 } }); + await kontext.addCookies(keks.split("; ").map((k) => { + const [name, ...rest] = k.split("="); + return { name, value: rest.join("="), domain: "127.0.0.1", path: "/" }; + })); + const seite = await kontext.newPage(); + const konsole = []; + seite.on("console", (e) => { if (e.type() === "error") konsole.push(e.text()); }); + await seite.goto(`${BASIS}/workspace/start.html`, { waitUntil: "networkidle" }); + await seite.waitForSelector(".kacheln .kachel", { timeout: 15_000 }); + await seite.waitForTimeout(500); + + const daten = await seite.evaluate(() => { + /* DIE COMMUNITY-GRUPPE finden -- ueber ihre Ueberschrift, nicht + ueber eine Position. Eine Position aendert sich, sobald jemand + eine Gruppe dazwischenschiebt. */ + const gruppen = [...document.querySelectorAll(".kacheln")]; + const treffer = gruppen.find((g) => { + const kopf = g.previousElementSibling; + return kopf && /COMMUNITY/i.test(kopf.textContent || ""); + }) || gruppen[gruppen.length - 1]; + const stil = getComputedStyle(treffer); + const spalten = stil.gridTemplateColumns.split(" ").filter(Boolean).length; + const kacheln = [...treffer.querySelectorAll(".kachel")].map((k) => ({ + name: k.querySelector(".kachel__name")?.textContent?.trim() || "", + breit: k.dataset.gross === "ja", + oben: Math.round(k.getBoundingClientRect().top), + /* DAS ZEICHEN HAT KEINEN NAMEN IM DOM -- es wird als SVG + gezeichnet. Als Fingerabdruck dient deshalb der Pfad selbst: + Zwei Kacheln mit demselben Schild haben denselben Pfad, und + genau das ist die Frage. */ + zeichen: [...(k.querySelectorAll(".kachel__svg path") || [])] + .map((z) => z.getAttribute("d") || "").join("|").slice(0, 120), + /* Traegt die Kachel eine sichtbare Schiene in ihrem Ton? */ + schiene: getComputedStyle(k, "::before").backgroundImage, + ton: getComputedStyle(k).getPropertyValue("--ton").trim(), + })); + const reihen = new Set(kacheln.map((k) => k.oben)).size; + return { spalten, kacheln, reihen }; + }); + + if (process.env.SCHIRM) { + /* ERST HINSCHEUEN, DANN FOTOGRAFIEREN. Die Kacheln blenden sich + beim Hereinscrollen ein; ein Vollbild-Foto von oben zeigt sie + deshalb als leere Flaeche -- ich habe das Bild einmal fuer den + Beweis gehalten, dass nichts da ist, waehrend die Messung + daneben acht Kacheln zaehlte. */ + await seite.evaluate(() => { + const gruppen = [...document.querySelectorAll(".kacheln")]; + const treffer = gruppen.find((g) => { + const kopf = g.previousElementSibling; + return kopf && /COMMUNITY/i.test(kopf.textContent || ""); + }) || gruppen[gruppen.length - 1]; + treffer.scrollIntoView({ block: "center" }); + }); + await seite.waitForTimeout(1200); + const pfad = fileURLToPath(new URL(`pruef-kachelraster-${wer}.png`, import.meta.url)); + await seite.screenshot({ path: pfad, fullPage: false }); + } + await kontext.close(); + return { ...daten, konsole }; +} + +try { + /* ===================================================================== + 1. DOGFATHER -- ACHT KACHELN + ===================================================================== */ + melde(""); + melde("=== 1. DogFather sieht acht ==="); + { + const m = await messen("admin", "CODE-DOGI-0001", "dogfather"); + ok(m.kacheln.length === 8, `${m.kacheln.length} Community-Kacheln`); + ok(m.spalten === 3, `drei Spalten (${m.spalten})`); + /* DIE BREITE KACHEL STEHT VORNE -- das ist die eigentliche + Reparatur. Ohne diese Zeile waere nicht geprueft, WARUM kein + Loch da ist; bei acht Kacheln ginge die Rechnung zufaellig auch + mit ihr an anderer Stelle auf. */ + const breitAn = m.kacheln.findIndex((k) => k.breit); + ok(breitAn === 0, + `die doppelt breite Kachel steht an erster Stelle (${breitAn === 0 + ? m.kacheln[0].name : "Platz " + (breitAn + 1)})`); + + const r = loecher(m); + ok(!r.loch, + `kein Loch: ${r.zellen} Zellen auf ${m.spalten} Spalten = ${r.minimum} Reihen, ` + + `gemessen ${r.reihen}`); + + /* JEDES ZEICHEN GENAU EINMAL. Vorher trugen "Regeln & Hilfe" und + "Meldungen & Massnahmen" beide denselben Schild -- nebeneinander + und nicht zu unterscheiden. */ + const zeichen = m.kacheln.map((k) => k.zeichen).filter(Boolean); + const doppelt = zeichen.filter((z, i) => zeichen.indexOf(z) !== i); + ok(zeichen.length === m.kacheln.length && doppelt.length === 0, + doppelt.length ? `Zeichen doppelt: ${doppelt.join(", ")}` + : `jedes der ${zeichen.length} Zeichen kommt genau einmal vor`); + + /* DIE SCHIENE. Sie war beschrieben und nicht gebaut: `.kachel::before` + hatte nur einen Uebergang und keinen Inhalt. Ohne sie trug den Ton + nur ein Verlauf, der unter dem Buehnenbild verschwindet. */ + const ohneSchiene = m.kacheln.filter( + (k) => !k.schiene || k.schiene === "none"); + ok(ohneSchiene.length === 0, + ohneSchiene.length ? `${ohneSchiene.length} Kacheln ohne Schiene` + : "jede Kachel traegt ihre farbige Schiene"); + + /* UND DIE TOENE SIND VERSCHIEDEN. Sonst waere eine sichtbare + Schiene siebenmal dieselbe Farbe -- schlimmer als keine. */ + const toene = new Set(m.kacheln.map((k) => k.ton).filter(Boolean)); + ok(toene.size === m.kacheln.length, + `${toene.size} verschiedene Toene bei ${m.kacheln.length} Kacheln`); + + ok(m.konsole.length === 0, + m.konsole.length ? `Konsole: ${m.konsole[0]}` : "keine Fehler in der Konsole"); + } + + /* ===================================================================== + 2. EIN MODI -- SIEBEN KACHELN + ===================================================================== */ + melde(""); + melde("=== 2. Ein Modi sieht dieselben acht ==="); + { + const m = await messen("creator", "CODE-MODI-0001", "modi"); + /* ACHT, NICHT SIEBEN -- und das war MEIN Denkfehler beim Schreiben + dieser Pruefung, nicht der des Codes: Ein Modi moderiert, also + braucht er "Meldungen & Massnahmen". Die Liste im Quelltext sagt + es deutlich (MODI_MIT_TREFF endet auf TREFF_MODERATION_KACHEL). + Der reine Zuschauer sieht sieben; an den kommt eine Pruefung + ohne Gastzugang nicht heran, deshalb steht der Fall unten in der + Rechnung. */ + ok(m.kacheln.length === 8, `${m.kacheln.length} Community-Kacheln`); + const r = loecher(m); + /* SIEBEN KACHELN, EINE DAVON BREIT = ACHT ZELLEN auf drei Spalten = + drei Reihen. Die letzte Reihe hat eine freie Zelle -- die ist + erlaubt, sie liegt am Ende. */ + ok(!r.loch, + `kein Loch: ${r.zellen} Zellen = ${r.minimum} Reihen, gemessen ${r.reihen}`); + ok(m.konsole.length === 0, + m.konsole.length ? `Konsole: ${m.konsole[0]}` : "keine Fehler in der Konsole"); + } + + /* ===================================================================== + 3. GEGENPROBE -- FINDET DIE RECHNUNG UEBERHAUPT EIN LOCH? + ===================================================================== */ + melde(""); + melde("=== 3. Die Gegenprobe ==="); + { + /* DIE ALTE REIHENFOLGE, nachgestellt: breite Kachel an dritter + Stelle. Sie belegt dann Zellen 3+4, Zelle 3 der ersten Reihe + bleibt leer, und es braucht eine Reihe mehr. */ + const alt = loecher({ + spalten: 3, reihen: 4, + kacheln: [{}, {}, { breit: true }, {}, {}, {}, {}, {}], + }); + ok(alt.loch, `die alte Reihenfolge meldet ein Loch (${alt.reihen} statt ${alt.minimum})`); + + /* UND DIE ANDERE RICHTUNG: Eine saubere Anordnung darf NICHT + anschlagen, sonst waere die Rechnung einfach immer rot. */ + const gut = loecher({ + spalten: 3, reihen: 3, + kacheln: [{ breit: true }, {}, {}, {}, {}, {}, {}, {}], + }); + ok(!gut.loch, `die neue Reihenfolge nicht (${gut.reihen} = ${gut.minimum})`); + + /* Und eine Luecke am ENDE gilt ausdruecklich nicht als Loch. */ + const ende = loecher({ spalten: 3, reihen: 3, kacheln: [{ breit: true }, {}, {}, {}, {}, {}, {}] }); + ok(!ende.loch, `eine freie Zelle am Ende ist kein Loch (${ende.zellen} Zellen, ${ende.reihen} Reihen)`); + + /* DER REINE ZUSCHAUER sieht sieben Kacheln ohne die Moderation. + An ihn kommt diese Pruefung nicht heran (kein Gastzugang), also + wird wenigstens die Rechnung fuer seinen Fall gefuehrt -- und + sie ist derselbe Fall wie eine Zeile darueber. */ + ok(ende.zellen === 8 && ende.minimum === 3, + "auch die Sicht eines Zuschauers (7 Kacheln) geht auf drei Reihen auf"); + } +} finally { + await browser.close(); +} + +melde(""); +melde(`${geprueft} Pruefungen, ${fehler} Fehler`); +melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG"); +try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest */ } +process.exit(fehler ? 1 : 0); diff --git a/server/pruef-treff.mjs b/server/pruef-treff.mjs index de2675d6..c286d0e4 100644 --- a/server/pruef-treff.mjs +++ b/server/pruef-treff.mjs @@ -302,7 +302,20 @@ let namen = []; Abschriften derselben sieben Namen waeren zwei Gelegenheiten, einen davon zu aendern und den anderen zu vergessen. */ namen = (j.bereiche || []).map((k) => k.name); - ok(namen.join(" | ") === "Anschlagbrett | Was ansteht | Der Treff | Wunschliste | Highlights | Regeln & Hilfe | Mitmachen", + /* DIE REIHENFOLGE IST TEIL DER AUSSAGE, nicht Zufall (17.09.2026). + "Der Treff" steht seit heute VORNE, und zwar aus zwei Gruenden: + + 1. Das Raster hat drei Spalten, seine Kachel ist doppelt breit. + An dritter Stelle passte sie nicht mehr in die erste Reihe und + riss ein Loch hinein -- genau das, was Filipe auf dem + Bildschirmfoto gesehen hat. Eine breite Kachel gehoert an den + Anfang; steht sie hinten, faellt die Luecke in die Mitte. + 2. Der Treff IST das Herz dieses Bereichs. Die Reihenfolge + stimmt jetzt auch inhaltlich. + + Wer sie wieder aendert, aendert beides. `pruef-kachelraster` + rechnet zusaetzlich nach, ob daraus ein Loch entsteht. */ + ok(namen.join(" | ") === "Der Treff | Anschlagbrett | Was ansteht | Wunschliste | Highlights | Regeln & Hilfe | Mitmachen", `und zwar diese: ${namen.join(", ")}`); ok((j.bereiche || []).every((k) => k.gruppe === "Community"), "alle in der Gruppe „Community“"); diff --git a/server/workspace.js b/server/workspace.js index 66e3d94b..ea7f443f 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -895,13 +895,41 @@ const TREFF_GRUPPE = { gruppeUnter: "Der Treff \u2013 der Raum f\u00fcr die Zuschauer", }; +/* DIE BREITE KACHEL STEHT VORNE (17.09.2026). + + Filipe, mit einem Bildschirmfoto: "das wie es jetzt ist ist es + einfach total scheisse ... gerade einfach nur dahin geknallt". + + Er hatte recht, und die Ursache stand genau hier. Das Raster hat + DREI Spalten, "Der Treff" ist doppelt breit (`grid-column: span 2`) + -- stand aber an DRITTER Stelle. Nach zwei normalen Kacheln war noch + EINE Spalte frei, er passte nicht hinein und rutschte eine Reihe + tiefer. Zurueck blieb ein Loch mitten im Raster: + + Reihe 1 [Anschlagbrett] [Was ansteht] ....... LUECKE + Reihe 2 [ Der Treff (2) ] [Wunschliste] + Reihe 3 [Highlights] [Regeln] [Mitmachen] + Reihe 4 [Meldungen] ...... LUECKE ...... LUECKE + + DIE REGEL, und sie gilt fuer jedes Raster im Haus: Eine breite + Kachel gehoert an den ANFANG. Steht sie hinten, faellt die Luecke in + die MITTE -- und eine Luecke in der Mitte sieht kaputt aus, eine am + Ende sieht grosszuegig aus. + + Jetzt fuellen Treff (2) plus Anschlagbrett die erste Reihe, die + naechsten drei die zweite, der Rest die dritte. Fuer das Team mit + der achten Kachel geht es genau auf: drei mal drei. + + UND ES IST AUCH INHALTLICH RICHTIG. Der Treff ist das Herz dieses + Bereichs -- dass er vorne steht, ist keine Notloesung fuer das + Raster, sondern die Reihenfolge, die ohnehin stimmt. */ const TREFF_BEREICHE = [ + { ...TREFF_GRUPPE, name: "Der Treff", unter: "Hallo sagen, fragen, loben", + zeichen: "community", ton: 34, gross: true, ziel: "bereich.html?b=treff", szene: "lounge" }, { ...TREFF_GRUPPE, name: "Anschlagbrett", unter: "Was gerade gilt", zeichen: "reports", ton: 33, ziel: "bereich.html?b=anschlag", szene: "halle" }, { ...TREFF_GRUPPE, name: "Was ansteht", unter: "Die n\u00e4chsten Streams und Events", zeichen: "kalender", ton: 31, ziel: "bereich.html?b=ansteht", szene: "skyline" }, - { ...TREFF_GRUPPE, name: "Der Treff", unter: "Hallo sagen, fragen, loben", - zeichen: "community", ton: 34, gross: true, ziel: "bereich.html?b=treff", szene: "lounge" }, { ...TREFF_GRUPPE, name: "Wunschliste", unter: "Was ihr euch w\u00fcnscht \u2013 und wer mitwill", zeichen: "content", ton: 30, ziel: "bereich.html?b=wunsch", szene: "portal" }, { ...TREFF_GRUPPE, name: "Highlights", unter: "Eure Clips und Bilder", @@ -929,7 +957,17 @@ const TREFF_BEREICHE = [ const TREFF_MODERATION_KACHEL = { ...TREFF_GRUPPE, name: "Meldungen & Ma\u00dfnahmen", unter: "Was gemeldet wurde \u2013 und was daraus wurde", - zeichen: "schutz", ton: 36, ziel: "treff-moderation.html", szene: "studio", + /* EIGENES ZEICHEN, NICHT DASSELBE WIE "REGELN & HILFE" (17.09.2026). + + Hier stand zweimal `schutz` -- zwei Kacheln nebeneinander mit + demselben Schild. Auf dem Bildschirmfoto sind sie nicht zu + unterscheiden, und genau daran erkennt man eine Sammlung, die + niemand als Ganzes angesehen hat. + + `startcheck` ist eine abgehakte Liste und sagt den Unterschied: + Bei "Regeln & Hilfe" steht, was GILT. Hier steht, was daraus + WURDE. */ + zeichen: "startcheck", ton: 36, ziel: "treff-moderation.html", szene: "studio", }; /* Welche Bretter zum Treff gehoeren -- ABGELEITET aus den Kacheln, nicht diff --git a/workspace/app.webmanifest b/workspace/app.webmanifest index 42183382..71b98e42 100644 --- a/workspace/app.webmanifest +++ b/workspace/app.webmanifest @@ -13,25 +13,25 @@ "theme_color": "#06090f", "icons": [ { - "src": "/assets/img/app-symbole/workspace-192.png?v=202609170300", + "src": "/assets/img/app-symbole/workspace-192.png?v=202609171020", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-512.png?v=202609170300", + "src": "/assets/img/app-symbole/workspace-512.png?v=202609171020", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609170300", + "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609171020", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609170300", + "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609171020", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/workspace/assets/css/start.css b/workspace/assets/css/start.css index b05cf567..e9117b12 100644 --- a/workspace/assets/css/start.css +++ b/workspace/assets/css/start.css @@ -5029,9 +5029,49 @@ body[data-fremde-sicht="ja"]::after { opacity: .58; transition: opacity var(--tempo), background-position .55s cubic-bezier(.35, 0, .2, 1); } +/* DIE SCHIENE — und sie war nicht da (17.09.2026). + + Filipe, mit einem Bildschirmfoto der Community: „das wie es jetzt + ist ist es einfach total scheisse". Beim Nachmessen: Die sieben + Bereiche HABEN klar verschiedene Töne (#cc9451 orange, #668e6e grün, + #cc92c6 rosa, #656a9e blau …) — auf dem Schirm kam davon fast + nichts an. + + Der Grund stand direkt daneben. Die Regeln `.kachel:hover::before` + und der Kommentar darüber sprechen ausführlich von einer Schiene, + die „heller wird und weiter in die Platte strahlt" — nur hatte + `.kachel::before` ausser einem Übergang KEINEN Inhalt. Das Element + wurde beim Umbau am 07.09. entfernt, seine Hover-Regeln blieben + stehen. Seither trug den Ton nur noch ein Verlauf, der auf den + obersten 12 % der Platte bei 30 % anfängt und bei 58 % verschwunden + ist — unter einem Bühnenbild reicht das nicht. + + Das hier ist deshalb kein neuer Einfall, sondern das Wiedereinsetzen + dessen, womit der Rest der Datei ohnehin rechnet. + + AUGENSCHONEND: drei Pixel, oben, nach rechts auslaufend. Ein + durchgezogener Balken wirkt wie ein Etikett, ein auslaufender wie + Licht — dieselbe Überlegung wie bei den Spalten des Aufgabenbretts. + Nicht die Fläche einfärben: Farbe an der Kante unterscheidet, + Farbe auf der Fläche blendet. */ .kachel::before { + content: ""; + position: absolute; left: 0; right: 0; top: 0; height: 3px; + z-index: 2; pointer-events: none; + border-radius: 16px 16px 0 0; + opacity: .82; + background: linear-gradient(90deg, + var(--ton), + color-mix(in srgb, var(--ton) 38%, transparent) 58%, + color-mix(in srgb, var(--ton) 6%, transparent) 88%, + transparent); transition: opacity var(--tempo), width var(--tempo), box-shadow var(--tempo); } +/* Wer weniger Bewegung will, bekommt sie trotzdem in Farbe -- die + Schiene ist Information, keine Animation. */ +@media (prefers-reduced-motion: reduce) { + .kachel::before { transition: none; } +} /* ANHEBEN STATT AUFLEUCHTEN. Beim Überfahren wird die Kachel nicht heller, sondern sie kommt näher: Sie steigt zwei Pixel, ihr Schatten diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index 1f8b2555..188904e6 100644 --- a/workspace/aufgaben.html +++ b/workspace/aufgaben.html @@ -12,19 +12,19 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - + + + + + + - +
@@ -291,10 +291,10 @@ - - - - - + + + + +