diff --git a/server/pruef-befinden.mjs b/server/pruef-befinden.mjs index 022d1497..18a1f660 100644 --- a/server/pruef-befinden.mjs +++ b/server/pruef-befinden.mjs @@ -366,6 +366,145 @@ melde("=== 6. Im Browser ==="); await browser.close(); } +/* ======================================================================= + 7. WAS DEIN VERLAUF SAGT + + Filipe: „mach mir auch eine analyse, mach eine perfektion draus. eine + professionelle auch mit infos und so aus aller welt was tiktok + angeht." + + Eine Auswertung über das eigene Befinden ist das Empfindlichste, was + dieses Haus rechnet. Also dieselben drei Fragen wie beim Verlauf: + Sagt sie das Richtige? Sagt sie es nur IHR? Und sagt sie gar nichts, + wenn sie noch nichts sagen kann? + ======================================================================= */ +melde(""); +melde("=== 7. Die Analyse ==="); +{ + const holen = async (keks, host) => + json(await ruf("/workspace/api/befinden", host, "GET", { cookie: keks })); + + /* Stand nach Abschnitt 3: zwei Runden. In der zweiten hakt der erste + Punkt, vorher lief er -- das ist „neu", noch kein Muster. */ + const d = await holen(kFrida, CREW); + const erste = d.punkte[0].schluessel; + ok(d.analyse?.moeglich === true, `mit ${d.analyse?.runden} Runden gibt es eine Analyse`); + ok(d.analyse.zeilen.length === d.punkte.length, + `sie geht alle ${d.analyse.zeilen.length} Fragen durch, nicht nur die schlechten`); + const z1 = d.analyse.zeilen.find((z) => z.schluessel === erste); + ok(z1.art === "neu", `der frisch hakende Punkt gilt als neu, nicht als Muster (${z1.art})`); + ok(/noch kein Muster/i.test(d.analyse.satz), + `und der Satz oben sagt genau das: „${d.analyse.satz.slice(0, 60)}…“`); + + /* DIE EINORDNUNG IST DER TEIL, DER „AUS ALLER WELT" HEISST. Sie steht + bei JEDER Zeile -- auch bei denen, die laufen. */ + ok(d.analyse.zeilen.every((z) => z.welt && z.welt.length > 40), + "zu jeder der sechs Fragen steht ein belegter Befund"); + ok(new Set(d.analyse.zeilen.map((z) => z.welt)).size === d.punkte.length, + "und jeder ist ein anderer — kein Satz, der zu allen passen würde"); + /* „Was hilft" NUR dort, wo es etwas zu tun gibt. */ + ok(d.analyse.zeilen.filter((z) => z.hilft).length === 1, + `ein Rat steht nur an der Stelle, an der es hakt (${d.analyse.zeilen.filter((z) => z.hilft).length})`); + + /* ---- Und jetzt dasselbe ein zweites Mal: daraus wird ein Muster ---- */ + for (const p of d.punkte) { + await ruf(`/workspace/api/entwicklung/mich/${p.schluessel}`, CREW, "PUT", + { cookie: kFrida }, p.schluessel === erste + ? { stufe: "hakt", anlass: "Immer noch dasselbe." } : { stufe: "laeuft" }); + } + const dritte = await ruf("/workspace/api/befinden/runde", CREW, "POST", { cookie: kFrida }, {}); + ok(dritte.code === 201, `eine dritte Runde (${dritte.code})`); + + const n = await holen(kFrida, CREW); + const z2 = n.analyse.zeilen.find((z) => z.schluessel === erste); + ok(z2.art === "dauerhaft", `jetzt ist es ein Muster (${z2.art})`); + ok(z2.runden === modul.MUSTER_AB, `über ${z2.runden} Runden`); + ok(/kein schlechter Tag/i.test(n.analyse.satz), + `und der Satz sagt, worauf es ankommt: „${n.analyse.satz.slice(0, 64)}…“`); + /* DIE REIHENFOLGE MACHT DIE AUSSAGE -- das Muster gehört nach oben. + Sortiert wird im Browser; hier wird geprüft, dass die Angabe dafür + überhaupt mitkommt. */ + ok(n.analyse.zahlen.dauerhaft === 1 && n.analyse.zahlen.neu === 0, + `die Zahlen daneben stimmen (${JSON.stringify(n.analyse.zahlen)})`); + + /* ---- Sie gehört ihr allein ---- */ + const seine = await holen(kDogi, CREW); + ok(seine.analyse?.moeglich === false, + "DogFather bekommt seine eigene — und die ist leer"); + ok(!JSON.stringify(seine).includes("Immer noch dasselbe"), + "nichts von ihr steht darin"); + + /* ---- Und ohne zweite Runde gibt es sie gar nicht ---- */ + ok(seine.analyse.ab === modul.MUSTER_AB, + `sie sagt, ab wann es sie gibt (ab ${seine.analyse.ab} Runden)`); + ok(Array.isArray(seine.analyse.zeilen) && seine.analyse.zeilen.length === 0, + "und liefert bis dahin keine einzige Zeile"); + + /* ---- Im Protokoll steht weiterhin nichts davon ---- */ + const db = oeffne(); + const spuren = db.prepare("SELECT aktion, detail FROM protokoll").all(); + db.close(); + ok(!spuren.some((x) => /analyse/i.test(x.aktion || "")), + "eine Analyse ist kein Vorgang — sie steht in keinem Protokoll"); + ok(!spuren.some((x) => /Immer noch dasselbe/.test(x.detail || "")), + "und ihr Anlass erst recht nicht"); +} + +/* ======================================================================= + 8. DIE ANALYSE AUF DEM BILDSCHIRM + ======================================================================= */ +melde(""); +melde("=== 8. Die Analyse im Browser ==="); +{ + const { chromium } = await import( + "file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); + const browser = await chromium.launch(); + const kontext = await browser.newContext({ viewport: { width: 1280, height: 1000 } }); + await kontext.addCookies(kFrida.split("; ").filter(Boolean).map((teil) => { + const i = teil.indexOf("="); + return { name: teil.slice(0, i), value: teil.slice(i + 1), domain: "127.0.0.1", path: "/" }; + })); + const seite = await kontext.newPage(); + const konsole = []; + seite.on("console", (m) => { if (m.type() === "error" && !/favicon|ERR_/i.test(m.text())) konsole.push(m.text()); }); + seite.on("pageerror", (e) => konsole.push("Absturz: " + e.message)); + + await seite.goto(`http://127.0.0.1:${PORT}/workspace/befinden.html`, { waitUntil: "networkidle" }); + await seite.waitForTimeout(1000); + + const a = await seite.evaluate(() => ({ + da: document.getElementById("analyse-block")?.hidden === false, + satz: document.getElementById("analyse-satz")?.textContent.trim() || "", + zeilen: document.querySelectorAll(".b-analyse__zeile").length, + ersteMarke: document.querySelector(".b-analyse__zeile")?.dataset.ton || "", + welt: document.querySelectorAll(".b-analyse__welt").length, + hilft: document.querySelectorAll(".b-analyse__hilft").length, + quellen: [...document.querySelectorAll(".b-analyse__quelle-hin")].map((x) => x.href), + blank: [...document.querySelectorAll(".b-analyse__quelle-hin")] + .every((x) => x.rel.includes("noopener")), + })); + ok(a.da, "die Analyse steht auf der Seite"); + ok(a.zeilen === 6, `mit allen sechs Fragen (${a.zeilen})`); + /* DAS MUSTER STEHT OBEN. Alphabetisch oder in Katalogreihenfolge + müsste man es suchen -- und wer sucht, liest es beim zweiten Mal + nicht mehr. */ + ok(a.ersteMarke === "hakt", + `und das, was seit mehreren Runden hakt, steht zuoberst (${a.ersteMarke})`); + ok(a.welt === 6, `zu jeder Zeile die Einordnung (${a.welt})`); + ok(a.hilft === 1, `ein Rat, genau dort, wo es hakt (${a.hilft})`); + ok(a.quellen.length === 3, `${a.quellen.length} Quellen stehen darunter`); + ok(a.quellen.some((x) => /tiktok/i.test(x)), + "darunter eine zu TikTok — danach hatte Filipe ausdrücklich gefragt"); + ok(a.blank, "die Verweise öffnen ohne Zugriff aufs eigene Fenster (noopener)"); + if (process.env.SCHIRM) { + await seite.locator("#analyse-block").screenshot( + { path: "C:/Users/qciga/Documents/Obelix/DogiHompage/blick.png" }); + } + ok(konsole.length === 0, + konsole.length ? "Fehler im Browser: " + konsole[0].slice(0, 90) : "keine Fehler im Browser"); + await browser.close(); +} + /* ------------------------------------------------------------------- */ melde(""); melde(`${geprueft} Pruefungen, ${fehler} Fehler`); diff --git a/server/workspace-befinden.js b/server/workspace-befinden.js index f700736a..88463e01 100644 --- a/server/workspace-befinden.js +++ b/server/workspace-befinden.js @@ -191,15 +191,24 @@ befindenRouter.get("/workspace/api/befinden", angemeldet, (req, res) => { zeilen.filter((z) => z.runde === r).map((z) => [z.schluessel, z.stufe])), })); + const punkte = (ENTWICKLUNG_PUNKTE[b.schluessel] || []).map((pt) => ({ + ...pt, meins: meine.get(pt.schluessel) || null, + })); + res.json({ block: b, stufen: ENTWICKLUNG_STUFEN, rhythmus_tage: RHYTHMUS_TAGE, rhythmus: rhythmus(req.person.id), - punkte: (ENTWICKLUNG_PUNKTE[b.schluessel] || []).map((pt) => ({ - ...pt, meins: meine.get(pt.schluessel) || null, - })), + punkte, verlauf, + /* DIE ANALYSE KOMMT MIT, sie holt sich niemand extra. + + Kein eigener Weg: Sie wird aus demselben Verlauf gerechnet, der + hier ohnehin schon steht. Ein zweiter Weg waere eine zweite + Stelle, an der die Rechte stimmen muessen -- und die zweite ist + immer die, die etwas durchlaesst. */ + analyse: analyseVon(verlauf, punkte), }); } catch (f) { console.error("[befinden] Lesen:", f?.message); @@ -253,3 +262,220 @@ befindenRouter.post("/workspace/api/befinden/runde", express.json({ limit: "8kb" res.status(503).json({ fehler: "nicht_verfuegbar" }); } }); + +/* ===================================================================== + WAS DEIN VERLAUF SAGT (15.09.2026) + + Filipe: „mach mir auch eine analyse, mach eine perfektion draus. eine + professionelle auch mit infos und so aus aller welt was tiktok + angeht. ich will dass es perfekt ist!!!!" + + --------------------------------------------------------------------- + SIE WIRD GERECHNET, NICHT GESPEICHERT + + Kein Feld, keine Tabelle, keine Note. Die Analyse entsteht bei jedem + Abruf neu aus dem Verlauf dieser einen Person — und verlässt das + Haus damit genauso wenig wie er. DogFather sieht sie nicht; es gibt + keinen Weg, der sie für jemand anderen ausgibt. + + --------------------------------------------------------------------- + DREI DINGE, DIE SIE TUT — UND EINES, DAS SIE NICHT TUT + + 1. SIE NENNT DAS MUSTER, nicht den Einzelwert. „Da hakt es" in + einer Runde ist ein schlechter Tag. Dasselbe in zwei Runden + hintereinander ist etwas anderes, und genau das ist in der + Forschung das Warnzeichen. + + 2. SIE ORDNET EIN. Zu jeder der sechs Fragen gibt es einen + belegten Befund aus der Moderationsarbeit — damit niemand + denkt, er sei der Einzige, dem das so geht. Das ist der Teil, + den Filipe „Infos aus aller Welt" nennt. + + 3. SIE SAGT, WAS HILFT. Aus derselben Forschung, und zwar + Maßnahmen, die die Person oder das Team wirklich umsetzen kann + — nicht „mehr Selbstfürsorge". + + 4. SIE BEWERTET NICHT. Keine Punktzahl, kein Score, kein + Ampelgesicht. Eine Zahl über das eigene Befinden lädt dazu ein, + sie zu verbessern statt ehrlich zu antworten — und ab da misst + die Abfrage nur noch sich selbst. + + --------------------------------------------------------------------- + DIE QUELLEN + + Zu freiwilligen Moderatoren (Reddit, Discord, Facebook): + „Zu wenig Zeit" und „Streit mit anderen Moderatoren" sind die zwei + meistgenannten Gründe fürs Aufhören. Burnout entsteht aus der + Menge, aus persönlichen Angriffen wegen Entscheidungen, aus der + unsichtbaren emotionalen Arbeit und aus fehlenden Werkzeugen. + https://news.umich.edu/online-content-moderators-likely-to-experience-burnout-u-m-study-suggests/ + https://arxiv.org/pdf/2502.06985 + + Zu bezahlten Moderatoren bei TikTok und anderen Plattformen: + Über 80 % der Befragten sagen, ihr Arbeitgeber müsse mehr für ihre + psychische Gesundheit tun. In der Türkei berichteten TikTok- + Moderatoren von Traumatisierung, langen Schichten und fehlender + Begleitung; weltweit organisieren sich Moderatoren dagegen. + https://restofworld.org/2025/tiktok-moderators-turkey/ + https://www.japantimes.co.jp/news/2025/07/04/world/science-health/content-moderators-mental-trauma/ + https://uniglobalunion.org/news/tech-protocols/ + + Was schützt: + Pausen und Rotation (niemand ist dauerhaft zuständig), Anschluss + ans Team (der stärkste Einzelfaktor gegen Belastung), ausdrückliche + Anerkennung, und ein bekannter Weg für den Ernstfall. + https://discord.com/safety/understanding-and-avoiding-moderator-burnout + https://www.zevohealth.com/blog/7-tips-for-managing-content-moderator-wellbeing/ + ===================================================================== */ + +/** Ab wie vielen Runden hintereinander ein „hakt" ein Muster ist. + * + * ZWEI, nicht drei. Bei vierzehn Tagen Abstand sind zwei Runden ein + * Monat -- wer einen Monat lang dasselbe meldet, hat keinen schlechten + * Tag. Drei waeren zehn Wochen, und so lange soll niemand warten. */ +export const MUSTER_AB = 2; + +/** Zu jeder Frage: der belegte Befund und was dagegen hilft. + * + * KEINE ALLGEMEINPLAETZE. Jeder Eintrag gehoert zu genau dieser Frage + * und stammt aus den Quellen oben. Ein Satz, der zu allen sechs passen + * wuerde, gehoert hier nicht hinein. */ +export const EINORDNUNG = { + "e-ich-menge-passt": { + welt: "„Zu wenig Zeit“ ist in Untersuchungen zu freiwilligen Moderatoren der " + + "meistgenannte Grund fürs Aufhören – noch vor den Inhalten selbst.", + hilft: "Das ist fast nie eine Frage der Person, sondern der Verteilung: feste " + + "Zeiten statt „immer erreichbar“, und eine Schicht, die wirklich endet. " + + "Sag es weiter, bevor du es allein löst.", + }, + "e-ich-niemand-schwierig": { + welt: "Streit im Team ist der zweithäufigste Grund – und der, über den am " + + "wenigsten geredet wird. Er trifft gerade die Teams, die sonst gut laufen.", + hilft: "Je länger es liegt, desto größer wird es. Ein Gespräch zu dritt, mit " + + "jemandem dabei, der nicht Partei ist, löst mehr als zwei Monate Abwarten.", + }, + "e-ich-weiss-an-wen": { + welt: "Ein fehlender Eskalationsweg ist in der Forschung kein Wissensproblem, " + + "sondern ein Belastungsproblem: Er fühlt sich nicht wie eine offene Frage " + + "an, sondern wie Unsicherheit in jeder Schicht.", + hilft: "Ein Name, nicht „das Team“. Wer im Ernstfall überlegen muss, wen er " + + "fragt, fragt niemanden.", + }, + "e-ich-jemand-zum-reden": { + welt: "Anschluss ans Team gilt als der stärkste einzelne Schutzfaktor gegen " + + "Belastung in der Moderationsarbeit. Umgekehrt arbeiten viele Moderatoren " + + "faktisch allein – auch mitten in einem Team.", + hilft: "Nicht erst, wenn etwas passiert ist. Ein kurzer fester Austausch nach " + + "harten Schichten wirkt nachweislich besser als ein Angebot, das man von " + + "sich aus annehmen muss.", + }, + "e-ich-nein-ohne-problem": { + welt: "Bei bezahlten Moderatoren – auch bei TikTok – sagen über 80 % der " + + "Befragten, ihr Arbeitgeber müsse mehr für ihre psychische Gesundheit tun. " + + "Wo Nein-Sagen teuer wirkt, bleibt Belastung unsichtbar, bis jemand geht.", + hilft: "Es geht nicht darum, ob du darfst – sondern wie es sich anfühlt. Wenn " + + "es sich teuer anfühlt, ist das eine Auskunft über das Team, nicht über dich.", + }, + "e-ich-faellt-auf": { + welt: "Fehlende Anerkennung ist der am häufigsten genannte emotionale Preis " + + "dieser Arbeit – die eigentliche Arbeit ist die, die NICHT passiert ist, " + + "und die sieht niemand.", + hilft: "Die billigste Maßnahme überhaupt und die mit der besten Wirkung: " + + "ausdrücklich danken, für etwas Bestimmtes, nicht allgemein.", + }, +}; + +/** Das Muster einer Frage ueber die Runden hinweg -- neueste zuerst. */ +function musterVon(antworten) { + if (!antworten.length) return { art: "offen", runden: 0 }; + const jetzt = antworten[0]; + let amStueck = 1; + while (amStueck < antworten.length && antworten[amStueck] === jetzt) amStueck++; + + if (jetzt === "hakt") { + return amStueck >= MUSTER_AB + ? { art: "dauerhaft", runden: amStueck } + : { art: "neu", runden: amStueck }; + } + /* Vorher hakte es, jetzt nicht mehr -- das gehoert genauso gesagt. + Eine Auswertung, die nur Schlechtes findet, liest man zweimal. */ + if (antworten.slice(1).includes("hakt")) return { art: "besser", runden: amStueck }; + if (jetzt === "laeuft" && amStueck === antworten.length && antworten.length >= MUSTER_AB) { + return { art: "traegt", runden: amStueck }; + } + return { art: "ruhig", runden: amStueck }; +} + +/** Die Analyse -- aus dem Verlauf gerechnet, fuer niemanden sonst. */ +export function analyseVon(verlauf, punkte) { + /* EINE EINZIGE RUNDE IST KEINE ENTWICKLUNG. Dann steht hier nichts + ausser dem Hinweis, dass es beim naechsten Mal etwas zu sehen gibt + -- eine Auswertung ueber einen Punkt waere geraten. */ + if (!Array.isArray(verlauf) || verlauf.length < MUSTER_AB) { + return { moeglich: false, runden: verlauf?.length || 0, ab: MUSTER_AB, zeilen: [] }; + } + + const zeilen = punkte.map((pt) => { + /* Neueste zuerst -- so kommt der Verlauf ohnehin. */ + const antworten = verlauf.map((r) => r.antworten[pt.schluessel]).filter(Boolean); + const m = musterVon(antworten); + const e = EINORDNUNG[pt.schluessel] || {}; + return { + schluessel: pt.schluessel, + titel: pt.titel, + art: m.art, + runden: m.runden || 0, + welt: e.welt || null, + /* WAS HILFT, STEHT NUR DA, WO ES ETWAS ZU TUN GIBT. Ein Rat an + einer Stelle, die laeuft, ist Laerm -- und er entwertet die + anderen fuenf. */ + hilft: (m.art === "dauerhaft" || m.art === "neu") ? (e.hilft || null) : null, + }; + }); + + const dauerhaft = zeilen.filter((z) => z.art === "dauerhaft"); + const neu = zeilen.filter((z) => z.art === "neu"); + const besser = zeilen.filter((z) => z.art === "besser"); + const traegt = zeilen.filter((z) => z.art === "traegt"); + + /* DER SATZ OBEN IST DIE GANZE ANALYSE IN EINER ZEILE. Wer nur ihn + liest, soll richtig informiert sein. Vier Lagen, vier Saetze -- aus + demselben Grund, aus dem die Ampel drei hat. */ + let satz; + if (dauerhaft.length) { + satz = dauerhaft.length === 1 + ? `Eine Sache meldest du seit ${dauerhaft[0].runden} Runden gleich. ` + + "Das ist kein schlechter Tag mehr – das ist das Muster, auf das es ankommt." + : `${dauerhaft.length} Sachen melden sich seit mehreren Runden gleich. ` + + "Das ist selten Zufall und fast nie deine Schuld."; + } else if (neu.length) { + satz = neu.length === 1 + ? "Eine Sache hakt seit dieser Runde. Einmal ist noch kein Muster – " + + "aber der richtige Moment, es anzusprechen, statt zu warten." + : `${neu.length} Sachen haken seit dieser Runde. Noch kein Muster – ` + + "aber zwei auf einmal sind einen Satz wert."; + } else if (besser.length) { + satz = "Nichts hakt gerade – und " + + (besser.length === 1 ? "eine Sache hat sich" : `${besser.length} Sachen haben sich`) + + " seit der letzten Runde gelöst. Das gehört genauso festgehalten wie das Gegenteil."; + } else { + satz = "Nichts hakt, und über die letzten Runden hat sich daran nichts geändert. " + + "Das ist die Antwort, für die diese Seite gebaut ist – nicht die Ausnahme."; + } + + return { + moeglich: true, + runden: verlauf.length, + ab: MUSTER_AB, + satz, + zeilen, + /* Abgeleitet, damit die Zahlen nicht neben den Zeilen danebenliegen + koennen. */ + zahlen: { + dauerhaft: dauerhaft.length, + neu: neu.length, + besser: besser.length, + traegt: traegt.length, + }, + }; +} diff --git a/workspace/app.webmanifest b/workspace/app.webmanifest index 22b3c758..1d7ee5e0 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=202609151730", + "src": "/assets/img/app-symbole/workspace-192.png?v=202609151738", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-512.png?v=202609151730", + "src": "/assets/img/app-symbole/workspace-512.png?v=202609151738", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609151730", + "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609151738", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609151730", + "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609151738", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/workspace/assets/css/entwicklung.css b/workspace/assets/css/entwicklung.css index 883fb184..ddac18f6 100644 --- a/workspace/assets/css/entwicklung.css +++ b/workspace/assets/css/entwicklung.css @@ -729,3 +729,83 @@ font-size: .79rem; color: var(--text-leise); } .e-schritt__knopf { margin: 0; } + +/* ===================================================================== + WAS DEIN VERLAUF SAGT (15.09.2026) + + Der linke Rand trägt die Aussage — dieselbe Sprache wie überall auf + dieser Seite: Gedeckt, kein Ampelgesicht, keine Farbe, die schreit. + Das Thema ist heikel genug; eine Oberfläche, die dramatisiert, macht + aus einer ehrlichen Antwort eine, die man sich überlegt. + ===================================================================== */ +.b-analyse { margin: 34px 0 0; } +/* DER SATZ UND DIE QUELLEN BRAUCHEN EINE FLÄCHE. + + Beide standen im Bildschirmfoto direkt auf der Bühne -- also auf + einem Foto. Über einem Foto lässt sich Lesbarkeit nicht ausrechnen, + und die Quellenzeile ist die kleinste Schrift der Seite. Dieselbe + Antwort wie bei der Entwicklungskarte heute Mittag: keine hellere + Schrift, sondern etwas Deckendes darunter. */ +.b-analyse__satz { + margin: 0 0 14px; padding: 12px 15px; max-width: 68ch; + font-size: .92rem; line-height: 1.65; color: var(--text); + background: color-mix(in srgb, var(--tinte) 92%, #ffffff 8%); + border: 1px solid var(--rand); + border-left: 3px solid color-mix(in srgb, var(--akzent) 45%, transparent); +} +.b-analyse__liste { display: grid; gap: 9px; } + +.b-analyse__zeile { + padding: 13px 15px; + background: color-mix(in srgb, var(--tinte) 94%, #ffffff 6%); + border: 1px solid var(--rand); + border-left: 3px solid var(--rand-hell); +} +/* Vier Lagen, vier Töne. „Hakt seit mehreren Runden" ist der einzige, + der wirklich auffällt — die anderen treten zurück. */ +.b-analyse__zeile[data-ton="hakt"] { border-left-color: #cc9451; } +.b-analyse__zeile[data-ton="neu"] { border-left-color: color-mix(in srgb, #cc9451 55%, transparent); } +.b-analyse__zeile[data-ton="besser"] { border-left-color: #668e6e; } +.b-analyse__zeile[data-ton="gut"] { border-left-color: color-mix(in srgb, #668e6e 55%, transparent); } +.b-analyse__zeile[data-ton="still"] { border-left-color: var(--rand); } + +.b-analyse__kopf { + display: flex; flex-wrap: wrap; align-items: baseline; gap: 9px; +} +.b-analyse__titel { margin: 0; font-size: .93rem; font-weight: 620; color: var(--text); } +.b-analyse__marke { + font-size: .73rem; letter-spacing: .04em; text-transform: uppercase; + padding: 2px 8px; border-radius: 999px; + border: 1px solid var(--rand-hell); color: var(--text-still); +} +.b-analyse__zeile[data-ton="hakt"] .b-analyse__marke { + color: #d8a76a; border-color: color-mix(in srgb, #cc9451 50%, transparent); +} +.b-analyse__zeile[data-ton="besser"] .b-analyse__marke { color: #7fb08a; } + +/* Der Befund aus der Welt. Er ist der Grund, warum die Zeile mehr ist + als eine Wiederholung der eigenen Antwort. */ +.b-analyse__welt { + margin: 7px 0 0; max-width: 68ch; + font-size: .83rem; line-height: 1.62; color: var(--text-leise); +} +.b-analyse__hilft { + margin: 9px 0 0; max-width: 68ch; padding: 9px 11px; + font-size: .83rem; line-height: 1.62; color: var(--text); + background: rgba(0, 0, 0, .24); + border: 1px solid var(--rand); +} +.b-analyse__hilft-marke { + display: block; margin-bottom: 3px; + font-size: .72rem; letter-spacing: .05em; text-transform: uppercase; + color: var(--text-still); +} + +.b-analyse__quelle { + margin: 14px 0 0; padding: 10px 13px; max-width: 72ch; + font-size: .78rem; line-height: 1.7; color: var(--text-still); + background: rgba(0, 0, 0, .3); + border: 1px solid var(--rand); +} +.b-analyse__quelle-hin { color: var(--text-leise); } +.b-analyse__quelle-hin:hover { color: var(--text); } diff --git a/workspace/assets/js/befinden.js b/workspace/assets/js/befinden.js index 7874949a..82231b3c 100644 --- a/workspace/assets/js/befinden.js +++ b/workspace/assets/js/befinden.js @@ -181,6 +181,99 @@ } } + /* ---- Was dein Verlauf sagt -------------------------------------------- + + Filipe: „mach mir auch eine analyse, mach eine perfektion draus. + eine professionelle auch mit infos und so aus aller welt was tiktok + angeht." + + GERECHNET HAT SIE DER SERVER, aus demselben Verlauf, der oben steht + -- hier wird nur gezeichnet. Das ist kein Zufall: Der Verlauf darf + das Haus nicht verlassen, und eine Auswertung im Browser hieße, ihn + dorthin zu schicken, wo man ihn abgreifen kann. + ==================================================================== */ + const ART = { + dauerhaft: { marke: 'seit mehreren Runden', ton: 'hakt' }, + neu: { marke: 'neu in dieser Runde', ton: 'neu' }, + besser: { marke: 'hat sich gelöst', ton: 'besser' }, + traegt: { marke: 'trägt durchgehend', ton: 'gut' }, + ruhig: { marke: 'unauffällig', ton: 'still' }, + offen: { marke: 'noch nicht beantwortet', ton: 'still' }, + }; + + function analyse() { + const block = $('analyse-block'); + const a = daten.analyse; + if (!a || !a.moeglich) { + /* NOCH KEINE ZWEITE RUNDE: Dann steht hier nichts. Eine Deutung + über einen einzigen Punkt wäre geraten, und geraten ist bei + diesem Thema schlimmer als nichts. */ + block.hidden = true; + return; + } + block.hidden = false; + $('analyse-satz').textContent = a.satz; + + const ziel = $('analyse'); + ziel.textContent = ''; + + /* DIE REIHENFOLGE IST DIE AUSSAGE: erst, was seit mehreren Runden + hakt, dann das Neue, dann was sich gelöst hat, dann der Rest. + Alphabetisch oder in Katalogreihenfolge müsste man suchen. */ + const rang = { dauerhaft: 0, neu: 1, besser: 2, traegt: 3, ruhig: 4, offen: 5 }; + const zeilen = [...a.zeilen].sort((x, y) => rang[x.art] - rang[y.art]); + + for (const z of zeilen) { + const art = ART[z.art] || ART.ruhig; + const k = el('article', 'b-analyse__zeile'); + k.dataset.ton = art.ton; + + const kopf = el('div', 'b-analyse__kopf'); + kopf.append(el('h3', 'b-analyse__titel', z.titel)); + kopf.append(el('span', 'b-analyse__marke', + z.art === 'dauerhaft' ? `seit ${z.runden} Runden` : art.marke)); + k.append(kopf); + + /* DIE EINORDNUNG -- der Teil, der sagt: du bist damit nicht + allein. Sie steht bei JEDER Zeile, auch bei den guten: Wer nur + dort etwas liest, wo es hakt, liest eine Mängelliste. */ + if (z.welt) k.append(el('p', 'b-analyse__welt', z.welt)); + if (z.hilft) { + const h = el('p', 'b-analyse__hilft'); + h.append(el('span', 'b-analyse__hilft-marke', 'Was hilft')); + h.append(document.createTextNode(z.hilft)); + k.append(h); + } + ziel.append(k); + } + + /* WOHER DAS KOMMT, GEHÖRT DAZU. Ein Satz über „die Forschung“ ohne + Quelle ist eine Behauptung -- und bei diesem Thema wäre das der + Moment, an dem man der ganzen Seite nicht mehr glaubt. */ + const q = $('analyse-quelle'); + q.textContent = ''; + q.append(document.createTextNode( + `Eingeordnet nach ${a.runden} Runden von dir – und nach Untersuchungen zur ` + + 'Moderationsarbeit: ')); + const quellen = [ + ['University of Michigan: warum Moderatoren aufhören', + 'https://news.umich.edu/online-content-moderators-likely-to-experience-burnout-u-m-study-suggests/'], + ['Discord: Anzeichen von Moderator-Burnout', + 'https://discord.com/safety/understanding-and-avoiding-moderator-burnout'], + ['Rest of World: TikTok-Moderation in der Türkei', + 'https://restofworld.org/2025/tiktok-moderators-turkey/'], + ]; + quellen.forEach(([name, url], i) => { + if (i) q.append(document.createTextNode(' · ')); + const a2 = el('a', 'b-analyse__quelle-hin', name); + a2.href = url; + a2.target = '_blank'; + /* noopener: Ein fremdes Fenster darf das eigene nicht umlenken. */ + a2.rel = 'noopener noreferrer'; + q.append(a2); + }); + } + /* ---- Runde abschließen ----------------------------------------------- */ $('takt-knopf')?.addEventListener('click', async (e) => { const k = e.currentTarget; @@ -208,6 +301,7 @@ takt(); fragen(); verlauf(); + analyse(); } (async () => { diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index 7795b6e0..5baf16fd 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 @@ - - - - - + + + + + diff --git a/workspace/automation.html b/workspace/automation.html index 3c0ae591..45eaa78e 100644 --- a/workspace/automation.html +++ b/workspace/automation.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -168,10 +168,10 @@ - - - - - + + + + + diff --git a/workspace/befinden.html b/workspace/befinden.html index e96d05ee..b6e97156 100644 --- a/workspace/befinden.html +++ b/workspace/befinden.html @@ -7,14 +7,14 @@ - - - - - - - - + + + + + + + + @@ -82,11 +82,32 @@
+ + + - - - - + + + + diff --git a/workspace/bereich.html b/workspace/bereich.html index 2141e5da..1cabe244 100644 --- a/workspace/bereich.html +++ b/workspace/bereich.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -181,14 +181,14 @@ - - - - - - - - - + + + + + + + + + diff --git a/workspace/calls.html b/workspace/calls.html index da63eb3e..a7df86ba 100644 --- a/workspace/calls.html +++ b/workspace/calls.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -89,11 +89,11 @@ - - - - - - + + + + + + diff --git a/workspace/chat.html b/workspace/chat.html index d0e87d25..295642f6 100644 --- a/workspace/chat.html +++ b/workspace/chat.html @@ -12,26 +12,26 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - + + + + - - - + + + - + @@ -341,10 +341,10 @@ - - - - - + + + + + diff --git a/workspace/content.html b/workspace/content.html index 4cd98b9e..87b9d5bf 100644 --- a/workspace/content.html +++ b/workspace/content.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -179,12 +179,12 @@ - - - - - - - + + + + + + + diff --git a/workspace/crew-index.html b/workspace/crew-index.html index 6bbf179f..57f053bc 100644 --- a/workspace/crew-index.html +++ b/workspace/crew-index.html @@ -36,15 +36,15 @@ - - - + + + - + @@ -339,6 +339,6 @@ - + diff --git a/workspace/crew.webmanifest b/workspace/crew.webmanifest index ff1c145e..85fbe7dd 100644 --- a/workspace/crew.webmanifest +++ b/workspace/crew.webmanifest @@ -13,25 +13,25 @@ "theme_color": "#06090f", "icons": [ { - "src": "/assets/img/app-symbole/crew-192.png?v=202609151730", + "src": "/assets/img/app-symbole/crew-192.png?v=202609151738", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/crew-512.png?v=202609151730", + "src": "/assets/img/app-symbole/crew-512.png?v=202609151738", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609151730", + "src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609151738", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609151730", + "src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609151738", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/workspace/dateien.html b/workspace/dateien.html index eee8d02d..b14de808 100644 --- a/workspace/dateien.html +++ b/workspace/dateien.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -108,10 +108,10 @@ - - - - - + + + + + diff --git a/workspace/entwicklung.html b/workspace/entwicklung.html index e00ddeb2..5f86f96e 100644 --- a/workspace/entwicklung.html +++ b/workspace/entwicklung.html @@ -7,14 +7,14 @@ - - - - - - - - + + + + + + + + @@ -102,9 +102,9 @@ - - - - + + + + diff --git a/workspace/index.html b/workspace/index.html index 3dec07ba..cdc826b2 100644 --- a/workspace/index.html +++ b/workspace/index.html @@ -30,9 +30,9 @@ - - - + + + @@ -401,6 +401,6 @@ - + diff --git a/workspace/kalender.html b/workspace/kalender.html index 576e8678..8aec956f 100644 --- a/workspace/kalender.html +++ b/workspace/kalender.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -377,11 +377,11 @@ - - - - - - + + + + + + diff --git a/workspace/leistung.html b/workspace/leistung.html index ab37c23e..3a373b16 100644 --- a/workspace/leistung.html +++ b/workspace/leistung.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -423,10 +423,10 @@ - - - - - + + + + + diff --git a/workspace/personen.html b/workspace/personen.html index cb6fcfe3..ded78d4a 100644 --- a/workspace/personen.html +++ b/workspace/personen.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -423,10 +423,10 @@ - - - - - + + + + + diff --git a/workspace/profil.html b/workspace/profil.html index fcd19707..27545515 100644 --- a/workspace/profil.html +++ b/workspace/profil.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -214,11 +214,11 @@ - - - - - - + + + + + + diff --git a/workspace/rechte.html b/workspace/rechte.html index 033109ca..cfbfbf85 100644 --- a/workspace/rechte.html +++ b/workspace/rechte.html @@ -7,14 +7,14 @@ - - - - - - - - + + + + + + + + @@ -134,9 +134,9 @@ Beim ersten Anlauf fehlte die Zeile auf genau diesen beiden neuen Seiten. Gefunden hat das nicht das Auge, sondern pruef-css-klassen, die seit dem 07.09. jede Seite danach absucht. --> - - - - + + + + diff --git a/workspace/report.html b/workspace/report.html index 09de32ca..6a8402a4 100644 --- a/workspace/report.html +++ b/workspace/report.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -106,11 +106,11 @@ - - - - - - + + + + + + diff --git a/workspace/scouting.html b/workspace/scouting.html index 00f2927b..c845971c 100644 --- a/workspace/scouting.html +++ b/workspace/scouting.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -173,10 +173,10 @@ - - - - - + + + + + diff --git a/workspace/start.html b/workspace/start.html index 79c1c19e..c5f28792 100644 --- a/workspace/start.html +++ b/workspace/start.html @@ -12,21 +12,21 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - + + + + - - + + - + @@ -674,10 +674,10 @@

- - - - - + + + + + diff --git a/workspace/startcheck.html b/workspace/startcheck.html index a6b5fd65..84db6ffb 100644 --- a/workspace/startcheck.html +++ b/workspace/startcheck.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -85,11 +85,11 @@ - - - - - - + + + + + + diff --git a/workspace/steckbrief.html b/workspace/steckbrief.html index 3b499725..2c1082c0 100644 --- a/workspace/steckbrief.html +++ b/workspace/steckbrief.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -146,11 +146,11 @@ - - - - - - + + + + + + diff --git a/workspace/talente.html b/workspace/talente.html index 0eddb571..7cf97fa0 100644 --- a/workspace/talente.html +++ b/workspace/talente.html @@ -7,14 +7,14 @@ - - - - - - - - + + + + + + + + @@ -94,9 +94,9 @@ - - - - + + + + diff --git a/workspace/team.html b/workspace/team.html index c62a383c..cf7f647c 100644 --- a/workspace/team.html +++ b/workspace/team.html @@ -7,20 +7,20 @@ - - - - - - - + + + + + + + - + @@ -110,10 +110,10 @@ - - - - - + + + + + diff --git a/workspace/teamlage.html b/workspace/teamlage.html index 11e88872..c164a88d 100644 --- a/workspace/teamlage.html +++ b/workspace/teamlage.html @@ -7,20 +7,20 @@ - - - - - - - + + + + + + + - + - - - - + + + + diff --git a/workspace/treff-moderation.html b/workspace/treff-moderation.html index f205ed43..2fe83647 100644 --- a/workspace/treff-moderation.html +++ b/workspace/treff-moderation.html @@ -7,14 +7,14 @@ - - - - - - - - + + + + + + + + @@ -110,9 +110,9 @@ - - - - + + + + diff --git a/workspace/treff-regeln.html b/workspace/treff-regeln.html index 10547d86..7e05aa31 100644 --- a/workspace/treff-regeln.html +++ b/workspace/treff-regeln.html @@ -7,18 +7,18 @@ - - - - - - - + + + + + + + - + @@ -210,10 +210,10 @@ Beim ersten Anlauf fehlte die Zeile auf genau diesen beiden neuen Seiten. Gefunden hat das nicht das Auge, sondern pruef-css-klassen, die seit dem 07.09. jede Seite danach absucht. --> - - - - - + + + + + diff --git a/workspace/uebersicht.html b/workspace/uebersicht.html index 12b158ab..84363ebd 100644 --- a/workspace/uebersicht.html +++ b/workspace/uebersicht.html @@ -12,22 +12,22 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - + + + + - - - + + + - + @@ -149,11 +149,11 @@ - + - - - - + + + + diff --git a/workspace/wissen.html b/workspace/wissen.html index 31a7d905..ea45bcdd 100644 --- a/workspace/wissen.html +++ b/workspace/wissen.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -190,10 +190,10 @@ - - - - - + + + + +