diff --git a/server/helfer-deutsch.mjs b/server/helfer-deutsch.mjs new file mode 100644 index 00000000..3246d320 --- /dev/null +++ b/server/helfer-deutsch.mjs @@ -0,0 +1,41 @@ +/* ===================================================================== + ERSATZSCHREIBUNGEN ERKENNEN (16.09.2026) + + "waehrend", "Loeschen", "begruessen" -- Umlaute, die als ae/oe/ue/ss + geschrieben sind. In einem Kommentar ist das die Hausschreibweise, in + einem Satz fuer Menschen ein Fehler. Warum er entsteht und was sich + dagegen pruefen laesst, steht ausfuehrlich in pruef-deutsche-texte. + + Diese Datei importiert NICHTS. Sie ist ein Blatt, wie helfer-tag: Wer + nur wissen will, ob ein Satz sauber ist, soll dafuer keinen Server + hochfahren und keine Pruefung ausloesen -- pruef-deutsche-texte ruft + beim Laden `process.exit` auf, ein Import von dort waere toedlich. + + Und es ist der Grund, warum die Liste hier steht und nicht zweimal + irgendwo: Zwei Listen, die dasselbe entscheiden, laufen auseinander. + ===================================================================== */ + +/** Wortstuecke, die als Ersatzschreibung praktisch eindeutig sind. + * + * Klein geschrieben; verglichen wird klein gegen klein. Bewusst KEIN + * blosses "ae|oe|ue|ss": Das steht in "Feuer", "Michael", "Poesie" und + * "Wasser" voellig zu Recht. Die Liste ist ein Netz, kein Beweis -- + * sie findet das Haeufige und kann Seltenes durchlassen. */ +export const ERSATZSCHREIBUNGEN = [ + "aehnlich", "aender", "aerger", "anfaeng", "ausser", "aussen", + "begruess", "beitraeg", "bloed", "draussen", "empfaeng", "faell", + "froehl", "fuer", "gaest", "gemaess", "geraet", "groess", "gross", + "gruess", "haett", "haeng", "heiss", "hoech", "hoeh", "hoer", + "kaempf", "koenn", "koerper", "laeng", "laesst", "laeuf", "loesch", + "maessig", "massnahme", "moech", "moegl", "muess", "naechst", + "oeffn", "oefter", "plaen", "schaetz", "schliess", "schoen", "spaet", + "staerk", "stoer", "taegl", "taet", "traeg", "ueber", "uebung", + "ungefaehr", "veraend", "verlaeuf", "vorschlaeg", "waehl", "waehr", + "waere", "weiss", "woert", "zaehl", "zugaeng", "zurueck", +]; + +/** Welche Ersatzschreibungen stecken in diesem Text? Leer heisst sauber. */ +export function ersatzschreibungen(text) { + const klein = String(text || "").toLowerCase(); + return ERSATZSCHREIBUNGEN.filter((m) => klein.includes(m)); +} diff --git a/server/pruef-deutsche-texte.mjs b/server/pruef-deutsche-texte.mjs new file mode 100644 index 00000000..61ec402e --- /dev/null +++ b/server/pruef-deutsche-texte.mjs @@ -0,0 +1,184 @@ +/* ===================================================================== + TEXT FUER MENSCHEN TRAEGT UMLAUTE (16.09.2026) + + Auf dem Aufgabenbrett des Teams stand, gut lesbar, in der Erklaerung + unter der Kategorie Chat-Moderation: + + "Was im Livechat passiert, waehrend gesendet wird. Loeschen, + stummschalten, begruessen, deeskalieren." + + Vierzehn Kategorien und drei Stufen -- vierundzwanzig solcher + Stellen. Keine Pruefung hat es gesehen: Der Text war inhaltlich + richtig, der Katalog vollstaendig, die Seite gruen. Aufgefallen ist + es erst auf einem Bildschirmfoto. + + --------------------------------------------------------------------- + WARUM ES PASSIERT IST -- UND WIEDER PASSIEREN WUERDE + + Kommentare sind in diesem Haus bewusst ohne Umlaute geschrieben, und + Aenderungen an grossen Dateien laufen ueber Hilfsskripte, die an + Umlauten und Maskierungen gern scheitern. Wer in so einem Skript Text + schreibt, schreibt ihn reflexhaft in ASCII. Fuer einen Kommentar ist + das gleichgueltig. Fuer einen Satz, den ein Mensch liest, ist es ein + Fehler -- und einer, der keine Fehlermeldung erzeugt. + + Ein Warnhinweis daneben haette nicht geholfen. Das steht so in den + Hausregeln, aus einem anderen Schaden: Ein Kommentar, der vor einem + Fehler warnt, verhindert ihn nicht. Er wird beim naechsten Mal nicht + gelesen. Deshalb eine Pruefung. + + --------------------------------------------------------------------- + WAS SIE SEHEN KANN -- UND WAS NICHT + + Rechnerisch entscheiden laesst es sich NICHT. "ue" ist in "Feuer", + "Steuer", "neue" und "aktuell" voellig richtig, "ae" in "Michael", + "oe" in "Poesie", und "ss" stimmt in jedem zweiten deutschen Wort. + Ein Muster aus blossen Buchstabenfolgen wuerde entweder alles + durchlassen oder staendig falschen Alarm schlagen. + + Deshalb eine Liste von Wortstuecken, die als Ersatzschreibung + praktisch eindeutig sind ("waehr", "loesch", "begruess", "ueber"). + Sie ist ein NETZ, kein Beweis: Sie findet das Haeufige und kann + Seltenes durchlassen. Das ist ehrlich so gemeint -- eine Pruefung, + die ihre Grenze nennt, ist mehr wert als eine, die mehr verspricht, + als sie haelt. + + Vor dem Bauen gemessen: null Fehlalarme auf 174 Katalogtexten und + 28 ausgelieferten Seiten. + + --------------------------------------------------------------------- + UND WARUM DIE SKRIPTE NICHT MITGEPRUEFT WERDEN + + Ausprobiert -- und es war das Aufschlussreichste an der Sache: In den + 38 Skripten unter workspace/assets/js schlug dieselbe Liste 71 Mal + an, und KEIN EINZIGER Treffer war ein Fehler. Es waren Feldnamen + ("f-geraet"), Stilklassen ("team-karte__ueber"), Adressen + ("/workspace/api/uebersicht"), Datenschluessel ("review_faellig"). + Die muessen ASCII sein -- ein Umlaut waere dort der Fehler. + + Eine Warnung, die immer kommt, ist keine Warnung mehr. Der sichtbare + Text aus den Skripten wird deshalb dort geprueft, wo er Text ist und + kein Bezeichner: am fertig gebauten Bildschirm, in + pruef-modi-katalog. + + Aufrufen mit: node server/pruef-deutsche-texte.mjs + ===================================================================== */ + +import { readdirSync, readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { ersatzschreibungen } from "./helfer-deutsch.mjs"; + +/* Pfade am Ort DIESER Datei festmachen, nicht am Arbeitsverzeichnis. + Sonst misst die Pruefung verschiedene Dinge, je nachdem, aus welchem + Ordner sie gestartet wurde -- und findet im schlimmsten Fall gar + keine Seiten und meldet trotzdem "in Ordnung". */ +const SEITEN_ORDNER = fileURLToPath(new URL("../workspace/", import.meta.url)); + +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++; }; + +/* ======================================================================= + 1. DIE KATALOGE + ======================================================================= */ +melde(""); +melde("=== 1. Was in den Katalogen steht ==="); +const [w, v] = await Promise.all([ + import("./workspace.js"), import("./workspace-vorlagen.js"), +]); + +const texte = []; +for (const k of w.MODI_KATEGORIEN) texte.push([`Kategorie ${k.wert}`, `${k.name} ${k.text}`]); +for (const s of w.MODI_STUFEN) texte.push([`Stufe ${s.wert}`, `${s.name} ${s.text}`]); +for (const g of v.MODI_GRUPPEN) texte.push([`Gruppe ${g.schluessel}`, `${g.name} ${g.text}`]); +for (const a of v.MODI_AUFGABEN_FLACH) texte.push([`Modi ${a.schluessel}`, `${a.titel} ${a.text}`]); +for (const a of v.MODI_KATALOG) texte.push([`Agentur ${a.schluessel}`, `${a.titel} ${a.text}`]); + +/* DIE ANZAHL GEHOERT IN DIE BEDINGUNG, nicht nur in den Meldetext. + Waere der Katalog leer, haette die Schleife darunter nichts zu tun + und wuerde ein sattes Gruen melden. */ +ok(texte.length >= 150, `${texte.length} ausgelieferte Katalogtexte gefunden`); + +const schmutzig = texte.filter(([, t]) => ersatzschreibungen(t).length); +for (const [wo, t] of schmutzig) { + melde(` ${wo}: [${ersatzschreibungen(t).join(", ")}] ${t.slice(0, 70)}`); +} +ok(schmutzig.length === 0, + `keiner davon schreibt Umlaute als ae/oe/ue/ss (${schmutzig.length} Funde)`); + +/* ZWEI BINDESTRICHE SIND KEIN GEDANKENSTRICH. Derselbe Reflex aus + demselben Grund: Im Kommentar ist "--" die Hausschreibweise, auf dem + Bildschirm sind es zwei Striche, die wie ein Tippfehler aussehen. */ +const striche = texte.filter(([, t]) => t.includes("--")); +for (const [wo, t] of striche) melde(` ${wo}: ${t.slice(0, 70)}`); +ok(striche.length === 0, + `und keiner zwei Bindestriche statt eines Gedankenstrichs (${striche.length} Funde)`); + +/* ======================================================================= + 2. DER SICHTBARE TEXT DER SEITEN + ======================================================================= */ +melde(""); +melde("=== 2. Was fest auf den Seiten steht ==="); +{ + let seiten = 0, stellen = 0; + for (const datei of readdirSync(SEITEN_ORDNER)) { + if (!datei.endsWith(".html")) continue; + seiten++; + /* Kommentare und Skriptbloecke raus -- dort ist ASCII richtig. */ + const roh = readFileSync(SEITEN_ORDNER + datei, "utf8") + .replace(//g, " ") + .replace(/<(script|style)[\s\S]*?<\/\1>/g, " "); + for (const stueck of roh.split(/<[^>]*>/)) { + const t = stueck.trim(); + if (t.length < 4) continue; + const treffer = ersatzschreibungen(t); + if (treffer.length) { + stellen++; + melde(` ${datei}: [${treffer.join(", ")}] ${t.slice(0, 70)}`); + } + } + } + ok(seiten >= 20, `${seiten} ausgelieferte Seiten durchgesehen`); + ok(stellen === 0, `ihr sichtbarer Text ist frei von Ersatzschreibungen (${stellen} Funde)`); +} + +/* ======================================================================= + 3. GEGENPROBE -- KANN DIE PRUEFUNG UEBERHAUPT ROT WERDEN? + ======================================================================= */ +melde(""); +melde("=== 3. Die Gegenprobe ==="); +{ + /* WORTWOERTLICH DER TEXT, DER AM 16.09. AUF DEM BRETT STAND. Ohne + diese Zeile beweisen die Abschnitte darueber nichts: Eine Liste, + die nie anschlaegt, meldet immer "in Ordnung". */ + const alt = "Was im Livechat passiert, waehrend gesendet wird. " + + "Loeschen, stummschalten, begruessen, deeskalieren."; + const gefunden = ersatzschreibungen(alt); + ok(gefunden.length >= 3, `der alte Kategorietext schlaegt an (${gefunden.join(", ")})`); + + /* UND DIE ANDERE RICHTUNG, genauso wichtig: Der reparierte Text darf + NICHT anschlagen. Sonst waere die Liste einfach zu breit. */ + const neu = "Was im Livechat passiert, während gesendet wird. " + + "Löschen, stummschalten, begrüßen, deeskalieren."; + ok(ersatzschreibungen(neu).length === 0, "der reparierte Text nicht"); + + /* DIE HAEUFIGSTEN ECHTEN WOERTER, in denen ae/oe/ue/ss korrekt sind. + Sie stehen hier, weil genau sie eine zu grobe Liste verraten + wuerden -- und weil "Feuer" und "neue" in echten Texten vorkommen. */ + const echt = "Die neue Steuer ist teuer, das Feuer ein Abenteuer. " + + "Michael liest Poesie, das Wasser ist nass, und dass es aktuell " + + "regnet, muss uns nicht weiter stören."; + const falschAlarm = ersatzschreibungen(echt); + ok(falschAlarm.length === 0, + `echte ue/ae/oe/ss-Woerter loesen keinen Alarm aus (${falschAlarm.join(", ") || "keiner"})`); + + /* UND DIE ZWEITE GEGENPROBE zum Gedankenstrich, aus demselben Grund. */ + ok("Klar umrissen -- und erreichbar.".includes("--"), + "zwei Bindestriche werden als solche erkannt"); +} + +/* ------------------------------------------------------------------- */ +melde(""); +melde(`${geprueft} Pruefungen, ${fehler} Fehler`); +melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG"); +process.exit(fehler ? 1 : 0); diff --git a/server/pruef-modi-katalog.mjs b/server/pruef-modi-katalog.mjs index 86f7a449..35b7572d 100644 --- a/server/pruef-modi-katalog.mjs +++ b/server/pruef-modi-katalog.mjs @@ -31,6 +31,8 @@ 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"; +import { ersatzschreibungen } from "./helfer-deutsch.mjs"; +import { fileURLToPath } from "node:url"; await import("./index.js"); notbremse(180_000, "pruef-modi-katalog"); await new Promise((r) => setTimeout(r, 700)); @@ -304,12 +306,41 @@ console.log("\n=== 6. Das Brett im echten Browser ===\n"); /* ZWEI REIHEN SEIT DEM 16.09.2026: oben die Kategorien, darunter die Stufen. Getrennt gezaehlt -- eine gemeinsame Zahl haette nicht gesagt, WELCHE der beiden fehlt. */ + /* UEBER DIE BESCHRIFTUNG UNTERSCHIEDEN, nicht ueber eine + Hilfsklasse (16.09.2026). Seit die Stufenreihe genauso aussieht + wie die Kategorienreihe -- so wollte Filipe es -- gibt es im + Aussehen keinen Unterschied mehr. Den gibt es aber im SINN, und + der steht ohnehin da: in dem Wort, das ein Vorleseprogramm + ansagt. Eine Klasse nur zum Wiederfinden waere eine zweite + Wahrheit ueber dieselbe Sache. */ kategorien: [...document.querySelectorAll( - ".stufenleiter:not(.stufenleiter--fein) .stufenleiter__name")] + '[aria-label="Kategorie wählen"] .stufenleiter__name')] .map((z) => z.textContent.trim()), stufen: [...document.querySelectorAll( - ".stufenleiter--fein .stufenleiter__name")].map((z) => z.textContent.trim()), + '[aria-label="Stufe wählen"] .stufenleiter__name')] + .map((z) => z.textContent.trim()), karten: document.querySelectorAll(".vorlagenbrett__karte").length, + /* WIE VIELE ZEILEN BRAUCHT DIE FRIST? Gemessen ueber einen + Bereich um den TEXT, nicht ueber das Element. + + ERST FALSCH GEMESSEN, und die Gegenprobe hat es verraten: + `element.getClientRects()` liefert hier IMMER genau ein + Rechteck, weil ein Flex-Kind zu einem Block wird -- die + Pruefung blieb gruen, auch mit dem Fehler wieder eingebaut. + Ein Bereich ueber den Textknoten dagegen gibt pro Zeilenkasten + ein Rechteck, und genau das ist die Frage: Steht "in 2 Tagen" + auf einer Zeile oder als "in / 2 / Tagen" untereinander? */ + fristZeilen: [...document.querySelectorAll(".vorlagenbrett__frist")] + .map((z) => { + const b = document.createRange(); + b.selectNodeContents(z); + return b.getClientRects().length; + }), + /* Und wie viele Karten tragen ueberhaupt den DRITTEN Eintrag, an + dem es eng wurde. Ohne diese Zahl wuerde die Zeile darueber + auch dann gruen, wenn der enge Fall gar nicht auf dem Schirm + war. */ + engeKarten: document.querySelectorAll(".vorlagenbrett__schon").length, text: document.body.innerText, kopf: (document.querySelector(".kopfleiste .marke__text") || {}).textContent || "", })); @@ -334,9 +365,49 @@ console.log("\n=== 6. Das Brett im echten Browser ===\n"); /* Und der Seitenname dahinter steht noch da -- ihn beim Austausch zu verlieren waere lautlos passiert. */ ok(/Aufgaben/.test(bild.kopf), "der Seitenname dahinter blieb erhalten"); + /* DIE FUSSZEILE DER KARTEN (16.09.2026, auf dem Bildschirmfoto + gefunden). Bei einer schon uebernommenen Aufgabe stehen dort + drei Dinge statt zwei, und "Frist: in 2 Tagen" brach mitten im + Wort auf drei Zeilen um. Die Karten sind alle gleich breit -- + eine einzige gequetschte reicht, um den Fehler zu zeigen. */ + ok(bild.engeKarten > 0, + `${bild.engeKarten} Karten tragen den dritten Fusseintrag (der enge Fall)`); + const zuViel = bild.fristZeilen.filter((n) => n !== 1).length; + ok(bild.fristZeilen.length > 0 && zuViel === 0, + `jede der ${bild.fristZeilen.length} Fristangaben steht auf einer Zeile` + + (zuViel ? ` -- ${zuViel} umgebrochen` : "")); + + /* UMLAUTE AUF DEM FERTIGEN BILDSCHIRM (16.09.2026). + Am 16.09. stand unter jeder Kategorie eine Erklaerung in ASCII: + "Was im Livechat passiert, waehrend gesendet wird. Loeschen, + stummschalten, begruessen". Alles gruen, niemand hat es gemerkt. + + HIER und nicht im Quelltext, weil hier der Unterschied sichtbar + wird, auf den es ankommt: `innerText` ist, was ein Mensch liest. + Feldnamen, Stilklassen und Adressen -- die ASCII sein MUESSEN -- + stehen nicht drin. Im Quelltext derselben Skripte schlaegt + dieselbe Liste 71 Mal an, und kein einziges Mal zu Recht. + + Die Kataloge und die festen Seitentexte prueft + pruef-deutsche-texte, ohne Browser. */ + const ersatz = ersatzschreibungen(bild.text); + ok(bild.text.length > 400 && ersatz.length === 0, + ersatz.length + ? `Ersatzschreibungen auf dem Brett: ${ersatz.join(", ")}` + : `der Text auf dem Brett traegt Umlaute (${bild.text.length} Zeichen geprueft)`); + /* GEGENPROBE, sonst hiesse "nichts gefunden" nur, dass nichts + gesucht wurde -- etwa wenn `innerText` leer bliebe. */ + ok(ersatzschreibungen(`${bild.text} waehrend`).length === 1, + "und ein eingeschmuggeltes waehrend wuerde auffallen"); ok(konsole.length === 0, konsole.length ? `Fehler in der Konsole: ${konsole[0]}` : "keine Fehler in der Konsole"); - await seite.screenshot({ path: "pruef-modi-katalog.png", fullPage: false }); + /* DER PFAD GEHOERT AN DIESE DATEI, nicht ans Arbeitsverzeichnis. + Vorher stand hier ein blosser Dateiname: Das Bild landete dort, + von wo aus gestartet wurde, waehrend die Zeile darunter + "server/..." meldete. Man sucht es dann an der falschen Stelle + -- oder findet ein altes und haelt es fuer das neue. */ + const bildPfad = fileURLToPath(new URL("pruef-modi-katalog.png", import.meta.url)); + await seite.screenshot({ path: bildPfad, fullPage: false }); console.log(" -- Bildschirmfoto: server/pruef-modi-katalog.png"); await kontext.close(); } finally { @@ -388,8 +459,8 @@ console.log(""); } const wahl = await seite.evaluate(() => { - const reihen = [...document.querySelectorAll(".stufenleiter--fein")]; - const letzte = reihen[reihen.length - 1]; + const reihen = [...document.querySelectorAll(".stufenleiter")]; + const letzte = document.querySelector('[aria-label="Person wählen"]'); const zahl = /^[0-9]+$/; return { reihen: reihen.length, @@ -399,14 +470,19 @@ console.log(""); .every((z) => zahl.test(z.textContent.trim())), zahlen: [...(letzte?.querySelectorAll(".stufenleiter__zahl") || [])] .map((z) => z.textContent.trim()), - satz: [...document.querySelectorAll(".vorlagenbrett__stand")] + /* Der Satz steht seit dem 16.09.2026 in `stufenleiter__hilfe` + -- derselbe Baustein wie beim anderen Brett. Vorher war es + `vorlagenbrett__stand`, und das ist die Zeile fuer Zahlen. */ + satz: [...document.querySelectorAll(".stufenleiter__hilfe")] .map((z) => z.textContent.trim()) .find((t) => t.includes("Formular oben") || t.includes("gehen an")) || "", }; }); /* ZWEI FEINE REIHEN: die Stufen und die Personen. Eine einzelne Zahl haette nicht gesagt, welche der beiden fehlt. */ - ok(wahl.reihen === 2, "zwei Auswahlreihen (Stufen und Personen): " + wahl.reihen); + /* DREI REIHEN: Kategorie, Stufe, Person. Die ersten beiden sehen + jetzt aus wie drueben beim anderen Brett. */ + ok(wahl.reihen === 3, "drei Auswahlreihen (Kategorie, Stufe, Person): " + wahl.reihen); ok(wahl.namen.includes("Marina"), "der Modi steht zur Auswahl (" + wahl.namen.join(", ") + ")"); ok(!wahl.namen.includes("Tili") && !wahl.namen.includes("Schulle"), @@ -420,14 +496,14 @@ console.log(""); DIESEM Menschen landet. Ein Knopf, den niemand betaetigt hat, ist kein geprueter Knopf. */ await seite.evaluate(() => { - const reihen = [...document.querySelectorAll(".stufenleiter--fein")]; - const letzte = reihen[reihen.length - 1]; + const reihen = [...document.querySelectorAll(".stufenleiter")]; + const letzte = document.querySelector('[aria-label="Person wählen"]'); [...letzte.querySelectorAll(".stufenleiter__knopf")] .find((k) => k.textContent.includes("Marina"))?.click(); }); await seite.waitForTimeout(600); const nachWahl = await seite.evaluate(() => - [...document.querySelectorAll(".vorlagenbrett__stand")] + [...document.querySelectorAll(".stufenleiter__hilfe")] .map((z) => z.textContent.trim()).find((t) => t.includes("gehen an")) || ""); ok(nachWahl.includes("Marina"), "nach dem Klick steht es da: " + nachWahl); diff --git a/server/workspace.js b/server/workspace.js index d65c239f..4a54e0fb 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -1161,46 +1161,46 @@ const MODI_MARKE = "Team Dogi"; * Anleitung: Er wird dort gebraucht, wo man sich entscheidet. */ export const MODI_KATEGORIEN = [ { wert: "chat", name: "Chat-Moderation", - text: "Was im Livechat passiert, waehrend gesendet wird. Loeschen, " - + "stummschalten, begruessen, deeskalieren." }, + text: "Was im Livechat passiert, während gesendet wird. Löschen, " + + "stummschalten, begrüßen, deeskalieren." }, { wert: "community", name: "Community", - text: "Die Menschen ausserhalb der Sendung: wer wiederkommt, wer neu " + text: "Die Menschen außerhalb der Sendung: wer wiederkommt, wer neu " + "ist, wer lange nicht da war." }, { wert: "events", name: "Events", text: "Alles mit einem Termin: Matches, Aktionen, Geburtstage, " + "besondere Sendungen." }, { wert: "clipping", name: "Clipping", - text: "Aus einer Sendung viele Videos machen -- schneiden, " + text: "Aus einer Sendung viele Videos machen — schneiden, " + "beschriften, hochladen." }, { wert: "social", name: "Social Media", - text: "Alles ausserhalb des Livestreams: Ankuendigungen, Beitraege, " + text: "Alles außerhalb des Livestreams: Ankündigungen, Beiträge, " + "Antworten unter Videos." }, { wert: "technik", name: "Technik", - text: "Ton, Bild, Verbindung, Geraete. Meistens unsichtbar -- und " + text: "Ton, Bild, Verbindung, Geräte. Meistens unsichtbar — und " + "sofort sichtbar, wenn es fehlt." }, { wert: "planung", name: "Planung", text: "Was NOCH NICHT ist: Sendeplan, Themen, wer wann kann." }, { wert: "organisation", name: "Organisation", - text: "Was BEREITS ist, in Ordnung halten: Ablage, Listen, Zugaenge, " - + "Uebersichten." }, + text: "Was BEREITS ist, in Ordnung halten: Ablage, Listen, Zugänge, " + + "Übersichten." }, { wert: "team", name: "Team", - text: "Die Zusammenarbeit selbst: Uebergaben, Einarbeitung, " + text: "Die Zusammenarbeit selbst: Übergaben, Einarbeitung, " + "Absprachen, Vertretung." }, { wert: "kommunikation", name: "Kommunikation", - text: "Was nach aussen geht und beantwortet werden muss: " + text: "Was nach außen geht und beantwortet werden muss: " + "Nachrichten, Anfragen, Kooperationen." }, { wert: "analyse", name: "Analyse", - text: "Nachsehen statt schaetzen: Zahlen, Verlaeufe, was sich " - + "veraendert hat." }, + text: "Nachsehen statt schätzen: Zahlen, Verläufe, was sich " + + "verändert hat." }, { wert: "wachstum", name: "Wachstum", - text: "Gezielt mehr werden: neue Zuschauer, neue Kanaele, neue " + text: "Gezielt mehr werden: neue Zuschauer, neue Kanäle, neue " + "Zeiten ausprobieren." }, { wert: "branding", name: "Branding", text: "Wie es aussieht und klingt: Farben, Zeichen, Sprache, " + "Wiedererkennung." }, { wert: "sonstiges", name: "Sonstiges", - text: "Was in keine der dreizehn passt. Bleibt absichtlich klein -- " - + "wird es gross, fehlt eine Kategorie." }, + text: "Was in keine der dreizehn passt. Bleibt absichtlich klein — " + + "wird es groß, fehlt eine Kategorie." }, ]; /** Die drei Stufen einer Modi-Aufgabe. @@ -1215,12 +1215,12 @@ export const MODI_KATEGORIEN = [ export const MODI_STUFEN = [ { wert: "neu", name: "Neu dabei", text: "Die ersten Wochen. Klar umrissen, ohne Folgen, wenn etwas " - + "schiefgeht -- und jemand ist erreichbar." }, + + "schiefgeht — und jemand ist erreichbar." }, { wert: "dabei", name: "Eingearbeitet", text: "Kennt den Laden. Kann allein entscheiden, ohne vorher zu " - + "fragen, und weiss, wann man doch fragt." }, + + "fragen, und weiß, wann man doch fragt." }, { wert: "erfahren", name: "Erfahren", - text: "Traegt Verantwortung fuer andere: arbeitet ein, vertritt, " + text: "Trägt Verantwortung für andere: arbeitet ein, vertritt, " + "entscheidet im Zweifel." }, ]; /** diff --git a/workspace/app.webmanifest b/workspace/app.webmanifest index 4ab7838b..4be57ba6 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=202609161917", + "src": "/assets/img/app-symbole/workspace-192.png?v=202609162320", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-512.png?v=202609161917", + "src": "/assets/img/app-symbole/workspace-512.png?v=202609162320", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609161917", + "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609162320", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609161917", + "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609162320", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/workspace/assets/css/aufgaben.css b/workspace/assets/css/aufgaben.css index fe067f0c..239b2be7 100644 --- a/workspace/assets/css/aufgaben.css +++ b/workspace/assets/css/aufgaben.css @@ -903,13 +903,26 @@ /* Der Fuß trägt Frist und Knopf. `margin-top: auto` schiebt ihn nach unten, damit die Knöpfe aller Karten auf einer Linie stehen -- auch wenn die Texte verschieden lang sind. */ +/* DIE FUSSZEILE DARF UMBRECHEN (16.09.2026). + Vorher eine starre Zeile. Solange zwei Dinge darin standen -- Frist + und Knopf -- ging die Rechnung auf. Bei einer schon uebernommenen + Aufgabe sind es DREI (Frist, Vermerk, "Nochmal"), im Aufbauplan + sogar vier, und in einer 215 px breiten Karte wurde daraus ein + "Frist: in / 2 / Tagen" ueber drei Zeilen neben dem Vermerk. + + Keine neue feste Breite dagegen -- das waere derselbe Fehler mit + einer anderen Zahl, und beim fuenften Element wieder faellig. + `flex-wrap` bricht genau dann um, wenn der Platz nicht reicht, und + `nowrap` haelt zusammen, was ein Begriff ist: "in 2 Tagen" mitten + im Wort zu trennen sagt weniger als dieselbe Angabe eine Zeile + tiefer. */ .vorlagenbrett__fuss { display: flex; align-items: center; justify-content: space-between; - gap: 12px; margin-top: auto; padding-top: 6px; + flex-wrap: wrap; gap: 8px 12px; margin-top: auto; padding-top: 6px; } .vorlagenbrett__frist { font-size: .78rem; font-weight: 700; letter-spacing: .04em; - color: var(--text-still); + color: var(--text-still); white-space: nowrap; } @media (max-width: 620px) { .vorlagenbrett { padding: 16px 14px 18px; } @@ -966,6 +979,7 @@ .vorlagenbrett__schon { font-size: .74rem; font-weight: 600; color: var(--scout-haupt); + white-space: nowrap; } .vorlagenbrett__stand { margin: 12px 0 0; diff --git a/workspace/assets/css/start.css b/workspace/assets/css/start.css index 4d204dbc..b05cf567 100644 --- a/workspace/assets/css/start.css +++ b/workspace/assets/css/start.css @@ -7617,8 +7617,11 @@ body.start .k-leiste .feldschild::after { /* Die zweite Reihe steht dichter unter der ersten und traegt weniger Gewicht: Sie waehlt innerhalb dessen, was oben schon gewaehlt ist. */ -.stufenleiter--fein { margin: -6px 0 14px; } -.stufenleiter--fein .stufenleiter__knopf { padding: 5px 11px; font-size: .76rem; } +/* DIE DRITTE REIHE: an wen die Aufgabe geht. + Etwas kleiner als die zwei darueber, weil sie etwas anderes waehlt -- + keinen Ausschnitt des Katalogs, sondern einen Menschen. */ +.stufenleiter--wer { margin: -4px 0 12px; } +.stufenleiter--wer .stufenleiter__knopf { padding: 5px 11px; font-size: .76rem; } /* WER SCHON ETWAS LIEGEN HAT (16.09.2026). diff --git a/workspace/assets/js/aufgaben.js b/workspace/assets/js/aufgaben.js index 70c03c1e..3fb82646 100644 --- a/workspace/assets/js/aufgaben.js +++ b/workspace/assets/js/aufgaben.js @@ -1340,7 +1340,7 @@ Das laesst sich nicht rueckgaengig machen. Erledigte Aufgaben sollten normalerwe etwas liegt, bevor man klickt. */ const reihe = el('div', 'stufenleiter'); reihe.setAttribute('role', 'group'); - reihe.setAttribute('aria-label', 'Etappe wählen'); + reihe.setAttribute('aria-label', 'Kategorie wählen'); for (const g of katalog.gruppen) { const n = katalog.aufgaben.filter((v) => v.gruppe === g.schluessel).length; if (!n) continue; @@ -1355,8 +1355,19 @@ Das laesst sich nicht rueckgaengig machen. Erledigte Aufgaben sollten normalerwe } koerper.append(reihe); + /* DERSELBE BAUSTEIN WIE BEIM ANDEREN BRETT (16.09.2026). + + Filipe: "ich will dass es vom aussehen auch genau gleich + aussieht und so geil wie bei den anderen kategorien." + + Hier stand `vorlagenbrett__stand` -- das ist die Zeile fuer + ZAHLEN ("3 von 11 noch nicht uebernommen"), kleiner und mit + Ziffern in Tabellenbreite. Die Erklaerung unter der Auswahl + heisst drueben `stufenleiter__hilfe` und sieht anders aus. + Zwei Bretter, die dasselbe tun und verschieden aussehen, wirken + wie zwei verschiedene Sachen. */ const gruppe = katalog.gruppen.find((g) => g.schluessel === kGruppe); - if (gruppe?.text) koerper.append(el('p', 'vorlagenbrett__stand', gruppe.text)); + if (gruppe?.text) koerper.append(el('p', 'stufenleiter__hilfe', gruppe.text)); /* ---- Die Stufe (16.09.2026) --------------------------------------- @@ -1373,7 +1384,9 @@ Das laesst sich nicht rueckgaengig machen. Erledigte Aufgaben sollten normalerwe veraltet -- und ausgeliefert wird sie an jeden. */ const inGruppe = katalog.aufgaben.filter((v) => v.gruppe === kGruppe); if (Array.isArray(katalog.stufen) && katalog.stufen.length) { - const sr = el('div', 'stufenleiter stufenleiter--fein'); + /* VOLLE GROESSE, nicht `--fein`: Drueben ist die zweite Reihe + (die Stufen) genauso gross wie die erste. */ + const sr = el('div', 'stufenleiter'); sr.setAttribute('role', 'group'); sr.setAttribute('aria-label', 'Stufe wählen'); const alle = el('button', 'stufenleiter__knopf'); @@ -1398,9 +1411,7 @@ Das laesst sich nicht rueckgaengig machen. Erledigte Aufgaben sollten normalerwe } koerper.append(sr); const gewaehlt = katalog.stufen.find((x) => x.schluessel === kStufe); - if (gewaehlt?.text) { - koerper.append(el('p', 'vorlagenbrett__stand', gewaehlt.text)); - } + if (gewaehlt?.text) koerper.append(el('p', 'stufenleiter__hilfe', gewaehlt.text)); } /* Was schon auf dem Brett liegt -- aus den Aufgaben, die ohnehin @@ -1428,8 +1439,13 @@ Das laesst sich nicht rueckgaengig machen. Erledigte Aufgaben sollten normalerwe ist: dass etwas überfällig liegt. Nur das wird markiert. */ const zielbar = personenListe.filter((p) => p.rolle === 'modi' || p.rolle === 'hand'); if (zielbar.length && $('feld-verant') && !$('feld-verant').hidden) { + /* DIE DRITTE REIHE IST ETWAS ANDERES als die zwei darueber: Sie + waehlt keinen Ausschnitt des Katalogs, sondern einen MENSCHEN. + Deshalb bleibt sie etwas kleiner -- gleich aussehende Reihen, + die Verschiedenes tun, sind schlechter als zwei, die man + unterscheiden kann. */ koerper.append(el('p', 'feldschild', 'An wen')); - const wer = el('div', 'stufenleiter stufenleiter--fein'); + const wer = el('div', 'stufenleiter stufenleiter--wer'); wer.setAttribute('role', 'group'); wer.setAttribute('aria-label', 'Person wählen'); for (const p of zielbar) { @@ -1454,7 +1470,7 @@ Das laesst sich nicht rueckgaengig machen. Erledigte Aufgaben sollten normalerwe } koerper.append(wer); const gewaehlt = zielbar.find((p) => String(p.id) === String(kZiel)); - koerper.append(el('p', 'vorlagenbrett__stand', gewaehlt + koerper.append(el('p', 'stufenleiter__hilfe', gewaehlt ? `Übernommene Aufgaben gehen an ${gewaehlt.name}.` : 'Niemand gewählt – es gilt die Person aus dem Formular oben.')); } diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index 6ee00afc..7139f2c0 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. --> - - - - - - + + + + + + - +
@@ -282,10 +282,10 @@ - - - - - + + + + +