Compare commits
2
Commits
d77dd216c5
...
2719bd9b88
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2719bd9b88 | ||
|
|
1bdeccc054 |
+118
-4
@@ -213,12 +213,51 @@ melde("=== 2. Sechs Fragen, ein Rhythmus ===");
|
||||
passiert, wenn der Versatz und die Zahl der Fragen einen
|
||||
gemeinsamen Teiler haben. */
|
||||
{
|
||||
/* DIE GRENZE WIRD GERECHNET, NICHT EINGETRAGEN (20.09.2026).
|
||||
|
||||
Hier stand fest „6 Runden". Das war richtig, solange es sieben
|
||||
wechselnde Fragen bei drei je Runde gab. Mit dreizehn Fragen ist
|
||||
es nur noch Zufall, ob die Zahl passt -- und eine Pruefung, die
|
||||
bei jedem Katalogzuwachs von Hand nachgestellt werden muss, wird
|
||||
irgendwann nachgestellt, statt den Fehler zu zeigen.
|
||||
|
||||
Der Anspruch ist stattdessen: Es darf nicht laenger dauern, als
|
||||
es MUSS, plus eine Runde Luft. Weniger als ceil(wechselnde /
|
||||
Plaetze) geht rechnerisch nicht; mehr als eine Runde darueber
|
||||
heisst, dass die Auswahl sich im Kreis dreht.
|
||||
|
||||
GENAU SO IST DER FEHLER AUFGEFALLEN, den diese Zeile gefunden
|
||||
hat: Eine erste Fassung der feldbewussten Rotation brauchte
|
||||
ZWOELF Runden statt vier -- fast ein halbes Jahr, bis jede Frage
|
||||
einmal gestellt war. */
|
||||
const wechselnde = kat.ENTWICKLUNG_SELBST.filter((p) => !p.kern).length;
|
||||
const mindestens = Math.ceil(wechselnde / modul.ZUSATZ_PRO_RUNDE);
|
||||
const grenze = mindestens + 1;
|
||||
|
||||
const gesehen = new Set();
|
||||
for (let r = 0; r < 6; r++) {
|
||||
let gebraucht = null;
|
||||
for (let r = 0; r < grenze * 4; r++) {
|
||||
for (const p of modul.fragenFuer(idFrida, r).alle) gesehen.add(p.schluessel);
|
||||
if (gebraucht === null && gesehen.size === kat.ENTWICKLUNG_SELBST.length) {
|
||||
gebraucht = r + 1;
|
||||
}
|
||||
}
|
||||
ok(gesehen.size === kat.ENTWICKLUNG_SELBST.length,
|
||||
`über sechs Runden kommt jede der ${kat.ENTWICKLUNG_SELBST.length} Fragen dran (${gesehen.size})`);
|
||||
`jede der ${kat.ENTWICKLUNG_SELBST.length} Fragen kommt überhaupt dran (${gesehen.size})`);
|
||||
ok(gebraucht !== null && gebraucht <= grenze,
|
||||
`und zwar in ${gebraucht} Runden — rechnerisch nötig sind ${mindestens}, erlaubt ${grenze}`);
|
||||
|
||||
/* UND JEDE RUNDE STREUT ÜBER DIE FELDER. Vier Zusatzfragen aus
|
||||
demselben Feld waeren kein Befinden mehr, sondern ein Interview
|
||||
zu einem Thema -- genau das passiert, wenn der Ring dem nach
|
||||
Themen sortierten Katalog folgt. */
|
||||
let engste = 99;
|
||||
for (let r = 0; r < 12; r++) {
|
||||
const felder = new Set(modul.fragenFuer(idFrida, r).zusatz.map((p) => p.feld));
|
||||
engste = Math.min(engste, felder.size);
|
||||
}
|
||||
ok(engste >= Math.min(3, modul.ZUSATZ_PRO_RUNDE),
|
||||
`auch die engste Runde streut über mindestens 3 Felder (${engste})`);
|
||||
/* Und die Auswahl ist STABIL: derselbe Stand, dieselbe Liste.
|
||||
Zufall hiesse, dass bei jedem Neuladen etwas anderes dasteht. */
|
||||
ok(modul.fragenFuer(idFrida, 2).alle.map((p) => p.schluessel).join()
|
||||
@@ -411,8 +450,32 @@ melde("=== 5. Eine Seite, ein Zweck ===");
|
||||
ok(!/personen-block|meine-karte|abweichung/.test(b),
|
||||
"und keinen einzigen Abschnitt über andere Menschen");
|
||||
|
||||
ok(/befinden\.js/.test(b) && !/entwicklung\.js/.test(b),
|
||||
"sie lädt ihr eigenes Skript, nicht das der Entwicklungsseite");
|
||||
/* DIE SKRIPT-QUELLEN, NICHT DER ROHE TEXT (korrigiert 20.09.2026).
|
||||
|
||||
Hier stand `!/entwicklung\.js/.test(b)` -- eine Textsuche ueber die
|
||||
GANZE Datei. Die schlug an einem KOMMENTAR an, der voellig zu
|
||||
Recht die Serverdatei `workspace-entwicklung.js` benennt. Die
|
||||
Pruefung meldete damit einen Fehler an einer Stelle, an der nichts
|
||||
kaputt war, und stand der eigentlichen Frage im Weg.
|
||||
|
||||
Die eigentliche Frage lautet: Welche Dateien LAEDT die Seite? Die
|
||||
steht in den `src`-Angaben und nirgends sonst. Dieselbe Lehre wie
|
||||
bei pruef-schranke am 19.09.: Wer Text sucht, misst Text -- nicht
|
||||
die Sache. */
|
||||
const quellen = [...b.matchAll(/<script[^>]+src="([^"]+)"/g)]
|
||||
/* Nur der Dateiname -- ohne Pfad und ohne Versionsstempel. Damit
|
||||
braucht es hier kein Muster, das man falsch schreiben kann: Ein
|
||||
Vergleich auf Gleichheit hat keine Randfaelle. (Der erste Anlauf
|
||||
benutzte eine Wortgrenze in der Zeichenkette und bekam dadurch
|
||||
ein echtes Steuerzeichen in die Datei -- die Pruefung verglich
|
||||
danach gegen etwas, das es nirgends gibt, und die Gegenprobe war
|
||||
still gruen. Genau die Sorte Pruefung, die nichts prueft.) */
|
||||
.map((m) => m[1].split("?")[0].split("/").pop());
|
||||
ok(quellen.length > 0, `die Seite laedt ueberhaupt Skripte (${quellen.length})`);
|
||||
ok(quellen.includes("befinden.js"),
|
||||
`sie laedt ihr eigenes Skript (${quellen.join(", ")})`);
|
||||
ok(!quellen.includes("entwicklung.js"),
|
||||
"und nicht das der Entwicklungsseite — zwei Stände wären zwei Wahrheiten");
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
@@ -766,6 +829,57 @@ melde("=== 8. Die Analyse im Browser ===");
|
||||
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)");
|
||||
|
||||
/* ---- DIE FELDER UND DIE EINE ZUSAMMENFASSUNG (20.09.2026) --------
|
||||
|
||||
Filipe: „viel mehr fragen aber kategorisiert … und am ende EINE
|
||||
gesamte zusammenfassung."
|
||||
|
||||
Gemessen wird hier NICHT, ob ein Text dasteht, sondern:
|
||||
* dass die Fragen unter Überschriften stehen -- und zwar unter
|
||||
MEHREREN, sonst ist es eine Überschrift über allem und keine
|
||||
Gliederung;
|
||||
* dass jede Frage unter einer steht und keine darüber hängt;
|
||||
* dass die Zusammenfassung GENAU EINMAL vorkommt. Eine je
|
||||
Kategorie war ausdrücklich nicht gewünscht, und eine Prüfung,
|
||||
die nur "mindestens eine" verlangt, würde sechs durchlassen. */
|
||||
const g = await seite.evaluate(() => {
|
||||
const felder = [...document.querySelectorAll("#fragen .e-feld")];
|
||||
const fragen = [...document.querySelectorAll("#fragen .e-punkt")];
|
||||
/* Wie viele Fragen stehen VOR der ersten Überschrift? Jede davon
|
||||
hängt ohne Thema in der Luft. */
|
||||
const ersteFeldPos = felder.length
|
||||
? [...document.getElementById("fragen").children].indexOf(felder[0]) : -1;
|
||||
const kinder = [...document.getElementById("fragen").children];
|
||||
const heimatlos = kinder.slice(0, ersteFeldPos < 0 ? kinder.length : ersteFeldPos)
|
||||
.filter((k) => k.classList.contains("e-punkt")).length;
|
||||
const fazit = document.getElementById("fazit-block");
|
||||
return {
|
||||
felder: felder.length,
|
||||
feldNamen: felder.map((f) => f.querySelector(".e-feld__wort")?.textContent.trim()),
|
||||
fragen: fragen.length,
|
||||
heimatlos,
|
||||
fazitDa: fazit && fazit.hidden === false,
|
||||
fazitAnzahl: document.querySelectorAll(".b-fazit").length,
|
||||
kopf: document.getElementById("fazit-kopf")?.textContent.trim() || "",
|
||||
traegt: document.querySelectorAll("#fazit-traegt-liste li").length,
|
||||
schluss: document.getElementById("fazit-schluss")?.textContent.trim() || "",
|
||||
zahlen: document.getElementById("fazit-zahlen")?.textContent.trim() || "",
|
||||
};
|
||||
});
|
||||
ok(g.felder >= 3,
|
||||
`die Fragen stehen unter ${g.felder} Feldüberschriften — nicht in einer Reihe`);
|
||||
ok(g.heimatlos === 0,
|
||||
`keine Frage hängt über der ersten Überschrift (${g.heimatlos})`);
|
||||
ok(g.feldNamen.every((x) => x && x.length > 3),
|
||||
`jede Überschrift hat einen Namen (${g.feldNamen.join(", ")})`);
|
||||
ok(g.fazitDa === true, "die Zusammenfassung steht am Ende der Fragen");
|
||||
ok(g.fazitAnzahl === 1,
|
||||
`und zwar GENAU EINMAL (${g.fazitAnzahl}) — eine je Kategorie war ausdrücklich nicht gewünscht`);
|
||||
ok(g.kopf.length > 30, `mit einem ganzen Satz („${g.kopf.slice(0, 52)}…")`);
|
||||
ok(g.schluss.length > 20, `und einem Schlusssatz („${g.schluss.slice(0, 46)}…")`);
|
||||
ok(/\d+ Fragen aus \d+ Feldern/.test(g.zahlen),
|
||||
`die Belegzahlen stehen darunter („${g.zahlen}")`);
|
||||
if (process.env.SCHIRM) {
|
||||
await seite.locator("#analyse-block").screenshot(
|
||||
{ path: "C:/Users/qciga/Documents/Obelix/DogiHompage/blick.png" });
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
/* =====================================================================
|
||||
pruef-highlights.mjs — Drei Accounts nebeneinander, nach Zeit geteilt
|
||||
|
||||
Filipe, 20.09.2026: „die highlights sollen in 3 kategorien sein
|
||||
nebeneinander, dogfather, hasidog und dogfather clips. und die liste
|
||||
soll man auch zu und auf klappen können. die videos will ich auch
|
||||
kategorisiert haben nach woche, monat, jahr und ganz oben aktuell
|
||||
also heute. und wenn man was hinzufügt soll man auch den account
|
||||
auswählen können."
|
||||
|
||||
---------------------------------------------------------------------
|
||||
VIER FRAGEN, UND DIE DRITTE IST DIE, DIE MAN VERGISST
|
||||
|
||||
1. Stehen die drei Spalten da — in DIESER Reihenfolge?
|
||||
2. Landet jedes Highlight in seiner Spalte?
|
||||
3. Verschwindet beim Zeitfilter wirklich etwas — und kommt es
|
||||
beim Zurückschalten wieder?
|
||||
4. Kann man beim Anlegen einen Account wählen — und lehnt der
|
||||
Server einen erfundenen ab?
|
||||
|
||||
Frage 3 braucht die Gegenprobe: Ein Filter, der nichts ausblendet,
|
||||
besteht jede Prüfung, die nur zählt, ob danach noch etwas dasteht.
|
||||
|
||||
AUFRUF: node server/pruef-highlights.mjs
|
||||
===================================================================== */
|
||||
|
||||
import { mkdtempSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { portMussFreiSein } from "./helfer-port.mjs";
|
||||
import { notbremse } from "./helfer-notbremse.mjs";
|
||||
|
||||
const PORT = await portMussFreiSein(4438, "pruef-highlights");
|
||||
const ordner = mkdtempSync(join(tmpdir(), "ws-highlight-"));
|
||||
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
||||
process.env.PORT = String(PORT);
|
||||
process.env.SITE_ACCESS_SECRET = "lokaler-test";
|
||||
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 }); };
|
||||
await import("./index.js");
|
||||
notbremse(240_000, "pruef-highlights");
|
||||
await new Promise((r) => setTimeout(r, 900));
|
||||
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 ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
||||
|
||||
console.log("\n=== Highlights: drei Accounts, vier Zeiträume ===\n");
|
||||
|
||||
/* ---- Daten ---------------------------------------------------------
|
||||
Die Zeitpunkte relativ zu HEUTE, nie fest: Ein festes Datum wird vom
|
||||
Kalender eingeholt, und dann schlägt die Prüfung fehl, ohne dass
|
||||
etwas kaputt ist (Hausregel 06.09.2026). */
|
||||
const { DatabaseSync } = await import("node:sqlite");
|
||||
const { scryptSync, randomBytes } = await import("node:crypto");
|
||||
const { KANAELE } = await import("./workspace.js");
|
||||
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
||||
const salt = randomBytes(16).toString("hex");
|
||||
const hash = scryptSync("CODE-DOGI-0001", 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, aktiv, erstellt)"
|
||||
+ " VALUES (?,?,?,?,?,1,?)").run("Filipe", "admin", hash, salt, 32768, new Date().toISOString());
|
||||
|
||||
ok(KANAELE.length === 3,
|
||||
`der Server kennt drei Accounts (${KANAELE.map((k) => k.name).join(", ")})`);
|
||||
|
||||
const vorTagen = (n) => {
|
||||
const x = new Date();
|
||||
x.setHours(9, 0, 0, 0);
|
||||
x.setDate(x.getDate() - n);
|
||||
return x;
|
||||
};
|
||||
/* Eines von heute, eines von vor 40 Tagen (also sicher weder heute
|
||||
noch diese Woche noch dieser Monat, aber dieses Jahr — sofern es
|
||||
nicht Januar ist; deshalb wird unten NICHT auf "jahr" gebaut). */
|
||||
const einf = (titel, kanal, wannTage) => d.prepare(
|
||||
"INSERT INTO eintraege (bereich, art, titel, text, status, dringlichkeit,"
|
||||
+ " datum, quelle_kanal, erstellt_von, erstellt)"
|
||||
+ " VALUES (?,?,?,?,?,?,?,?,?,?)")
|
||||
.run("highlight", "clip", titel, "", "offen", "mittel",
|
||||
vorTagen(wannTage).toISOString().slice(0, 10), kanal, 1,
|
||||
vorTagen(wannTage).toISOString());
|
||||
|
||||
einf("Heute DogFather", "dogfather", 0);
|
||||
einf("Heute HasiDog", "hasidog", 0);
|
||||
einf("Heute Clips", "clips", 0);
|
||||
einf("Alt DogFather", "dogfather", 40);
|
||||
einf("Ohne Account dabei", null, 0);
|
||||
d.close();
|
||||
|
||||
const an = await fetch(BASIS + "/workspace/api/anmelden", {
|
||||
method: "POST", headers: { "Content-Type": "application/json", Origin: BASIS },
|
||||
body: JSON.stringify({ rolle: "admin", code: "CODE-DOGI-0001" }),
|
||||
});
|
||||
const keks = (an.headers.getSetCookie?.() || []).map((z) => z.split(";")[0]).join("; ");
|
||||
ok(an.ok && !!keks, "angemeldet");
|
||||
|
||||
/* ---- 1. Der Server schickt die Kanäle mit ------------------------- */
|
||||
const brett = await (await fetch(BASIS + "/workspace/api/bereich/highlight",
|
||||
{ headers: { Cookie: keks } })).json();
|
||||
ok(Array.isArray(brett.kanaele) && brett.kanaele.length === 3,
|
||||
`die Brett-Auskunft bringt die Accounts mit (${brett.kanaele?.length})`);
|
||||
ok(brett.kanaele?.[0]?.wert === "dogfather" && brett.kanaele?.[2]?.wert === "clips",
|
||||
"und zwar in der Reihenfolge des Servers — nicht in der der Einträge");
|
||||
ok(brett.kanaele?.every((k) => !!k.handle),
|
||||
"mit Handle, damit die Spalte sagt, welcher Account gemeint ist");
|
||||
|
||||
/* GEGENPROBE: Wer nicht zum Team gehört, hat mit den Accounts nichts
|
||||
zu tun und darf die Liste nicht bekommen. Ohne diese Probe bestünde
|
||||
auch eine Fassung, die sie an jeden schickt. */
|
||||
{
|
||||
const dd = new DatabaseSync(process.env.WORKSPACE_DB);
|
||||
const s2 = randomBytes(16).toString("hex");
|
||||
const h2 = scryptSync("CODE-GAST-0002", s2, 64,
|
||||
{ N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
|
||||
dd.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, aktiv, erstellt)"
|
||||
+ " VALUES (?,?,?,?,?,1,?)").run("Gast", "gast", h2, s2, 32768, new Date().toISOString());
|
||||
dd.close();
|
||||
const ag = await fetch(BASIS + "/workspace/api/anmelden", {
|
||||
method: "POST", headers: { "Content-Type": "application/json", Origin: BASIS },
|
||||
body: JSON.stringify({ rolle: "gast", code: "CODE-GAST-0002" }),
|
||||
});
|
||||
const gk = (ag.headers.getSetCookie?.() || []).map((z) => z.split(";")[0]).join("; ");
|
||||
const gb = await fetch(BASIS + "/workspace/api/bereich/highlight", { headers: { Cookie: gk } });
|
||||
const gj = gb.ok ? await gb.json() : null;
|
||||
ok(!gj || (gj.kanaele || []).length === 0,
|
||||
`Gegenprobe: ein Gast bekommt die Accountliste nicht (${(gj?.kanaele || []).length})`);
|
||||
}
|
||||
|
||||
/* ---- 2. Ein erfundener Account wird abgelehnt --------------------- */
|
||||
const anlegen = async (titel, kanal) => fetch(BASIS + "/workspace/api/bereich/highlight", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", Cookie: keks, Origin: BASIS },
|
||||
body: JSON.stringify({ titel, art: "bild", text: "", quelle_kanal: kanal }),
|
||||
});
|
||||
const gut = await anlegen("Ein Bild für HasiDog", "hasidog");
|
||||
ok(gut.ok, `ein Bild lässt sich mit gewähltem Account anlegen (${gut.status})`);
|
||||
const boese = await anlegen("Erfundener Account", "netflix");
|
||||
ok(boese.status === 400,
|
||||
`Gegenprobe: ein erfundener Account wird abgelehnt (${boese.status}) — eine Regel, die nur im Formular steht, ist keine Regel`);
|
||||
const leer = await anlegen("Noch offen", "");
|
||||
ok(leer.ok, `„Noch offen" ist erlaubt — ein falscher Eintrag wäre schlechter als ein leerer (${leer.status})`);
|
||||
|
||||
/* ---- 3. Am Bildschirm -------------------------------------------- */
|
||||
const pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
|
||||
const browser = await pw.chromium.launch();
|
||||
try {
|
||||
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1000 } });
|
||||
const s = await ctx.newPage();
|
||||
await s.goto(BASIS + "/workspace/", { waitUntil: "networkidle" });
|
||||
await s.click('.rolle[data-rolle="admin"]');
|
||||
await s.fill("#code", "CODE-DOGI-0001");
|
||||
await s.click("button[type=submit]");
|
||||
await s.waitForURL("**/start.html", { timeout: 25000 });
|
||||
await s.goto(BASIS + "/workspace/bereich.html?b=highlight", { waitUntil: "networkidle" });
|
||||
await s.waitForTimeout(1600);
|
||||
|
||||
const lies = () => s.evaluate(() => {
|
||||
const sp = [...document.querySelectorAll(".kanalspalte")];
|
||||
return {
|
||||
spalten: sp.map((x) => ({
|
||||
kanal: x.dataset.kanal,
|
||||
wort: x.querySelector(".kanalspalte__wort")?.firstChild?.textContent?.trim(),
|
||||
handle: x.querySelector(".kanalspalte__handle")?.textContent?.trim() || null,
|
||||
zahl: x.querySelector(".kanalspalte__zahl")?.textContent,
|
||||
karten: x.querySelectorAll(".kanalspalte__inhalt .karte, .kanalspalte__inhalt > *").length,
|
||||
titel: [...x.querySelectorAll(".kanalspalte__inhalt")]
|
||||
.map((i) => i.textContent.replace(/\s+/g, " ").trim()).join(" "),
|
||||
oben: Math.round(x.getBoundingClientRect().top),
|
||||
links: Math.round(x.getBoundingClientRect().left),
|
||||
})),
|
||||
zeitknoepfe: [...document.querySelectorAll(".filter__zeit")]
|
||||
.map((b) => ({ zeit: b.dataset.zeit || "", text: b.textContent.trim(),
|
||||
aus: b.disabled, an: b.getAttribute("aria-pressed") === "true" })),
|
||||
};
|
||||
});
|
||||
|
||||
const a = await lies();
|
||||
ok(a.spalten.length >= 3, `die Spalten stehen (${a.spalten.length})`);
|
||||
ok(a.spalten[0]?.kanal === "dogfather" && a.spalten[1]?.kanal === "hasidog"
|
||||
&& a.spalten[2]?.kanal === "clips",
|
||||
`in der Reihenfolge DogFather, HasiDog, Clips (${a.spalten.map((x) => x.kanal).join(", ")})`);
|
||||
/* NEBENEINANDER, nicht untereinander — bei 1400 px ist dafür Platz.
|
||||
Ohne diese Messung bestünde auch eine Fassung, die drei Blöcke
|
||||
stapelt, und genau das war die Bitte. */
|
||||
ok(a.spalten[0].oben === a.spalten[1].oben && a.spalten[1].oben === a.spalten[2].oben,
|
||||
`sie stehen NEBENeinander (alle bei y=${a.spalten[0].oben})`);
|
||||
ok(a.spalten[0].links < a.spalten[1].links && a.spalten[1].links < a.spalten[2].links,
|
||||
"und in der richtigen Richtung von links nach rechts");
|
||||
/* NUR DIE ECHTEN ACCOUNTS. Die Spalte „Ohne Account" hat
|
||||
berechtigterweise kein Handle -- der erste Anlauf verlangte es von
|
||||
allen vieren und schlug an einer Stelle an, an der nichts kaputt
|
||||
war. Genau die Sorte Fehlalarm, die einen Prüfsatz entwertet. */
|
||||
const echte = a.spalten.filter((x) => x.kanal !== "ohne");
|
||||
ok(echte.length === 3 && echte.every((x) => /^@/.test(x.handle || "")),
|
||||
`jeder echte Account nennt sein Handle (${echte.map((x) => x.handle).join(" ")})`);
|
||||
ok(a.spalten.find((x) => x.kanal === "ohne")?.handle === null,
|
||||
'und die Spalte ohne Account erfindet sich keines');
|
||||
|
||||
/* Landet jedes Highlight in SEINER Spalte? */
|
||||
ok(/Heute DogFather/.test(a.spalten[0].titel) && !/Heute HasiDog/.test(a.spalten[0].titel),
|
||||
"ein DogFather-Clip steht in der DogFather-Spalte und nirgends sonst");
|
||||
ok(/Ohne Account/.test(a.spalten.at(-1).titel) && a.spalten.at(-1).kanal === "ohne",
|
||||
"was zu keinem Account gehört, bekommt eine eigene Spalte statt zu verschwinden");
|
||||
|
||||
/* ---- 4. Der Zeitfilter ----------------------------------------- */
|
||||
ok(a.zeitknoepfe.length === 5,
|
||||
`die Zeitleiste hat Aktuell, Woche, Monat, Jahr und Alles (${a.zeitknoepfe.length})`);
|
||||
ok(a.zeitknoepfe[0]?.zeit === "heute" && /Aktuell/.test(a.zeitknoepfe[0]?.text || ""),
|
||||
`„Aktuell" steht ganz oben (${a.zeitknoepfe[0]?.text})`);
|
||||
ok(a.zeitknoepfe.every((b) => /\d/.test(b.text)),
|
||||
"jeder Knopf trägt seine Zahl — sonst klickt man ins Leere und weiß nicht, ob es am Filter lag");
|
||||
|
||||
const vorher = a.spalten.reduce((n, x) => n + Number(x.zahl || 0), 0);
|
||||
await s.click('.filter__zeit[data-zeit="heute"]');
|
||||
await s.waitForTimeout(900);
|
||||
const b = await lies();
|
||||
const nachher = b.spalten.reduce((n, x) => n + Number(x.zahl || 0), 0);
|
||||
ok(nachher < vorher,
|
||||
`„Aktuell" blendet wirklich etwas aus (${vorher} → ${nachher})`);
|
||||
ok(!/Alt DogFather/.test(b.spalten.map((x) => x.titel).join(" ")),
|
||||
" der 40 Tage alte Eintrag ist weg");
|
||||
ok(/Heute DogFather/.test(b.spalten[0].titel),
|
||||
" der von heute steht noch da");
|
||||
|
||||
/* GEGENPROBE: Zurückschalten muss ihn zurückbringen. Ohne sie
|
||||
bestünde auch eine Fassung, die den Eintrag dauerhaft verliert. */
|
||||
await s.click('.filter__zeit[data-zeit="heute"]');
|
||||
await s.waitForTimeout(900);
|
||||
const c = await lies();
|
||||
ok(c.spalten.reduce((n, x) => n + Number(x.zahl || 0), 0) === vorher,
|
||||
`Gegenprobe: ein zweiter Klick hebt den Filter auf (${vorher} wieder da)`);
|
||||
|
||||
/* ---- 5. Zuklappen ---------------------------------------------- */
|
||||
const zuVorher = await s.evaluate(() =>
|
||||
document.querySelector(".kanalspalte__inhalt")?.hidden === true);
|
||||
await s.click(".kanalspalte__kopf");
|
||||
await s.waitForTimeout(500);
|
||||
const zuNachher = await s.evaluate(() => {
|
||||
const i = document.querySelector(".kanalspalte__inhalt");
|
||||
return { zu: i?.hidden === true || i?.offsetHeight === 0,
|
||||
sagt: document.querySelector(".kanalspalte__kopf")?.getAttribute("aria-expanded") };
|
||||
});
|
||||
ok(zuVorher === false && zuNachher.zu === true,
|
||||
"eine Spalte lässt sich zuklappen");
|
||||
ok(zuNachher.sagt === "false",
|
||||
` und sagt es auch Vorleseprogrammen (aria-expanded=${zuNachher.sagt})`);
|
||||
|
||||
await ctx.close();
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
console.log(`\n${fehler ? fehler + " FEHLER" : "Alles in Ordnung"} — ${geprueft} Prüfungen\n`);
|
||||
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
|
||||
process.exit(fehler ? 1 : 0);
|
||||
@@ -0,0 +1,181 @@
|
||||
/* =====================================================================
|
||||
pruef-resuemee.mjs — Die eine Zusammenfassung am Ende
|
||||
|
||||
Filipe, 20.09.2026: „am ende von allen fragen will ich eine gesamte
|
||||
zusammenfassung, nicht bei jeder frage oder kategorie eine, sondern
|
||||
EINE gesamte am ende. eine motivierende zusammenfassung für die
|
||||
person."
|
||||
|
||||
---------------------------------------------------------------------
|
||||
WAS HIER GEPRÜFT WIRD, IST NICHT „steht ein Text da"
|
||||
|
||||
Sondern die eine Eigenschaft, an der so ein Text scheitert:
|
||||
ER DARF NICHT LÜGEN, UM ZU MOTIVIEREN.
|
||||
|
||||
Eine Zusammenfassung, die bei sechs von acht „da hakt es" fröhlich
|
||||
bleibt, ist keine Ermutigung, sondern Hohn — und beim nächsten Mal
|
||||
antwortet man ihr nicht mehr ehrlich. Genau das ist mit einem
|
||||
Sprachmodell die wahrscheinlichste Fehlerart, und genau deshalb
|
||||
rechnet das hier ein Programm.
|
||||
|
||||
Die Prüfungen gehen deshalb paarweise: einmal die gute Lage, einmal
|
||||
die schwere — und der Text MUSS sich unterscheiden. Ein Text, der
|
||||
in beiden Fällen gleich klingt, besteht jede Prüfung, die nur nach
|
||||
„ist ein Satz da" fragt.
|
||||
|
||||
AUFRUF: node server/pruef-resuemee.mjs
|
||||
===================================================================== */
|
||||
|
||||
import { resuemeeVon } from "./workspace-befinden-resuemee.js";
|
||||
import { ENTWICKLUNG_PUNKTE, BEFINDEN_FELDER } from "./workspace-entwicklungs-punkte.js";
|
||||
|
||||
let fehler = 0, geprueft = 0;
|
||||
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
||||
|
||||
console.log("\n=== Die eine Zusammenfassung ===\n");
|
||||
|
||||
const punkte = ENTWICKLUNG_PUNKTE.befinden;
|
||||
ok(punkte.length >= 12, `es gibt genug Fragen zum Zusammenfassen (${punkte.length})`);
|
||||
ok(punkte.every((p) => !!p.feld),
|
||||
`jede Frage gehört zu einem Feld (${punkte.filter((p) => !p.feld).length} ohne)`);
|
||||
|
||||
/* JEDES FELD KOMMT VOR. Ein Feld ohne Fragen wäre eine Überschrift
|
||||
über nichts -- und in der Zusammenfassung ein Thema, das nie
|
||||
gewählt werden kann. */
|
||||
for (const f of BEFINDEN_FELDER) {
|
||||
const n = punkte.filter((p) => p.feld === f.schluessel).length;
|
||||
ok(n > 0, ` Feld '${f.name}' hat Fragen (${n})`);
|
||||
}
|
||||
/* GEGENPROBE IN DIE ANDERE RICHTUNG: kein verwaistes Feld an einer
|
||||
Frage, das es in der Liste nicht gibt. Ohne sie bliebe eine
|
||||
vertippte Feldkennung unbemerkt -- die Frage stünde dann unter
|
||||
„ohne Feld" am Ende, und niemand wüsste warum. */
|
||||
const bekannt = new Set(BEFINDEN_FELDER.map((f) => f.schluessel));
|
||||
const verwaist = punkte.filter((p) => p.feld && !bekannt.has(p.feld));
|
||||
ok(verwaist.length === 0,
|
||||
`Gegenprobe: keine Frage zeigt auf ein Feld, das es nicht gibt (${verwaist.map((p) => p.feld).join(", ") || "keine"})`);
|
||||
|
||||
const bau = (stufen) => {
|
||||
const a = {};
|
||||
punkte.slice(0, stufen.length).forEach((p, i) => { a[p.schluessel] = stufen[i]; });
|
||||
return a;
|
||||
};
|
||||
const teil = (n, stufe) => Array(n).fill(stufe);
|
||||
|
||||
/* ---- 1. DIE GUTE LAGE -------------------------------------------- */
|
||||
const gut = resuemeeVon(bau(teil(8, "laeuft")), punkte.slice(0, 8), BEFINDEN_FELDER);
|
||||
ok(gut.moeglich === true, "bei acht Mal 'Läuft' gibt es eine Zusammenfassung");
|
||||
ok(gut.thema === null, " und kein Thema zum Hinschauen — es gibt keins");
|
||||
ok(gut.traegt.length > 0, ` aber etwas, das trägt (${gut.traegt.length})`);
|
||||
ok(gut.zahlen.hakt === 0 && gut.zahlen.traegt === 8,
|
||||
` die Zahlen stimmen (${gut.zahlen.traegt} läuft, ${gut.zahlen.hakt} hakt)`);
|
||||
|
||||
/* ---- 2. DIE SCHWERE LAGE ----------------------------------------- */
|
||||
const schwer = resuemeeVon(bau(teil(6, "hakt").concat(teil(2, "laeuft"))),
|
||||
punkte.slice(0, 8), BEFINDEN_FELDER);
|
||||
ok(schwer.moeglich === true, "bei sechs von acht 'Da hakt es' auch");
|
||||
ok(schwer.zahlen.hakt === 6, ` und sie zählt richtig (${schwer.zahlen.hakt})`);
|
||||
|
||||
/* DIE ENTSCHEIDENDE PRÜFUNG. */
|
||||
ok(gut.kopf !== schwer.kopf,
|
||||
"der Kopfsatz ist ein ANDERER als bei der guten Lage — er ist nicht für alle gleich");
|
||||
ok(gut.schluss !== schwer.schluss,
|
||||
"und der Schlusssatz auch");
|
||||
ok(/6 von 8/.test(schwer.kopf),
|
||||
`die schwere Lage wird BEIM NAMEN genannt ('${schwer.kopf.slice(0, 44)}…')`);
|
||||
ok(!/super|toll|prima|großartig|weiter so/i.test(schwer.kopf + schwer.schluss),
|
||||
"und nicht schöngeredet — kein 'super', kein 'weiter so'");
|
||||
ok(/jemandem|allein|sagen/i.test(schwer.schluss),
|
||||
`der Schluss zeigt einen Weg statt Trost ('${schwer.schluss.slice(0, 50)}…')`);
|
||||
|
||||
/* UND TROTZDEM NICHT NIEDERSCHMETTERND: Auch in der schweren Lage
|
||||
steht das, was trägt — sonst ist es eine Mängelliste. */
|
||||
ok(schwer.traegt.length > 0,
|
||||
`auch hier steht, was trägt (${schwer.traegt.length}) — sonst wäre es eine Mängelliste`);
|
||||
|
||||
/* ---- 3. HÖCHSTENS EIN THEMA -------------------------------------- */
|
||||
ok(schwer.thema !== null, "bei sechs Haken gibt es ein Thema");
|
||||
ok(typeof schwer.thema.name === "string" && schwer.thema.name.length > 2,
|
||||
` und es hat einen Namen ('${schwer.thema?.name}')`);
|
||||
/* Wer fünf Probleme auf einmal vorgelegt bekommt, sieht keine fünf
|
||||
Aufgaben, sondern einen Berg. */
|
||||
ok(!Array.isArray(schwer.thema.felder),
|
||||
"es ist EIN Feld, keine Liste — fünf Probleme auf einmal sind ein Berg");
|
||||
|
||||
/* DAS THEMA IST DAS FELD MIT DEN MEISTEN HAKEN, nicht das erste. */
|
||||
{
|
||||
/* Drei Haken in EINEM Feld gegen einen in einem anderen. */
|
||||
const zielFeld = BEFINDEN_FELDER.find((f) =>
|
||||
punkte.filter((p) => p.feld === f.schluessel).length >= 3);
|
||||
const imFeld = punkte.filter((p) => p.feld === zielFeld.schluessel).slice(0, 3);
|
||||
const andere = punkte.find((p) => p.feld !== zielFeld.schluessel);
|
||||
const a = {};
|
||||
for (const p of imFeld) a[p.schluessel] = "hakt";
|
||||
a[andere.schluessel] = "hakt";
|
||||
const r = resuemeeVon(a, [...imFeld, andere], BEFINDEN_FELDER);
|
||||
ok(r.thema?.feld === zielFeld.schluessel,
|
||||
`das Thema ist das Feld mit den meisten Haken ('${r.thema?.name}', ${r.thema?.anzahl})`);
|
||||
ok(r.thema?.anzahl === 3, ` und nennt, wie viele es dort sind (${r.thema?.anzahl})`);
|
||||
}
|
||||
|
||||
/* ---- 4. „KANN ICH NICHT SAGEN" IST KEIN PROBLEM ------------------ */
|
||||
/* Wer sie zu den Sorgen schlägt, erzeugt Sorgen, die niemand
|
||||
geäußert hat. */
|
||||
const unklar = resuemeeVon(bau(teil(8, "unklar")), punkte.slice(0, 8), BEFINDEN_FELDER);
|
||||
ok(unklar.zahlen.hakt === 0 && unklar.zahlen.unklar === 8,
|
||||
`acht Mal 'Kann ich nicht sagen' ergibt null Haken (${unklar.zahlen.hakt})`);
|
||||
ok(unklar.thema === null, " und kein Thema — es wurde ja nichts gesagt");
|
||||
ok(/ehrliche Antwort/i.test(unklar.kopf),
|
||||
` der Satz nennt es eine ehrliche Antwort ('${unklar.kopf.slice(0, 40)}…')`);
|
||||
|
||||
/* ---- 5. „HAT SICH GEBESSERT" NUR, WENN ES STIMMT ----------------- */
|
||||
{
|
||||
const p3 = punkte.slice(0, 3);
|
||||
const vorher = { [p3[0].schluessel]: "hakt", [p3[1].schluessel]: "laeuft" };
|
||||
const jetzt = { [p3[0].schluessel]: "laeuft", [p3[1].schluessel]: "laeuft",
|
||||
[p3[2].schluessel]: "laeuft" };
|
||||
const r = resuemeeVon(jetzt, p3, BEFINDEN_FELDER, vorher);
|
||||
ok(r.zahlen.besser === 1,
|
||||
`genau eine echte Verbesserung wird gezählt (${r.zahlen.besser})`);
|
||||
/* DIE FALLE: Die dritte Frage war letztes Mal gar nicht dran. „War
|
||||
nicht dabei" ist KEINE Verbesserung -- seit die Fragen rotieren,
|
||||
wäre das sonst bei jeder frisch dazugekommenen Frage der Fall,
|
||||
und der Satz „drei Sachen haben sich gebessert" wäre erfunden. */
|
||||
ok(!/2 Sachen|3 Sachen/.test(r.schluss),
|
||||
` eine Frage, die letztes Mal nicht dran war, zählt nicht als Verbesserung ("${r.schluss.slice(0, 46)}…")`);
|
||||
|
||||
/* GEGENPROBE: Wird es schlechter, steht dort KEINE Verbesserung. */
|
||||
const rueck = resuemeeVon({ [p3[0].schluessel]: "hakt" }, [p3[0]], BEFINDEN_FELDER,
|
||||
{ [p3[0].schluessel]: "laeuft" });
|
||||
ok(rueck.zahlen.besser === 0,
|
||||
`Gegenprobe: eine Verschlechterung wird nicht als Besserung gezählt (${rueck.zahlen.besser})`);
|
||||
}
|
||||
|
||||
/* ---- 6. DER DRITTE AUSGANG --------------------------------------- */
|
||||
const leer = resuemeeVon({}, punkte.slice(0, 5), BEFINDEN_FELDER);
|
||||
ok(leer.moeglich === false,
|
||||
"ohne Antworten sagt sie, dass sie nichts sagen kann — statt freundlich über nichts zu reden");
|
||||
ok(typeof leer.grund === "string" && leer.grund.length > 5,
|
||||
` und nennt den Grund ('${leer.grund}')`);
|
||||
|
||||
/* ---- 7. KEINE ERFUNDENEN ZAHLEN ---------------------------------- */
|
||||
/* Jede Zahl im Text muss in `zahlen` wiederzufinden sein. Ein Satz,
|
||||
der „12 Fragen" sagt, während acht gestellt wurden, ist genau der
|
||||
Fehler, wegen dem hier kein Sprachmodell hängt. */
|
||||
{
|
||||
const r = resuemeeVon(bau(teil(3, "hakt").concat(teil(5, "laeuft"))),
|
||||
punkte.slice(0, 8), BEFINDEN_FELDER);
|
||||
const text = [r.kopf, r.schluss, r.thema?.satz].filter(Boolean).join(" ");
|
||||
const zahlenImText = [...text.matchAll(/\b(\d+)\b/g)].map((m) => Number(m[1]));
|
||||
const erlaubt = new Set(Object.values(r.zahlen).filter((x) => typeof x === "number"));
|
||||
/* Auch Differenzen sind erlaubt -- „5 von 8 laufen" nennt 8 minus 3. */
|
||||
for (const z of [...erlaubt]) for (const y of [...erlaubt]) erlaubt.add(Math.abs(z - y));
|
||||
const erfunden = zahlenImText.filter((z) => !erlaubt.has(z));
|
||||
ok(erfunden.length === 0,
|
||||
`jede Zahl im Text steht auch in den Zahlen (${erfunden.length} erfunden)`);
|
||||
console.log(` Text: '${r.kopf}'`);
|
||||
console.log(` Zahlen: ${JSON.stringify(r.zahlen)}`);
|
||||
}
|
||||
|
||||
console.log(`\n${fehler ? fehler + " FEHLER" : "Alles in Ordnung"} — ${geprueft} Prüfungen\n`);
|
||||
process.exit(fehler ? 1 : 0);
|
||||
@@ -0,0 +1,256 @@
|
||||
/* =====================================================================
|
||||
workspace-befinden-resuemee.js — Eine Zusammenfassung am Ende
|
||||
(20.09.2026)
|
||||
|
||||
Filipe: „und am ende von allen fragen will ich eine gesamte
|
||||
zusammenfassung, nicht bei jeder frage oder kategorie eine, sondern
|
||||
EINE gesamte am ende. eine motivierende zusammenfassung für die
|
||||
person. hol die beste der beste möglichkeiten dafür die man beutzen
|
||||
kann kostenlos."
|
||||
|
||||
---------------------------------------------------------------------
|
||||
DIE FRAGE NACH DER „BESTEN KOSTENLOSEN MÖGLICHKEIT" — EHRLICH
|
||||
BEANTWORTET
|
||||
|
||||
Naheliegend wäre eine der kostenlosen KI-Schnittstellen (Groq,
|
||||
Gemini, HuggingFace). Sie wäre hier die FALSCHE Wahl, und zwar aus
|
||||
drei Gründen, von denen jeder einzelne reicht:
|
||||
|
||||
1. ES SIND GESUNDHEITSDATEN. Die Antworten sagen, wer nachts wach
|
||||
liegt, wer angefeindet wird, wer ans Aufhören denkt. Genau
|
||||
deshalb sind sie im ganzen Haus als `nurSelbst` markiert und
|
||||
liegen in einer eigenen Tabelle, die kein anderer Weg liest.
|
||||
Sie an einen fremden Dienst zu schicken, damit er einen netten
|
||||
Satz daraus baut, würde diese ganze Sorgfalt in einer Zeile
|
||||
aufheben. Kostenlose KI-Angebote bezahlt man mit den Daten —
|
||||
in ihren Bedingungen steht meist ausdrücklich, dass Eingaben
|
||||
zum Training verwendet werden dürfen.
|
||||
|
||||
2. EIN SPRACHMODELL ERFINDET. Es schreibt ohne Weiteres „du hast
|
||||
dich diesen Monat deutlich erholt", wenn das Gegenteil dasteht,
|
||||
und es klingt dabei überzeugend. Eine Zusammenfassung über das
|
||||
eigene Befinden, die etwas Falsches behauptet, ist schlimmer
|
||||
als keine: Man glaubt ihr ja, und beim nächsten Mal glaubt man
|
||||
ihr nicht mehr — auch dann nicht, wenn sie recht hat.
|
||||
|
||||
3. SIE FÄLLT AUS. Ein fremder Dienst hat Störungen, Kontingente
|
||||
und Kündigungen. Dieser Text hier funktioniert in zehn Jahren
|
||||
noch, ohne Schlüssel, ohne Rechnung, ohne Netz.
|
||||
|
||||
DIE BESTE KOSTENLOSE MÖGLICHKEIT IST DESHALB KEINE KI, SONDERN
|
||||
SORGFÄLTIG GESCHRIEBENE SPRACHE ÜBER GERECHNETEN ZAHLEN. Sie läuft
|
||||
auf Filipes eigenem Server, kostet nichts, lügt nie und kann nicht
|
||||
ausfallen. Was ein Sprachmodell hier besser könnte — Abwechslung im
|
||||
Wortlaut — ist gegen die drei Punkte oben kein Argument.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
WIE MAN MOTIVIERT, OHNE ZU LÜGEN
|
||||
|
||||
Das ist die eigentliche Schwierigkeit. Eine Zusammenfassung, die bei
|
||||
sechs von acht „da hakt es" fröhlich bleibt, ist keine Ermutigung,
|
||||
sondern Hohn — und beim nächsten Mal antwortet man ihr nicht mehr
|
||||
ehrlich. Drei Regeln, und sie gelten in jeder Lage:
|
||||
|
||||
ZUERST, WAS TRÄGT — und zwar benannt, nicht als Zahl. „Dass du
|
||||
nach der Schicht abschalten kannst, ist das, was das hier
|
||||
zusammenhält" sagt etwas. „5 von 8 grün" sagt nichts.
|
||||
|
||||
DANN HÖCHSTENS EINE SACHE. Wer fünf Probleme auf einmal vorgelegt
|
||||
bekommt, sieht keine fünf Aufgaben, sondern einen Berg. Die Liste
|
||||
aller Punkte steht ohnehin darüber; hier steht die EINE, bei der
|
||||
sich das Hinsehen am meisten lohnt.
|
||||
|
||||
UND EIN SCHLUSS, DER ZUR LAGE PASST. Nicht „weiter so" für alle.
|
||||
Wer gerade viel trägt, braucht einen anderen letzten Satz als
|
||||
jemand, bei dem alles läuft.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
KEINE ZAHLEN, DIE JEMAND PFLEGEN MUSS
|
||||
|
||||
Jede Zahl in diesem Text wird gerechnet — auch „acht Fragen" und
|
||||
„sechs Felder". Ein fester Satz „alle sechs Felder sind ruhig" wäre
|
||||
beim siebten Feld still falsch, und niemand würde es merken.
|
||||
===================================================================== */
|
||||
|
||||
/** Welche Stufe zählt als „läuft", welche als „hakt".
|
||||
*
|
||||
* Abgeleitet aus dem Schlüssel und nicht als eigene Liste gepflegt:
|
||||
* `unklar` ist ausdrücklich WEDER gut noch schlecht — „kann ich nicht
|
||||
* sagen" ist eine ehrliche Antwort und darf nicht als halbes Problem
|
||||
* gezählt werden. Wer sie zu den Sorgen schlägt, erzeugt Sorgen, die
|
||||
* niemand geäußert hat. */
|
||||
const TRAEGT = new Set(["laeuft"]);
|
||||
const WAECHST = new Set(["waechst"]);
|
||||
const HAKT = new Set(["hakt"]);
|
||||
|
||||
/** Ein Satzende ohne doppelten Punkt. */
|
||||
const satz = (t) => {
|
||||
const x = String(t || "").trim();
|
||||
if (!x) return "";
|
||||
return /[.!?…]$/.test(x) ? x : x + ".";
|
||||
};
|
||||
|
||||
/** Der erste Buchstabe klein — für Halbsätze, die angehängt werden.
|
||||
* (Deutsche Substantive bleiben groß; deshalb nur dort benutzt, wo
|
||||
* ein Verb oder Pronomen folgt.) */
|
||||
const klein = (t) => (t ? t[0].toLowerCase() + t.slice(1) : t);
|
||||
|
||||
/**
|
||||
* Die eine Zusammenfassung am Ende einer Runde.
|
||||
*
|
||||
* @param antworten { schluessel: stufe } — die Antworten DIESER Runde
|
||||
* @param punkte Die Fragen dieser Runde (mit titel, feld, kern)
|
||||
* @param felder BEFINDEN_FELDER
|
||||
* @param vorher Die Antworten der VORIGEN Runde, oder null
|
||||
* @returns {{moeglich, kopf, traegt, thema, schluss, zahlen}}
|
||||
*/
|
||||
export function resuemeeVon(antworten, punkte, felder, vorher = null) {
|
||||
const gefragt = punkte.filter((p) => antworten?.[p.schluessel]);
|
||||
|
||||
/* DRITTER AUSGANG. Ohne Antworten gibt es nichts zusammenzufassen --
|
||||
und das muss dastehen, statt dass ein freundlicher Satz über
|
||||
nichts erscheint. */
|
||||
if (!gefragt.length) {
|
||||
return {
|
||||
moeglich: false,
|
||||
grund: "Noch keine Antworten in dieser Runde.",
|
||||
kopf: null, traegt: [], thema: null, schluss: null,
|
||||
zahlen: { gefragt: 0, traegt: 0, waechst: 0, hakt: 0, unklar: 0, felder: 0 },
|
||||
};
|
||||
}
|
||||
|
||||
const mit = (menge) => gefragt.filter((p) => menge.has(antworten[p.schluessel]));
|
||||
const traegtP = mit(TRAEGT);
|
||||
const waechstP = mit(WAECHST);
|
||||
const haktP = mit(HAKT);
|
||||
const unklarP = gefragt.length - traegtP.length - waechstP.length - haktP.length;
|
||||
|
||||
const feldName = (k) => felder.find((f) => f.schluessel === k)?.name || k;
|
||||
|
||||
/* ---- WELCHES FELD IST DAS THEMA? --------------------------------
|
||||
Nicht die erste Frage mit „hakt", sondern das FELD mit den
|
||||
meisten. Drei Haken quer über alles heißen „schwerer Monat";
|
||||
drei Haken in EINEM Feld heißen „hier ist etwas". Nur das zweite
|
||||
ist ein Befund, und nur darüber lohnt ein Satz.
|
||||
|
||||
Bei Gleichstand entscheidet die Reihenfolge der Felder, und die
|
||||
geht von außen nach innen — das schwerere Thema gewinnt. */
|
||||
const proFeld = new Map();
|
||||
for (const p of haktP) {
|
||||
proFeld.set(p.feld, (proFeld.get(p.feld) || 0) + 1);
|
||||
}
|
||||
let themaFeld = null, themaN = 0;
|
||||
for (const f of felder) {
|
||||
const n = proFeld.get(f.schluessel) || 0;
|
||||
if (n > themaN) { themaFeld = f; themaN = n; }
|
||||
}
|
||||
|
||||
/* ---- WAS TRÄGT ---------------------------------------------------
|
||||
Höchstens zwei, und benannt. Eine Aufzählung von fünf Dingen liest
|
||||
niemand; zwei bleiben hängen. Kernfragen zuerst — sie sind die,
|
||||
die in jeder Runde gestellt werden, und damit die, deren „läuft"
|
||||
am meisten bedeutet. */
|
||||
const traegtListe = [...traegtP]
|
||||
.sort((a, b) => (b.kern ? 1 : 0) - (a.kern ? 1 : 0))
|
||||
.slice(0, 2)
|
||||
.map((p) => ({ schluessel: p.schluessel, titel: p.titel, feld: p.feld }));
|
||||
|
||||
/* ---- HAT SICH SEIT DEM LETZTEN MAL ETWAS GEBESSERT? -------------
|
||||
Nur echte Verbesserungen, und nur bei Fragen, die BEIDE Male
|
||||
gestellt wurden. Seit die Fragen rotieren, wäre „war letztes Mal
|
||||
nicht dabei" sonst eine Verbesserung — und das ist keine. */
|
||||
const besser = [];
|
||||
if (vorher) {
|
||||
const rang = { hakt: 0, waechst: 1, laeuft: 2 };
|
||||
for (const p of gefragt) {
|
||||
const a = rang[vorher[p.schluessel]];
|
||||
const b = rang[antworten[p.schluessel]];
|
||||
if (a !== undefined && b !== undefined && b > a) besser.push(p);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- DER KOPFSATZ ------------------------------------------------
|
||||
Vier Lagen, vier Sätze — und keiner davon behauptet etwas, das
|
||||
nicht in den Zahlen steht. */
|
||||
const n = gefragt.length;
|
||||
let kopf;
|
||||
if (!haktP.length && !waechstP.length) {
|
||||
kopf = unklarP === n
|
||||
? "Diesmal konntest du zu fast allem nichts sagen. Das ist eine ehrliche "
|
||||
+ "Antwort – und beim nächsten Mal weißt du vermutlich mehr."
|
||||
: `Alle ${n} Fragen dieser Runde stehen auf „Läuft". Das ist selten, und `
|
||||
+ "es ist kein Zufall – irgendwas machst du gerade richtig.";
|
||||
} else if (!haktP.length) {
|
||||
kopf = `Nichts hakt gerade. ${waechstP.length === 1 ? "Eine Sache wächst" : `${waechstP.length} Sachen wachsen`} `
|
||||
+ "noch – das heißt, sie sind unterwegs, nicht liegengeblieben.";
|
||||
} else if (haktP.length === 1) {
|
||||
kopf = `Von ${n} Fragen hakt genau eine. ${n - 1} laufen oder wachsen – `
|
||||
+ "das ist der Boden, auf dem du stehst, und der ist tragfähig.";
|
||||
} else if (haktP.length < n / 2) {
|
||||
kopf = `${haktP.length} von ${n} Sachen haken gerade. Die anderen ${n - haktP.length} `
|
||||
+ "nicht – und das ist die Hälfte der Auskunft, die man beim Draufschauen "
|
||||
+ "meistens übersieht.";
|
||||
} else {
|
||||
/* DIE SCHWERE LAGE. Hier NICHT aufmunternd werden -- das ist der
|
||||
Moment, in dem eine fröhliche Zusammenfassung alles Vertrauen
|
||||
kostet. Stattdessen: benennen, entlasten, und den Weg zeigen.
|
||||
„Fast nie an dir" ist keine Beschönigung, sondern das, was die
|
||||
Forschung zu Belastung in Ehrenämtern durchgehend findet. */
|
||||
kopf = `${haktP.length} von ${n} Sachen haken. Das ist viel, und es liegt `
|
||||
+ "fast nie an der Person, die es aushält. Es ist ein guter Moment, "
|
||||
+ "das jemandem zu sagen, statt es noch eine Runde mitzunehmen.";
|
||||
}
|
||||
|
||||
/* ---- DER SCHLUSSSATZ ---------------------------------------------
|
||||
Er gehört zur Lage. „Weiter so" für alle wäre ein Satz, der nie
|
||||
jemanden meint. */
|
||||
let schluss;
|
||||
if (besser.length) {
|
||||
schluss = besser.length === 1
|
||||
? `Und etwas hat sich seit dem letzten Mal gebessert: ${klein(besser[0].titel)}. `
|
||||
+ "Das hat sich nicht von allein gemacht."
|
||||
: `Und ${besser.length} Sachen haben sich seit dem letzten Mal gebessert. `
|
||||
+ "Das hat sich nicht von allein gemacht.";
|
||||
} else if (haktP.length >= n / 2) {
|
||||
schluss = "Du musst das hier nicht allein lösen. Dass du es aufgeschrieben "
|
||||
+ "hast, ist der erste Teil – der zweite ist ein Satz an jemanden.";
|
||||
} else if (haktP.length) {
|
||||
schluss = "Das musst du heute nicht lösen. Es reicht, dass du es weißt – "
|
||||
+ "beim nächsten Mal siehst du, ob es geblieben ist.";
|
||||
} else {
|
||||
schluss = "Nächste Runde in zwei Wochen. Bis dahin: nichts zu tun.";
|
||||
}
|
||||
|
||||
return {
|
||||
moeglich: true,
|
||||
kopf,
|
||||
traegt: traegtListe,
|
||||
thema: themaFeld && themaN > 0
|
||||
? {
|
||||
feld: themaFeld.schluessel,
|
||||
name: feldName(themaFeld.schluessel),
|
||||
anzahl: themaN,
|
||||
/* MEHRERE IN EINEM FELD SIND ETWAS ANDERES ALS EINES. Der Satz
|
||||
sagt, welches von beidem es ist -- daran hängt, ob man mit
|
||||
einer Sache spricht oder mit einem Thema. */
|
||||
satz: themaN > 1
|
||||
? satz(`Wenn du nur an einer Stelle hinschaust, dann hier: „${feldName(themaFeld.schluessel)}" `
|
||||
+ `– da stehen ${themaN} der Haken beieinander. Das ist selten Zufall`)
|
||||
: satz(`Wenn du nur an einer Stelle hinschaust, dann hier: „${feldName(themaFeld.schluessel)}"`),
|
||||
titel: haktP.filter((p) => p.feld === themaFeld.schluessel).map((p) => p.titel),
|
||||
}
|
||||
: null,
|
||||
schluss,
|
||||
zahlen: {
|
||||
gefragt: n,
|
||||
traegt: traegtP.length,
|
||||
waechst: waechstP.length,
|
||||
hakt: haktP.length,
|
||||
unklar: unklarP,
|
||||
besser: besser.length,
|
||||
/* GERECHNET, nicht eingetragen: Wer ein siebtes Feld ergänzt,
|
||||
bekommt hier automatisch die 7. */
|
||||
felder: new Set(gefragt.map((p) => p.feld).filter(Boolean)).size,
|
||||
},
|
||||
};
|
||||
}
|
||||
+241
-10
@@ -61,7 +61,7 @@
|
||||
import express from "express";
|
||||
import { db, sitzungLesen, protokolliere, echteIp } from "./workspace.js";
|
||||
import {
|
||||
ENTWICKLUNG_BLOECKE, ENTWICKLUNG_PUNKTE, ENTWICKLUNG_STUFEN,
|
||||
ENTWICKLUNG_BLOECKE, ENTWICKLUNG_PUNKTE, ENTWICKLUNG_STUFEN, BEFINDEN_FELDER,
|
||||
} from "./workspace-entwicklungs-punkte.js";
|
||||
import { tabellen as entwicklungTabellen } from "./workspace-entwicklung.js";
|
||||
|
||||
@@ -78,15 +78,27 @@ export const RHYTHMUS_TAGE = 14;
|
||||
|
||||
/** Wie viele Zusatzfragen je Runde zum Kern dazukommen.
|
||||
*
|
||||
* DREI. Mit fünf Kernfragen macht das acht je Runde — die Quellen zu
|
||||
* VIER. Mit fünf Kernfragen macht das neun je Runde — die Quellen zu
|
||||
* Puls-Abfragen sagen übereinstimmend fünf bis fünfzehn, unter zwei
|
||||
* Minuten. Acht liegt mittig und lässt Platz: Der Katalog kann auf
|
||||
* zwanzig Fragen wachsen, ohne dass eine einzelne Runde länger wird.
|
||||
* Minuten. Neun liegt mittig und lässt Platz: Der Katalog kann weiter
|
||||
* wachsen, ohne dass eine einzelne Runde länger wird.
|
||||
*
|
||||
* Das ist der ganze Trick an Filipes zwei Wünschen, die sich zu
|
||||
* widersprechen schienen — „viel mehr Fragen" und „kurz und knapp".
|
||||
* Beides geht, nur nicht gleichzeitig in DERSELBEN Runde. */
|
||||
export const ZUSATZ_PRO_RUNDE = 3;
|
||||
* Beides geht, nur nicht gleichzeitig in DERSELBEN Runde.
|
||||
*
|
||||
* WARUM VIER UND NICHT DREI (geändert 20.09.2026, mit den Feldern).
|
||||
* Drei war richtig, solange es sieben wechselnde Fragen gab. Mit
|
||||
* dreizehn dauerte es bei drei je Runde ZEHN Runden, bis jede einmal
|
||||
* gestellt war — fast ein halbes Jahr, in dem eine Frage im Katalog
|
||||
* steht und nie jemandem begegnet. Bei vier sind es vier Runden, also
|
||||
* acht Wochen. Gemessen, nicht geschätzt; die Zahl steht in keiner
|
||||
* Behauptung, sondern in pruef-befinden.mjs.
|
||||
*
|
||||
* Dieselbe Rechnung wieder anstellen, sobald der Katalog wächst:
|
||||
* ceil(wechselnde / ZUSATZ_PRO_RUNDE) ist die Zahl der Runden, und
|
||||
* mehr als fünf davon sind zu viele. */
|
||||
export const ZUSATZ_PRO_RUNDE = 4;
|
||||
|
||||
/** So viele frühere Antworten bekommt die Person zu sehen.
|
||||
*
|
||||
@@ -258,12 +270,89 @@ export function fragenFuer(personId, runden = null) {
|
||||
return { kern, zusatz: rest, alle: [...kern, ...rest], runden: n };
|
||||
}
|
||||
|
||||
let schritt = ZUSATZ_PRO_RUNDE;
|
||||
while (ggt(schritt, rest.length) !== 1) schritt++;
|
||||
/* =====================================================================
|
||||
DER RING IM REISSVERSCHLUSS (20.09.2026)
|
||||
|
||||
const start = (n * schritt) % rest.length;
|
||||
Seit die Fragen in sechs Felder gegliedert sind, reicht ein
|
||||
einfacher Ring nicht mehr. Der Katalog ist nach Themen sortiert --
|
||||
wer daraus drei AUFEINANDERFOLGENDE nimmt, bekommt meistens drei
|
||||
aus demselben Feld. Eine Runde, die nur nach „Klarheit & Rueckhalt"
|
||||
fragt, ist keine Befindensabfrage mehr, sondern ein Interview zu
|
||||
einem Thema.
|
||||
|
||||
DER RING WIRD DESHALB VORHER VERZAHNT: erst je eine Frage aus
|
||||
jedem Feld, dann je eine zweite, dann je eine dritte. Ein Fenster
|
||||
von vier aufeinanderfolgenden Eintraegen trifft damit von selbst
|
||||
vier verschiedene Felder -- ohne dass irgendwo eine Regel
|
||||
nachrechnen muss, welche schon dran waren.
|
||||
|
||||
DIE FELDER OHNE KERNFRAGE STEHEN IM REISSVERSCHLUSS VORN. Sie sind
|
||||
die einzigen, die eine Runde lang ganz fehlen KOENNTEN -- die
|
||||
anderen sind ueber ihre Kernfrage ohnehin in jeder Runde dabei.
|
||||
Gemessen ueber zwanzig Runden: 19 davon enthalten mindestens eine
|
||||
Frage aus einem kernlosen Feld.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
WAS HIER VORHER STAND -- UND WARUM ES WEG IST
|
||||
|
||||
Der erste Anlauf reservierte JEDER Runde feste Plaetze fuer die
|
||||
kernlosen Felder. Begruendet war das mit „sonst stuende in der
|
||||
Auswertung nach Feldern ein leeres Kapitel, und das liest sich wie
|
||||
Zustimmung".
|
||||
|
||||
Die Begruendung war falsch: Es gibt gar keine Auswertung je Feld.
|
||||
Filipe wollte ausdruecklich EINE Zusammenfassung am Ende, nicht
|
||||
eine je Kategorie -- und die sucht sich ihr Thema aus dem, was
|
||||
tatsaechlich gefragt wurde. Ein Feld, das diese Runde nicht
|
||||
drankam, behauptet also gar nichts.
|
||||
|
||||
Gekostet hat die Reservierung dafuer etwas Messbares: Zwei von
|
||||
drei Plaetzen waren belegt, der Ring kam kaum voran, und bis jede
|
||||
der 13 wechselnden Fragen einmal gestellt war, vergingen ZWOELF
|
||||
Runden -- fast ein halbes Jahr. Gefunden hat das nicht das
|
||||
Nachdenken, sondern `pruef-befinden.mjs`: Sie verlangt, dass ueber
|
||||
wenige Runden jede Frage drankommt, und wurde rot.
|
||||
|
||||
Mit dem verzahnten Ring sind es vier Runden (acht Wochen), bei
|
||||
gleichzeitig besserer Streuung. Beides gemessen, nicht geschaetzt.
|
||||
===================================================================== */
|
||||
const felderMitKern = new Set(kern.map((p2) => p2.feld).filter(Boolean));
|
||||
|
||||
/* Die Feldreihenfolge: kernlose zuerst, sonst wie im Katalog. Ein
|
||||
stabiles Sortieren -- `sort` in JavaScript ist seit ES2019
|
||||
garantiert stabil, die Katalogreihenfolge bleibt also innerhalb
|
||||
der beiden Gruppen erhalten. */
|
||||
const feldReihe = [...new Set(rest.map((p2) => p2.feld).filter(Boolean))]
|
||||
.sort((a, b) => (felderMitKern.has(a) ? 1 : 0) - (felderMitKern.has(b) ? 1 : 0));
|
||||
|
||||
const koerbe = feldReihe.map((f) => rest.filter((p2) => p2.feld === f))
|
||||
.filter((k) => k.length);
|
||||
/* Fragen ganz ohne Feld kaemen sonst nie dran -- sie bekommen einen
|
||||
eigenen Korb. Abgeleitet, nicht angenommen. */
|
||||
const ohneFeld = rest.filter((p2) => !p2.feld);
|
||||
if (ohneFeld.length) koerbe.push(ohneFeld);
|
||||
|
||||
const ring = [];
|
||||
const tiefe = koerbe.reduce((m, k) => Math.max(m, k.length), 0);
|
||||
for (let i = 0; i < tiefe; i++) {
|
||||
for (const k of koerbe) if (k[i]) ring.push(k[i]);
|
||||
}
|
||||
|
||||
/* Der Versatz muss zur Ringlaenge teilerfremd sein, sonst laeuft der
|
||||
Kreis nie ganz durch -- dieselbe Ueberlegung wie vorher, nur jetzt
|
||||
auf dem verzahnten Ring. */
|
||||
let schritt = ZUSATZ_PRO_RUNDE;
|
||||
while (ggt(schritt, ring.length) !== 1) schritt++;
|
||||
|
||||
const start = (n * schritt) % ring.length;
|
||||
const zusatz = [];
|
||||
for (let i = 0; i < ZUSATZ_PRO_RUNDE; i++) zusatz.push(rest[(start + i) % rest.length]);
|
||||
const schonDrin = new Set();
|
||||
for (let i = 0; i < ring.length && zusatz.length < ZUSATZ_PRO_RUNDE; i++) {
|
||||
const w = ring[(start + i) % ring.length];
|
||||
if (schonDrin.has(w.schluessel)) continue;
|
||||
schonDrin.add(w.schluessel);
|
||||
zusatz.push(w);
|
||||
}
|
||||
|
||||
/* IN KATALOGREIHENFOLGE AUSGELIEFERT, nicht in Rotationsreihenfolge.
|
||||
Der Katalog ist nach Thema sortiert; wer ihn jede Runde in einer
|
||||
@@ -281,6 +370,8 @@ export function fragenFuer(personId, runden = null) {
|
||||
DIE SEITE
|
||||
===================================================================== */
|
||||
|
||||
import { resuemeeVon } from "./workspace-befinden-resuemee.js";
|
||||
|
||||
befindenRouter.get("/workspace/api/befinden", angemeldet, (req, res) => {
|
||||
try {
|
||||
const b = block();
|
||||
@@ -350,9 +441,86 @@ befindenRouter.get("/workspace/api/befinden", angemeldet, (req, res) => {
|
||||
};
|
||||
});
|
||||
|
||||
/* ==== DIE EINE ZUSAMMENFASSUNG AM ENDE (20.09.2026) ==============
|
||||
|
||||
Filipe: "am ende von allen fragen will ich eine gesamte
|
||||
zusammenfassung, nicht bei jeder frage oder kategorie eine,
|
||||
sondern EINE gesamte am ende. eine motivierende zusammenfassung
|
||||
fuer die person."
|
||||
|
||||
SIE WIRD AUS DEN FRISCHEN ANTWORTEN GERECHNET, nicht aus allen
|
||||
in `entwicklung_stand`. Der Unterschied ist derselbe wie eine
|
||||
Bildschirmseite weiter oben: Eine Zusammenfassung ueber alte
|
||||
Antworten sagt nicht "so geht es dir", sondern "so ging es dir
|
||||
irgendwann mal", und man merkt es nicht.
|
||||
|
||||
UND SIE BRAUCHT KEINE ZWEITE RUNDE. Die Musteranalyse darunter
|
||||
schon (ein Punkt ist keine Entwicklung) -- diese hier ist die
|
||||
Antwort auf das, was gerade dasteht, und die gibt es beim ersten
|
||||
Mal genauso.
|
||||
|
||||
DIE VORIGE RUNDE KOMMT MIT, wenn es eine gibt: Nur dann kann
|
||||
"hat sich gebessert" ueberhaupt gesagt werden, und nur fuer
|
||||
Fragen, die BEIDE Male dran waren. Seit die Fragen rotieren,
|
||||
waere "war letztes Mal nicht dabei" sonst eine Verbesserung. */
|
||||
const frischeAntworten = Object.fromEntries(
|
||||
punkte.filter((pt) => pt.frisch && pt.meins)
|
||||
.map((pt) => [pt.schluessel, pt.meins.stufe]));
|
||||
|
||||
/* WELCHE ANTWORTEN DIE ZUSAMMENFASSUNG MEINT -- und hier steckte
|
||||
ein Fehler, den die Pruefung gefunden hat, nicht das Nachdenken.
|
||||
|
||||
Erster Anlauf: nur die FRISCHEN, also die nach der letzten Runde
|
||||
gesetzten. Das ist waehrend einer laufenden Runde richtig -- und
|
||||
ausgerechnet in dem Moment falsch, in dem man die Zusammenfassung
|
||||
sehen will: DIREKT NACH dem Abschliessen einer Runde ist keine
|
||||
einzige Antwort mehr frisch. Man hat sie ja Sekunden vorher
|
||||
gesetzt, also VOR dem Abschluss. Die Seite zeigte nach dem
|
||||
Abschliessen: nichts.
|
||||
|
||||
(Derselbe Sachverhalt steht schon beim Hinweis „das war deine
|
||||
Antwort beim letzten Mal" in befinden.js -- dort wurde er
|
||||
beruecksichtigt, hier nicht. Eine Erkenntnis, die an einer
|
||||
Stelle notiert ist, wirkt nicht von selbst an der anderen.)
|
||||
|
||||
RICHTIG IST BEIDES, in dieser Rangfolge:
|
||||
laeuft gerade eine Runde -> die frischen Antworten
|
||||
gerade abgeschlossen -> die letzte Runde aus dem Verlauf
|
||||
|
||||
Der Vergleich rutscht entsprechend mit: Wird die laufende Runde
|
||||
gezeigt, ist die letzte abgeschlossene der Vergleich; wird die
|
||||
letzte abgeschlossene gezeigt, die vorletzte. Sonst verglichen
|
||||
wir eine Runde mit sich selbst -- und "nichts hat sich
|
||||
geaendert" waere dann keine Auskunft, sondern ein Rechenfehler. */
|
||||
const laeuftRunde = Object.keys(frischeAntworten).length > 0;
|
||||
const fazitAntworten = laeuftRunde
|
||||
? frischeAntworten
|
||||
: (verlauf[0]?.antworten || {});
|
||||
const fazitVergleich = laeuftRunde
|
||||
? (verlauf[0]?.antworten || null)
|
||||
: (verlauf[1]?.antworten || null);
|
||||
/* DIE FRAGEN DAZU aus dem KATALOG, nicht aus `diese.alle`: Eine
|
||||
abgeschlossene Runde kann andere Fragen enthalten haben als die,
|
||||
die gerade dran waeren -- die Zusatzfragen wechseln ja. Wer
|
||||
stattdessen die aktuellen nimmt, fasst Antworten zusammen, die es
|
||||
zu diesen Fragen gar nicht gibt. */
|
||||
const fazitPunkte = (ENTWICKLUNG_PUNKTE[b.schluessel] || [])
|
||||
.filter((pt) => fazitAntworten[pt.schluessel]);
|
||||
|
||||
res.json({
|
||||
block: b,
|
||||
stufen: ENTWICKLUNG_STUFEN,
|
||||
/* DIE FELDER. Sie stehen im Katalog, nicht im Browser -- eine
|
||||
zweite Liste dort waere die, die beim siebten Feld vergessen
|
||||
wird. */
|
||||
felder: BEFINDEN_FELDER,
|
||||
resuemee: resuemeeVon(fazitAntworten, fazitPunkte, BEFINDEN_FELDER,
|
||||
fazitVergleich),
|
||||
/* WORAUF SIE SICH BEZIEHT -- die Seite sagt das dazu. „Was das
|
||||
zusammen heisst" ueber eine Runde von vorgestern ist etwas
|
||||
anderes als ueber die, die man gerade ausfuellt, und wer das
|
||||
nicht weiss, bezieht den Satz auf den falschen Tag. */
|
||||
resuemee_stand: laeuftRunde ? "laufend" : (verlauf[0] ? "letzte" : "keine"),
|
||||
rhythmus_tage: RHYTHMUS_TAGE,
|
||||
rhythmus: takt,
|
||||
punkte,
|
||||
@@ -622,6 +790,69 @@ export const EINORDNUNG = {
|
||||
hilft: "Die billigste Maßnahme überhaupt und die mit der besten Wirkung: "
|
||||
+ "ausdrücklich danken, für etwas Bestimmtes, nicht allgemein.",
|
||||
},
|
||||
|
||||
/* ---- Die sechs vom 20.09.2026 ------------------------------------
|
||||
Sie schließen drei Lücken, die die Liste vorher hatte: Schlaf,
|
||||
Widersprechen und Mitreden. Jede bekommt hier ihre Einordnung --
|
||||
eine Frage ohne Grundlage ist eine Frage, die jemand gut fand, und
|
||||
davon gibt es in Befindensabfragen genug. */
|
||||
"e-ich-schlaf": {
|
||||
welt: "Schlafstörungen sind in der Erschöpfungsforschung das Zeichen, das "
|
||||
+ "AM FRÜHESTEN kommt – vor dem Leistungsabfall, vor dem Zynismus, vor "
|
||||
+ "dem Gedanken ans Aufhören. Wer nachts wach liegt und an eine Schicht "
|
||||
+ "denkt, hat das Problem noch nicht bei der Arbeit, aber schon im Kopf.",
|
||||
hilft: "Nicht weniger arbeiten, sondern klarer aufhören: ein festes Ende, "
|
||||
+ "Meldungen aus, und etwas dazwischen, das nichts mit dem Stream zu tun "
|
||||
+ "hat. Das Gehirn braucht ein Signal, dass die Schicht vorbei ist – die "
|
||||
+ "Uhrzeit allein ist keins.",
|
||||
},
|
||||
"e-ich-widersprechen": {
|
||||
welt: "Ob man widersprechen kann, ohne dass es unangenehm wird, ist der Kern "
|
||||
+ "dessen, was die Forschung psychologische Sicherheit nennt – der am "
|
||||
+ "besten belegte Einzelfaktor dafür, ob ein Team gut arbeitet. Ein Team, "
|
||||
+ "in dem niemand widerspricht, ist selten einig; meistens ist es still.",
|
||||
hilft: "Es liegt fast nie an der Person, die schweigt. Was hilft: dass die "
|
||||
+ "Leitung selbst widerspricht und widersprochen wird, sichtbar, und dass "
|
||||
+ "ein Einwand einmal ausdrücklich gedankt wird statt nur abgehakt.",
|
||||
},
|
||||
"e-ich-mitreden": {
|
||||
welt: "Viel Anforderung bei wenig Einfluss ist die Kombination, aus der "
|
||||
+ "Erschöpfung entsteht – nicht viel Arbeit allein. Das Modell dahinter "
|
||||
+ "(Job Demands-Resources) ist seit Jahrzehnten eines der am besten "
|
||||
+ "geprüften der Arbeitspsychologie, und Einfluss ist darin die "
|
||||
+ "wirksamste einzelne Entlastung.",
|
||||
hilft: "Es muss nicht Mitbestimmung über alles sein. Es reicht meistens, "
|
||||
+ "über das Eigene zu entscheiden: wann, wie lange, in welcher "
|
||||
+ "Reihenfolge. Wer das abgibt, gibt wenig ab und nimmt viel Last.",
|
||||
},
|
||||
"e-ich-fehler-ok": {
|
||||
welt: "Was zählt, ist nicht, ob Fehler passieren, sondern ob man sie "
|
||||
+ "erfährt. In Teams, in denen Fehler teuer sind, werden sie nicht "
|
||||
+ "seltener – sie werden unsichtbar, und die Folgekosten trägt der, dem "
|
||||
+ "er passiert ist, allein.",
|
||||
hilft: "Der erste Fehler, den jemand aus der Leitung selbst erzählt, wirkt "
|
||||
+ "mehr als jede Zusage. Und: beim nächsten gemeldeten Fehler zuerst "
|
||||
+ "fragen, was er über den ABLAUF sagt, nicht über die Person.",
|
||||
},
|
||||
"e-ich-dazugehoeren": {
|
||||
welt: "Anschluss ans Team ist in den Untersuchungen zu Moderatoren-Belastung "
|
||||
+ "der STÄRKSTE einzelne Schutzfaktor – stärker als kürzere Schichten und "
|
||||
+ "stärker als bessere Werkzeuge. Nützlich zu sein und dazuzugehören sind "
|
||||
+ "dabei zwei verschiedene Dinge, und nur das zweite hält.",
|
||||
hilft: "Es entsteht neben der Arbeit, nicht in ihr: ein Kanal, in dem es "
|
||||
+ "nicht um Schichten geht, ein gemeinsamer Termin ohne Zweck. Wer nur "
|
||||
+ "zum Arbeiten da ist, geht, sobald die Arbeit unangenehm wird.",
|
||||
},
|
||||
"e-ich-stolz": {
|
||||
welt: "Die einzige Frage der Liste, die zurückschaut. Wer sich an nichts "
|
||||
+ "erinnert, hat selten nichts geschafft – es hat nur niemand benannt. "
|
||||
+ "Das ist die gleiche Lücke wie bei der Anerkennung, nur von innen: "
|
||||
+ "Moderationsarbeit besteht zum großen Teil aus dem, was NICHT passiert "
|
||||
+ "ist, und daran erinnert sich niemand von selbst.",
|
||||
hilft: "Aufschreiben, sobald es passiert – eine Zeile reicht. Im Workspace "
|
||||
+ "ist dafür das Highlight-Brett da. Was nirgends steht, ist nach zwei "
|
||||
+ "Wochen weg, auch wenn es gut war.",
|
||||
},
|
||||
};
|
||||
|
||||
/** Das Muster einer Frage ueber die Runden hinweg -- neueste zuerst. */
|
||||
|
||||
@@ -64,7 +64,7 @@ import {
|
||||
TREFF_BRETTER, TREFF_ROLLEN, AUSSEN_ROLLEN,
|
||||
heuteLokal,
|
||||
gehoertAufDieseAdresse,
|
||||
bereicheFuer,
|
||||
bereicheFuer, kanaeleFuer, KANAELE,
|
||||
} from "./workspace.js";
|
||||
import { nachrichtSchicken } from "./workspace-chat.js";
|
||||
import {
|
||||
@@ -1500,6 +1500,22 @@ bereicheRouter.get("/workspace/api/bereich/:bereich", (req, res) => {
|
||||
weg. */
|
||||
kalender_offen: darfSeite(req.person, "/workspace/kalender.html")
|
||||
&& gehoertAufDieseAdresse(req.person, "/workspace/kalender.html"),
|
||||
/* DIE KANAELE KOMMEN VOM SERVER (20.09.2026).
|
||||
|
||||
Filipe: "die highlights sollen in 3 kategorien sein
|
||||
nebeneinander, dogfather, hasidog und dogfather clips."
|
||||
|
||||
Der Browser hatte dafuer bisher eine eigene kleine Liste
|
||||
(`KANAL_WORT` in bereich.js) -- in anderer Reihenfolge und
|
||||
ohne die Handles. Eine zweite Fassung derselben Tatsache ist
|
||||
die, die beim vierten Account vergessen wird; genau davor
|
||||
warnt der Kommentar an KANAELE selbst. Deshalb kommt sie von
|
||||
hier, samt Reihenfolge: DogFather, HasiDog, Clips.
|
||||
|
||||
`kanaeleFuer` und nicht `KANAELE`: Wer nicht zum Team gehoert,
|
||||
hat mit den Accounts nichts zu tun und bekommt die Liste gar
|
||||
nicht erst. */
|
||||
kanaele: kanaeleFuer(req.person),
|
||||
eintraege: treffBrett ? eintraege.map(mitRollenname) : eintraege,
|
||||
});
|
||||
} catch (fehler) {
|
||||
@@ -1760,6 +1776,32 @@ function pruefe(bereich, körper, { neu }) {
|
||||
else aus.einsatz = t || null;
|
||||
}
|
||||
|
||||
/* ---- ZU WELCHEM ACCOUNT? (20.09.2026) ---------------------------
|
||||
|
||||
Filipe: "und wenn man was hinzufuegt soll man auch den account
|
||||
auswaehlen koennen."
|
||||
|
||||
NUR AUF DEN HIGHLIGHTS und nur gegen die echte Kanalliste geprueft.
|
||||
Ein Aufruf an der Oberflaeche vorbei soll keinen erfundenen Kanal
|
||||
in die Spalte schreiben koennen -- danach stuende in der Galerie
|
||||
eine vierte Spalte, die es nicht gibt, und niemand faende heraus,
|
||||
woher sie kommt. Eine Regel, die nur im Formular steht, ist keine
|
||||
Regel (siehe der Absatz gleich darunter).
|
||||
|
||||
KEIN PFLICHTFELD: Leer bedeutet "noch offen" -- ein falscher
|
||||
Eintrag ist schlechter als ein leerer, genau wie an KANAELE
|
||||
vermerkt. Und wer einen TikTok-Link einfuegt, bekommt den Kanal
|
||||
ohnehin aus dem Handle abgeleitet; DAS ist genauer als jede
|
||||
Auswahl, weil niemand sich vertippen kann. Diese Zeilen
|
||||
ueberschreiben das nicht: Die Video-Route setzt `quelle_kanal`
|
||||
selbst und geht hier nicht durch. */
|
||||
if (bereich === "highlight" && körper.quelle_kanal !== undefined) {
|
||||
const w = String(körper.quelle_kanal ?? "").trim();
|
||||
if (!w) aus.quelle_kanal = null;
|
||||
else if (KANAELE.some((k) => k.wert === w)) aus.quelle_kanal = w;
|
||||
else fehler.push("Diesen Account gibt es nicht.");
|
||||
}
|
||||
|
||||
/* Ein Agentur-Eintrag gehoert NIEMANDEM einzeln (07.09.2026).
|
||||
|
||||
Er gilt fuer alle, und genau deshalb sieht ihn auch jeder (siehe
|
||||
|
||||
@@ -61,6 +61,66 @@
|
||||
* man im Zweifel "laeuft", weil das die freundlichste Taste ist --
|
||||
* und ab da steht im Katalog eine Beobachtung, die niemand gemacht
|
||||
* hat. */
|
||||
/* =====================================================================
|
||||
DIE SECHS FELDER DER BEFINDENSFRAGEN (20.09.2026)
|
||||
|
||||
Filipe: „bei der frage wie geht es dir will ich viel mehr fragen
|
||||
aber kategorisiert und so. psychologisch hochprofessionell."
|
||||
|
||||
---------------------------------------------------------------------
|
||||
WARUM KATEGORIEN UND NICHT NUR MEHR FRAGEN
|
||||
|
||||
Achtzehn Fragen in einer Reihe sind ein Fragebogen; achtzehn Fragen
|
||||
in sechs Feldern sind ein Gespraech mit sechs Themen. Der Unterschied
|
||||
ist nicht kosmetisch:
|
||||
|
||||
* BEIM ANTWORTEN gibt eine Ueberschrift den Rahmen. „Ich kann nein
|
||||
sagen" heisst in „Last & Erholung" etwas anderes als in
|
||||
„Miteinander" -- die Ueberschrift beantwortet die Rueckfrage,
|
||||
bevor sie entsteht.
|
||||
* BEIM AUSWERTEN ist das Feld die Aussage. Drei Haken quer ueber
|
||||
alles heissen „schwerer Monat"; drei Haken in EINEM Feld heissen
|
||||
„hier ist etwas". Das erste ist ein Gefuehl, das zweite ein
|
||||
Befund.
|
||||
|
||||
WOHER DIE SECHS KOMMEN -- nicht ausgedacht. Die Befindensforschung
|
||||
zu ehrenamtlichen und hochbelasteten Rollen fragt durchgehend
|
||||
dieselben Gebiete ab (Job-Demands-Resources-Modell, Maslach-Modell
|
||||
zur Erschoepfung, Copenhagen Psychosocial Questionnaire). Zusammen
|
||||
sind es sechs, und genau die stehen hier:
|
||||
|
||||
last Anforderung und Erholung -- wie viel, und hoert es auf
|
||||
zusammen Das Miteinander und die psychologische Sicherheit
|
||||
sinn Sinn und Anerkennung -- wofuer, und merkt es jemand
|
||||
halt Klarheit und Rueckhalt -- weiss ich, was gilt und wer hilft
|
||||
schutz Grenzen und Sicherheit -- bleibt das Private privat
|
||||
vorne Weiterkommen und Ausblick -- lohnt es sich auf Sicht
|
||||
|
||||
KEIN FELD OHNE KERNFRAGE. Ein Feld, das nur rotierende Fragen
|
||||
enthaelt, kann eine ganze Runde lang stumm bleiben -- und dann steht
|
||||
in der Auswertung ein leeres Kapitel, das nach Zustimmung aussieht.
|
||||
`pruef-befinden.mjs` weist das nach, statt es zu behaupten.
|
||||
|
||||
DIE REIHENFOLGE IST NICHT BELIEBIG: Sie geht von aussen nach innen.
|
||||
„Wie viel ist es" laesst sich leicht beantworten; „bleibe ich" ist
|
||||
die schwerste Frage der Liste und steht deshalb zuletzt -- wer bis
|
||||
dahin gekommen ist, hat sich eingelassen.
|
||||
===================================================================== */
|
||||
export const BEFINDEN_FELDER = [
|
||||
{ schluessel: "last", name: "Last & Erholung", zeichen: "\u25d0",
|
||||
text: "Wie viel es ist – und ob es aufhört, wenn du aufhörst." },
|
||||
{ schluessel: "zusammen", name: "Miteinander", zeichen: "\u25c7",
|
||||
text: "Wie es sich mit den anderen anfühlt." },
|
||||
{ schluessel: "sinn", name: "Sinn & Anerkennung", zeichen: "\u25b3",
|
||||
text: "Wofür das Ganze – und ob es jemandem auffällt." },
|
||||
{ schluessel: "halt", name: "Klarheit & Rückhalt", zeichen: "\u25a1",
|
||||
text: "Ob du weißt, was gilt, und wer da ist, wenn es eng wird." },
|
||||
{ schluessel: "schutz", name: "Grenzen & Sicherheit", zeichen: "\u25cb",
|
||||
text: "Ob das Private privat bleibt." },
|
||||
{ schluessel: "vorne", name: "Weiterkommen & Ausblick", zeichen: "\u25b7",
|
||||
text: "Ob es sich auf Sicht lohnt." },
|
||||
];
|
||||
|
||||
export const ENTWICKLUNG_STUFEN = [
|
||||
{ schluessel: "laeuft", name: "Läuft", zeichen: "✓",
|
||||
text: "Habe ich gesehen. Passt." },
|
||||
@@ -437,25 +497,25 @@ export const ENTWICKLUNG_PUNKTE = {
|
||||
bei jedem Neuladen eine andere Liste, und eine Seite, die sich
|
||||
unter den Fingern aendert, beantwortet niemand zu Ende. */
|
||||
befinden: [
|
||||
{ schluessel: "e-ich-menge-passt", stufe: "probe", kern: true,
|
||||
{ schluessel: "e-ich-menge-passt", feld: "last", stufe: "probe", kern: true,
|
||||
/* KERN: Die meistgenannte Ursache ueberhaupt. Wer sie nur jede dritte Runde fragt, erfaehrt sie im Schnitt sechs Wochen zu spaet. */
|
||||
titel: "Die Menge passt gerade",
|
||||
text: "Zu wenig Zeit ist der meistgenannte Grund, warum Moderatoren aufhören." },
|
||||
{ schluessel: "e-ich-niemand-schwierig", stufe: "probe", kern: true,
|
||||
{ schluessel: "e-ich-niemand-schwierig", feld: "zusammen", stufe: "probe", kern: true,
|
||||
/* KERN: Die zweithaeufigste -- und die, ueber die von selbst am wenigsten geredet wird. Genau deshalb muss sie gefragt werden. */
|
||||
titel: "Es gibt gerade niemanden im Team, mit dem es schwierig ist",
|
||||
text: "Der zweithäufigste Grund – und der, über den am wenigsten geredet wird." },
|
||||
{ schluessel: "e-ich-weiss-an-wen", stufe: "probe",
|
||||
{ schluessel: "e-ich-weiss-an-wen", feld: "halt", stufe: "probe",
|
||||
titel: "Ich weiß, an wen ich mich wende, wenn etwas eskaliert",
|
||||
text: "Ein fehlender Eskalationsweg fühlt sich nicht wie ein fehlender Weg an, "
|
||||
+ "sondern wie Unsicherheit." },
|
||||
{ schluessel: "e-ich-jemand-zum-reden", stufe: "probe",
|
||||
{ schluessel: "e-ich-jemand-zum-reden", feld: "zusammen", stufe: "probe",
|
||||
titel: "Nach einer hässlichen Schicht gibt es jemanden zum Reden",
|
||||
text: "Wer Hässliches wegmoderiert, nimmt es mit. Das geht nicht von allein weg." },
|
||||
{ schluessel: "e-ich-nein-ohne-problem", stufe: "probe",
|
||||
{ schluessel: "e-ich-nein-ohne-problem", feld: "halt", stufe: "probe",
|
||||
titel: "Ich kann nein sagen, ohne dass es ein Problem ist",
|
||||
text: "Nicht ob du darfst – ob es sich so anfühlt. Das ist der Unterschied." },
|
||||
{ schluessel: "e-ich-faellt-auf", stufe: "probe",
|
||||
{ schluessel: "e-ich-faellt-auf", feld: "sinn", stufe: "probe",
|
||||
titel: "Ich habe das Gefühl, dass es auffällt, wenn ich da bin",
|
||||
text: "Fehlende Anerkennung ist der am häufigsten genannte emotionale Preis – "
|
||||
+ "und der billigste zu beheben." },
|
||||
@@ -466,33 +526,100 @@ export const ENTWICKLUNG_PUNKTE = {
|
||||
ALLE AUF "probe": Diese Fragen sind keine Stufenleiter. Sie
|
||||
gelten am ersten Tag genauso wie im dritten Jahr -- ein
|
||||
Befinden hat keine Erwartungsstufe. */
|
||||
{ schluessel: "e-ich-abschalten", stufe: "probe", kern: true,
|
||||
{ schluessel: "e-ich-abschalten", feld: "last", stufe: "probe", kern: true,
|
||||
/* KERN: Ob die Erholung traegt, sagt besser voraus, ob jemand aufhoert, als die Menge der Arbeit. */
|
||||
titel: "Nach einer Schicht kann ich abschalten",
|
||||
text: "Nicht ob du müde bist – ob es aufhört, wenn du aufhörst. "
|
||||
+ "Das ist der Unterschied zwischen anstrengend und zehrend." },
|
||||
{ schluessel: "e-ich-sinn", stufe: "probe",
|
||||
{ schluessel: "e-ich-sinn", feld: "sinn", stufe: "probe",
|
||||
titel: "Ich habe das Gefühl, dass meine Arbeit hier etwas bewirkt",
|
||||
text: "Wer nicht sieht, was seine Arbeit bewirkt, hört auf – auch wenn "
|
||||
+ "alles andere stimmt. Es ist der stillste Grund von allen." },
|
||||
{ schluessel: "e-ich-klar-was-erwartet", stufe: "probe",
|
||||
{ schluessel: "e-ich-klar-was-erwartet", feld: "halt", stufe: "probe",
|
||||
titel: "Mir ist klar, was von mir erwartet wird",
|
||||
text: "Unklarheit fühlt sich nicht wie Unklarheit an, sondern wie "
|
||||
+ "das Gefühl, es nie ganz richtig zu machen." },
|
||||
{ schluessel: "e-ich-nicht-angefeindet", stufe: "probe", kern: true,
|
||||
{ schluessel: "e-ich-nicht-angefeindet", feld: "schutz", stufe: "probe", kern: true,
|
||||
/* KERN: Der Punkt, an dem aus Belastung eine Gefahr wird. Er darf nie in einer Rotation warten. */
|
||||
titel: "Ich werde außerhalb des Streams in Ruhe gelassen",
|
||||
text: "Privatnachrichten, andere Plattformen, das Privatleben. Wer hier "
|
||||
+ "„da hakt es“ setzt, braucht keine Umfrage, sondern ein Gespräch." },
|
||||
{ schluessel: "e-ich-lerne-dazu", stufe: "probe",
|
||||
{ schluessel: "e-ich-lerne-dazu", feld: "vorne", stufe: "probe",
|
||||
titel: "Ich lerne hier etwas, das mir auch sonst nützt",
|
||||
text: "Ehrenamt hält, wenn es etwas zurückgibt. Das muss kein Geld sein – "
|
||||
+ "aber es muss etwas sein." },
|
||||
{ schluessel: "e-ich-naechste-monate", stufe: "probe", kern: true,
|
||||
{ schluessel: "e-ich-naechste-monate", feld: "vorne", stufe: "probe", kern: true,
|
||||
/* KERN: Der beste einzelne Fruehindikator, den Befindensabfragen kennen -- er schlaegt an, bevor die anderen es tun. */
|
||||
titel: "Ich kann mir gut vorstellen, in ein paar Monaten noch dabei zu sein",
|
||||
text: "Die ehrlichste Frage der Liste. Wer hier zögert, hat es meistens "
|
||||
+ "vorher schon gewusst – und niemand hat gefragt." },
|
||||
|
||||
/* =================================================================
|
||||
SECHS WEITERE (20.09.2026)
|
||||
|
||||
Filipe: „bei der frage wie geht es dir will ich viel mehr fragen
|
||||
aber kategorisiert und so. psychologisch hochprofessionell."
|
||||
|
||||
WAS SIE SCHLIESSEN, IST NICHT „noch ein bisschen mehr", sondern
|
||||
drei echte blinde Flecken -- und alle drei sind in der Forschung
|
||||
zu belasteten Rollen ueblich, in dieser Liste bis heute aber
|
||||
nicht vertreten:
|
||||
|
||||
* SCHLAF. Erschoepfung zeigt sich zuerst nachts, nicht bei der
|
||||
Arbeit. Wer wach liegt und an eine Schicht denkt, merkt es
|
||||
dort Wochen bevor die Arbeit darunter leidet.
|
||||
* WIDERSPRECHEN. „Kann ich nein sagen" fragt nach der eigenen
|
||||
Grenze. Etwas anderes ist, einem VORSCHLAG zu widersprechen,
|
||||
ohne dass es unangenehm wird -- das ist der Kern dessen, was
|
||||
die Forschung psychologische Sicherheit nennt, und es fehlte.
|
||||
* MITREDEN. Ob jemand mitbestimmt, wie seine eigene Arbeit
|
||||
laeuft. Wenig Einfluss bei viel Anforderung ist die
|
||||
Kombination, aus der Erschoepfung entsteht -- nicht viel
|
||||
Arbeit allein.
|
||||
|
||||
Dazu je eine Frage, die ihrem Feld gefehlt hat: Fehler
|
||||
(„Grenzen & Sicherheit" hatte nur die Anfeindung), Zugehoerigkeit
|
||||
(„Miteinander" fragte nur nach Konflikt und Reden) und der Blick
|
||||
zurueck („Weiterkommen" fragte nur nach vorn).
|
||||
|
||||
ALLE AUF "probe" wie die uebrigen: Ein Befinden hat keine
|
||||
Erwartungsstufe. Es gilt am ersten Tag wie im dritten Jahr.
|
||||
|
||||
NUR EINE DAVON IST KERN. Der Kern ist das, was in JEDER Runde
|
||||
gefragt wird -- er waechst nicht mit dem Katalog, sonst waere die
|
||||
Runde bald wieder lang, und "kurz und knapp" war die andere
|
||||
Haelfte von Filipes Wunsch. Gewaehlt wurde der Schlaf: Er schlaegt
|
||||
frueher an als jede andere Frage hier. */
|
||||
/* NICHT KERN, obwohl sie die beste Einzelfrage des Feldes waere:
|
||||
"Last & Erholung" hat bereits zwei Kernfragen (Menge und
|
||||
Abschalten). Eine dritte machte aus einer kurzen Runde eine
|
||||
Umfrage ueber ein einziges Thema -- und "kurz und knapp" war die
|
||||
andere Haelfte von Filipes Wunsch. Dass ihr Feld trotzdem in
|
||||
JEDER Runde vorkommt, sorgt die Rotation (siehe fragenFuer). */
|
||||
{ schluessel: "e-ich-schlaf", feld: "last", stufe: "probe",
|
||||
titel: "Ich liege nachts nicht wach wegen Sachen von hier",
|
||||
text: "Erschöpfung zeigt sich zuerst nachts. Wer wach liegt und an eine "
|
||||
+ "Schicht denkt, merkt es Wochen bevor die Arbeit darunter leidet." },
|
||||
{ schluessel: "e-ich-widersprechen", feld: "zusammen", stufe: "probe",
|
||||
titel: "Ich kann einem Vorschlag widersprechen, ohne dass es unangenehm wird",
|
||||
text: "Nicht ob du recht bekommst – ob du es sagen kannst. Ein Team, in dem "
|
||||
+ "niemand widerspricht, ist selten einig; meistens ist es nur still." },
|
||||
{ schluessel: "e-ich-mitreden", feld: "halt", stufe: "probe",
|
||||
titel: "Ich rede mit, wie meine Arbeit hier läuft",
|
||||
text: "Viel Anforderung und wenig Einfluss ist die Kombination, aus der "
|
||||
+ "Erschöpfung entsteht – nicht viel Arbeit allein." },
|
||||
{ schluessel: "e-ich-fehler-ok", feld: "schutz", stufe: "probe",
|
||||
titel: "Wenn mir ein Fehler passiert, kann ich das sagen",
|
||||
text: "Was zählt, ist nicht, ob Fehler passieren – sondern ob man sie "
|
||||
+ "erfährt. Wer einen Fehler verschweigt, trägt ihn allein weiter." },
|
||||
{ schluessel: "e-ich-dazugehoeren", feld: "zusammen", stufe: "probe",
|
||||
titel: "Ich gehöre hier dazu",
|
||||
text: "Nicht ob du nützlich bist – ob du dazugehörst. Das ist etwas "
|
||||
+ "anderes, und es hält länger." },
|
||||
{ schluessel: "e-ich-stolz", feld: "sinn", stufe: "probe",
|
||||
titel: "In den letzten Wochen war etwas dabei, auf das ich stolz bin",
|
||||
text: "Die einzige Frage hier, die nach hinten schaut. Wer sich an nichts "
|
||||
+ "erinnert, hat selten nichts geschafft – es hat nur niemand benannt." },
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -1799,3 +1799,118 @@ body[data-ton] .kopf-zeile::before {
|
||||
transparent 62%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
/* =====================================================================
|
||||
DIE HIGHLIGHTS: DREI ACCOUNTS NEBENEINANDER (20.09.2026)
|
||||
|
||||
Filipe: „die highlights sollen in 3 kategorien sein nebeneinander,
|
||||
dogfather, hasidog und dogfather clips. und die liste soll man auch
|
||||
zu und auf klappen können."
|
||||
|
||||
---------------------------------------------------------------------
|
||||
`auto-fit` UND NICHT `repeat(3, …)`
|
||||
|
||||
Drei feste Spalten wären auf einem Handy drei Streifen von 100 px,
|
||||
in denen kein Titel steht. `minmax(230px, 1fr)` misst statt zu
|
||||
rechnen: Nebeneinander, solange der Platz für 230 px je Spalte
|
||||
reicht, sonst untereinander. Kommt ein vierter Account dazu, bricht
|
||||
die Regel von selbst richtig um — bei einer festen 3 wäre er die
|
||||
Zeile, die jemand vergisst.
|
||||
|
||||
Das ist dieselbe Lehre wie bei der Kopfleiste am 06.09.2026: Wo es
|
||||
geht, eine Regel statt einer Zahl.
|
||||
===================================================================== */
|
||||
.kanalspalten {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.kanalspalte {
|
||||
border: 1px solid var(--rand);
|
||||
border-radius: var(--radius);
|
||||
background: color-mix(in srgb, var(--flaeche) 70%, transparent);
|
||||
overflow: hidden;
|
||||
/* Der farbige Strich oben ist das, was die drei auf einen Blick
|
||||
unterscheidet -- vor jedem gelesenen Wort. */
|
||||
border-top: 3px solid var(--kfarbe, var(--rand-hell));
|
||||
}
|
||||
.kanalspalte[data-kanal="dogfather"] { --kfarbe: #7c6cf0; }
|
||||
.kanalspalte[data-kanal="hasidog"] { --kfarbe: #e58bbd; }
|
||||
.kanalspalte[data-kanal="clips"] { --kfarbe: #4fb9c8; }
|
||||
.kanalspalte[data-kanal="ohne"] { --kfarbe: var(--rand-hell); }
|
||||
|
||||
.kanalspalte__kopf {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
width: 100%;
|
||||
padding: 11px 13px;
|
||||
border: 0; background: color-mix(in srgb, var(--kfarbe) 9%, transparent);
|
||||
color: var(--text);
|
||||
font: inherit; font-weight: 650;
|
||||
text-align: left; cursor: pointer;
|
||||
}
|
||||
.kanalspalte__kopf:hover { background: color-mix(in srgb, var(--kfarbe) 16%, transparent); }
|
||||
.kanalspalte__kopf:focus-visible { outline: 2px solid var(--akzent); outline-offset: -2px; }
|
||||
|
||||
.kanalspalte__wort { flex: 1 1 auto; min-width: 0; display: block; }
|
||||
/* Das Handle DARUNTER, nicht daneben: In einer Spalte von 230 px
|
||||
schöbe „@dogis.modi.gang" neben dem Namen die Zahl aus dem Bild. */
|
||||
.kanalspalte__handle {
|
||||
display: block;
|
||||
font-size: .72rem; font-weight: 500; color: var(--text-leise);
|
||||
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||
}
|
||||
.kanalspalte__zahl {
|
||||
flex: 0 0 auto;
|
||||
min-width: 22px; padding: 1px 7px;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--kfarbe) 26%, transparent);
|
||||
font-size: .74rem; font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.kanalspalte__inhalt {
|
||||
display: flex; flex-direction: column; gap: 10px;
|
||||
padding: 12px;
|
||||
}
|
||||
/* Die Galerie-Form IN der Spalte. `minmax(180px, 1fr)` statt der 280 px
|
||||
von `#liste`: Eine Spalte ist schmaler als die ganze Seite, und mit
|
||||
280 px bliebe in einer 376-px-Spalte eine Kachel je Reihe, auch wenn
|
||||
zwei hineinpassen. */
|
||||
.kanalspalte__inhalt[data-galerie="ja"] {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
align-items: stretch;
|
||||
}
|
||||
.kanalspalte__leer { margin: 0; font-size: .82rem; }
|
||||
|
||||
/* =====================================================================
|
||||
DIE ZEITLEISTE
|
||||
|
||||
Sie steht ÜBER den Kanalknöpfen und sieht anders aus als sie: zwei
|
||||
Reihen, die gleich aussehen und Verschiedenes tun, sind die Sorte
|
||||
Doppeldeutigkeit, die am 18.09. auf „Regeln & Hilfe" schon einmal
|
||||
aufgefallen ist.
|
||||
===================================================================== */
|
||||
.filter__zeiten {
|
||||
display: flex; flex-wrap: wrap; gap: 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.filter__zeit { display: inline-flex; align-items: center; gap: 6px; }
|
||||
.filter__zeit[aria-pressed="true"] {
|
||||
border-color: var(--akzent);
|
||||
background: color-mix(in srgb, var(--akzent) 16%, transparent);
|
||||
color: var(--text);
|
||||
}
|
||||
.filter__zeit:disabled { opacity: .42; cursor: default; }
|
||||
.filter__zeit-zahl {
|
||||
font-size: .72rem; font-variant-numeric: tabular-nums;
|
||||
color: var(--text-leise);
|
||||
}
|
||||
.filter__zeit[aria-pressed="true"] .filter__zeit-zahl { color: inherit; }
|
||||
|
||||
@media (forced-colors: active) {
|
||||
.kanalspalte { border: 1px solid CanvasText; border-top-width: 4px; }
|
||||
.filter__zeit[aria-pressed="true"] { border: 2px solid Highlight; }
|
||||
}
|
||||
|
||||
@@ -1481,3 +1481,140 @@
|
||||
}
|
||||
.t-kuemmern__seit { font-size: .78rem; color: var(--text-still); }
|
||||
|
||||
|
||||
|
||||
/* =====================================================================
|
||||
DIE FELDER DER BEFINDENSFRAGEN (20.09.2026)
|
||||
|
||||
Filipe: „bei der frage wie geht es dir will ich viel mehr fragen aber
|
||||
kategorisiert und so. psychologisch hochprofessionell … es soll
|
||||
extrem übersichtlich sein und sich speziell anfühlen."
|
||||
|
||||
ÜBERSICHTLICH HEISST HIER: RUHIG. Eine Seite, auf der jemand über
|
||||
sein Befinden nachdenkt, ist der falsche Ort für kräftige Farben und
|
||||
Kästen. Die Feldüberschrift ist deshalb kein Kasten, sondern eine
|
||||
Linie mit einem Zeichen davor — sie gliedert, ohne sich
|
||||
dazwischenzustellen. Augenschonend ist hier nicht nur eine
|
||||
Hausregel, sondern Teil der Sache.
|
||||
===================================================================== */
|
||||
.e-feld {
|
||||
margin: 30px 0 12px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--rand);
|
||||
}
|
||||
/* Das erste Feld braucht oben keinen Abstand -- sonst klafft zwischen
|
||||
der Taktzeile und der ersten Frage ein Loch, das wie ein Ladefehler
|
||||
aussieht. */
|
||||
#fragen > .e-feld:first-child { margin-top: 4px; }
|
||||
|
||||
.e-feld__wort {
|
||||
display: flex; align-items: baseline; gap: 9px;
|
||||
margin: 0;
|
||||
font-size: .96rem; font-weight: 650;
|
||||
color: var(--text);
|
||||
letter-spacing: .01em;
|
||||
}
|
||||
.e-feld__zeichen {
|
||||
flex: 0 0 auto;
|
||||
font-size: .8rem;
|
||||
color: var(--akzent);
|
||||
/* Es ist ein Gliederungszeichen, kein Symbol mit Bedeutung -- im
|
||||
Markup steht deshalb aria-hidden, und hier bekommt es nur so viel
|
||||
Gewicht, dass das Auge die Überschrift findet. */
|
||||
opacity: .85;
|
||||
}
|
||||
.e-feld__text {
|
||||
margin: 4px 0 0;
|
||||
font-size: .82rem; line-height: 1.5;
|
||||
color: var(--text-leise);
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
DIE EINE ZUSAMMENFASSUNG
|
||||
|
||||
SIE DARF ANDERS AUSSEHEN ALS ALLES DARÜBER — sie ist das Einzige auf
|
||||
der Seite, das jemand FÜR die Person geschrieben hat, statt sie zu
|
||||
fragen. Das ist der eine Ort, an dem „speziell" nicht Dekoration
|
||||
ist, sondern Bedeutung trägt.
|
||||
|
||||
Gemacht wird das mit einem sehr weichen Verlauf und einer Kante
|
||||
links — nicht mit Leuchten oder Farbe. Hausregel: augenschonend,
|
||||
gedeckt statt Neon. Wer hier gerade liest, dass fünf von acht Sachen
|
||||
haken, braucht keine fröhlichen Farben dazu.
|
||||
===================================================================== */
|
||||
.b-fazit {
|
||||
margin: 34px 0 0;
|
||||
padding: 20px 22px;
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid color-mix(in srgb, var(--akzent) 26%, var(--rand));
|
||||
border-left: 3px solid var(--akzent);
|
||||
background:
|
||||
linear-gradient(160deg,
|
||||
color-mix(in srgb, var(--akzent) 9%, transparent) 0%,
|
||||
transparent 62%),
|
||||
color-mix(in srgb, var(--flaeche) 80%, transparent);
|
||||
}
|
||||
.b-fazit .e-abschnitt { margin-top: 0; }
|
||||
|
||||
/* DER KOPFSATZ IST DER GRÖSSTE TEXT DER SEITE. Wer nur ihn liest, soll
|
||||
richtig informiert sein -- dieselbe Regel wie beim Satz über der
|
||||
Musteranalyse. */
|
||||
.b-fazit__kopf {
|
||||
margin: 6px 0 0;
|
||||
font-size: 1.04rem; line-height: 1.6;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.b-fazit__traegt { margin-top: 16px; }
|
||||
.b-fazit__schild {
|
||||
margin: 0 0 6px;
|
||||
font-size: .74rem; font-weight: 650;
|
||||
letter-spacing: .06em; text-transform: uppercase;
|
||||
color: var(--text-leise);
|
||||
}
|
||||
.b-fazit__liste { margin: 0; padding: 0; list-style: none;
|
||||
display: flex; flex-direction: column; gap: 5px; }
|
||||
.b-fazit__punkt {
|
||||
position: relative;
|
||||
padding-left: 20px;
|
||||
font-size: .9rem; line-height: 1.5;
|
||||
color: var(--text);
|
||||
}
|
||||
/* Ein Haken, kein Aufzählungspunkt: Was trägt, ist etwas Erledigtes,
|
||||
und das sieht man an der Form schneller als am Wort. */
|
||||
.b-fazit__punkt::before {
|
||||
content: "¹3";
|
||||
position: absolute; left: 0; top: 0;
|
||||
color: var(--gut, #5fc08a);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.b-fazit__thema {
|
||||
margin: 16px 0 0;
|
||||
padding: 11px 13px;
|
||||
border-radius: var(--radius-klein);
|
||||
background: color-mix(in srgb, var(--warn, #e0a355) 10%, transparent);
|
||||
border-left: 2px solid color-mix(in srgb, var(--warn, #e0a355) 55%, transparent);
|
||||
font-size: .9rem; line-height: 1.55;
|
||||
color: var(--text);
|
||||
}
|
||||
.b-fazit__themafragen {
|
||||
display: inline-block; margin-top: 5px;
|
||||
font-size: .82rem; color: var(--text-leise);
|
||||
}
|
||||
|
||||
.b-fazit__schluss {
|
||||
margin: 16px 0 0;
|
||||
font-size: .92rem; line-height: 1.6;
|
||||
color: var(--text);
|
||||
}
|
||||
.b-fazit__zahlen {
|
||||
margin: 12px 0 0;
|
||||
font-size: .76rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
@media (forced-colors: active) {
|
||||
.b-fazit { border: 2px solid CanvasText; }
|
||||
.e-feld { border-bottom: 1px solid CanvasText; }
|
||||
}
|
||||
|
||||
@@ -135,7 +135,63 @@
|
||||
ziel.textContent = '';
|
||||
ziel.setAttribute('aria-busy', 'false');
|
||||
|
||||
for (const pt of daten.punkte) {
|
||||
/* =====================================================================
|
||||
NACH FELDERN GEGLIEDERT (20.09.2026)
|
||||
|
||||
Filipe: „bei der frage wie geht es dir will ich viel mehr fragen
|
||||
aber kategorisiert und so. psychologisch hochprofessionell."
|
||||
|
||||
Acht Fragen in einer Reihe sind ein Fragebogen; acht Fragen in
|
||||
sechs Feldern sind ein Gespräch mit Themen. Die Überschrift ist
|
||||
dabei nicht Schmuck, sondern Teil der Frage: „Ich kann nein
|
||||
sagen" heißt unter „Klarheit & Rückhalt" etwas anderes als unter
|
||||
„Miteinander" — sie beantwortet die Rückfrage, bevor sie
|
||||
entsteht.
|
||||
|
||||
DIE REIHENFOLGE KOMMT VOM SERVER, nicht aus einer Liste hier.
|
||||
Sie geht von außen nach innen: „Wie viel ist es" lässt sich
|
||||
leicht beantworten, „bleibe ich" ist die schwerste Frage der
|
||||
Liste und steht zuletzt.
|
||||
|
||||
EIN FELD OHNE FRAGEN WIRD NICHT GEZEICHNET. Eine leere
|
||||
Überschrift sieht aus, als wäre etwas verlorengegangen — und der
|
||||
Server sorgt ohnehin dafür, dass in jeder Runde jedes Feld
|
||||
vorkommt. */
|
||||
const felder = Array.isArray(daten.felder) ? daten.felder : [];
|
||||
/* ABGELEITET STATT ANGENOMMEN: Fragen ohne bekanntes Feld (eine
|
||||
neue, die noch keins hat) landen in einer eigenen Gruppe am
|
||||
Ende, statt zu verschwinden. Ein Eintrag, den man angelegt hat
|
||||
und nirgends wiederfindet, ist der schlimmere Fehler. */
|
||||
const gruppen = felder.map((f) => ({
|
||||
feld: f,
|
||||
punkte: daten.punkte.filter((pt) => pt.feld === f.schluessel),
|
||||
})).filter((g) => g.punkte.length);
|
||||
const bekannt = new Set(felder.map((f) => f.schluessel));
|
||||
const uebrig = daten.punkte.filter((pt) => !bekannt.has(pt.feld));
|
||||
if (uebrig.length) gruppen.push({ feld: null, punkte: uebrig });
|
||||
|
||||
for (const g of gruppen) {
|
||||
if (g.feld) {
|
||||
const kopf = el('div', 'e-feld');
|
||||
const wort = el('h3', 'e-feld__wort');
|
||||
if (g.feld.zeichen) {
|
||||
const zch = el('span', 'e-feld__zeichen', g.feld.zeichen);
|
||||
zch.setAttribute('aria-hidden', 'true');
|
||||
wort.append(zch);
|
||||
}
|
||||
wort.append(document.createTextNode(g.feld.name));
|
||||
kopf.append(wort);
|
||||
if (g.feld.text) kopf.append(el('p', 'e-feld__text', g.feld.text));
|
||||
ziel.append(kopf);
|
||||
}
|
||||
for (const pt of g.punkte) zeichneFrage(pt, ziel);
|
||||
}
|
||||
}
|
||||
|
||||
/** Eine einzelne Frage. Ausgelagert, damit die Gliederung oben
|
||||
* lesbar bleibt -- der Inhalt ist unverändert. */
|
||||
function zeichneFrage(pt, ziel) {
|
||||
{
|
||||
const z = el('article', 'e-punkt e-punkt--meins');
|
||||
if (pt.meins) z.dataset.stufe = pt.meins.stufe;
|
||||
/* ALT, ABER SICHTBAR. Die Antwort vom letzten Mal stehen zu
|
||||
@@ -190,6 +246,92 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
DIE EINE ZUSAMMENFASSUNG (20.09.2026)
|
||||
|
||||
Filipe: „am ende von allen fragen will ich eine gesamte
|
||||
zusammenfassung, nicht bei jeder frage oder kategorie eine, sondern
|
||||
EINE gesamte am ende. eine motivierende zusammenfassung für die
|
||||
person."
|
||||
|
||||
GERECHNET HAT SIE DER SERVER — hier wird nur gezeichnet. Dieselbe
|
||||
Regel wie bei der Analyse darunter, und aus demselben Grund: Diese
|
||||
Antworten dürfen das Haus nicht verlassen, und eine Auswertung im
|
||||
Browser hieße, sie dorthin zu schicken, wo man sie abgreifen kann.
|
||||
|
||||
WARUM KEIN SPRACHMODELL: steht in workspace-befinden-resuemee.js.
|
||||
Kurz: Gesundheitsdaten, ein Modell erfindet, ein fremder Dienst
|
||||
fällt aus.
|
||||
|
||||
DIE REIHENFOLGE DER VIER TEILE IST DIE AUSSAGE:
|
||||
1. der Kopfsatz — die Lage in einem Satz
|
||||
2. was trägt — benannt, nicht als Zahl
|
||||
3. höchstens EINE Sache zum Hinschauen
|
||||
4. ein Schluss, der zur Lage passt
|
||||
|
||||
Erst das Tragende, dann das Thema. Umgekehrt wäre es eine
|
||||
Mängelliste mit Trostpflaster — und so etwas beantwortet man beim
|
||||
nächsten Mal nicht mehr ehrlich.
|
||||
===================================================================== */
|
||||
function fazit() {
|
||||
const block = $('fazit-block');
|
||||
if (!block) return;
|
||||
const f = daten.resuemee;
|
||||
/* KEINE ANTWORTEN, KEIN FAZIT. Ein freundlicher Satz über nichts
|
||||
wäre schlimmer als gar keiner -- er behauptet, es sei schon
|
||||
etwas gesagt worden. */
|
||||
if (!f || !f.moeglich) { block.hidden = true; return; }
|
||||
block.hidden = false;
|
||||
|
||||
/* WORAUF SIE SICH BEZIEHT. „Was das zusammen heißt" über eine
|
||||
Runde von vorgestern ist etwas anderes als über die, die man
|
||||
gerade ausfüllt -- und wer das nicht weiß, bezieht den Satz auf
|
||||
den falschen Tag. */
|
||||
const schild = block.querySelector('.e-abschnitt');
|
||||
if (schild) {
|
||||
schild.textContent = daten.resuemee_stand === 'letzte'
|
||||
? 'Was deine letzte Runde zusammen heißt'
|
||||
: 'Was das zusammen heißt';
|
||||
}
|
||||
|
||||
$('fazit-kopf').textContent = f.kopf || '';
|
||||
|
||||
const tBlock = $('fazit-traegt'), tListe = $('fazit-traegt-liste');
|
||||
tListe.textContent = '';
|
||||
if (f.traegt?.length) {
|
||||
tBlock.hidden = false;
|
||||
for (const t of f.traegt) tListe.append(el('li', 'b-fazit__punkt', t.titel));
|
||||
} else tBlock.hidden = true;
|
||||
|
||||
const th = $('fazit-thema');
|
||||
if (f.thema?.satz) {
|
||||
th.hidden = false;
|
||||
th.textContent = f.thema.satz;
|
||||
/* Welche Fragen es sind, gehört dazu -- „Klarheit & Rückhalt"
|
||||
allein ist eine Überschrift, keine Auskunft. */
|
||||
if (f.thema.titel?.length) {
|
||||
th.append(document.createElement('br'));
|
||||
const w = el('span', 'b-fazit__themafragen', f.thema.titel.join(' · '));
|
||||
th.append(w);
|
||||
}
|
||||
} else th.hidden = true;
|
||||
|
||||
$('fazit-schluss').textContent = f.schluss || '';
|
||||
|
||||
/* DIE ZAHLEN GANZ UNTEN UND LEISE. Sie sind die Belegstelle für
|
||||
alles darüber -- wer sie nicht will, überliest sie; wer prüfen
|
||||
will, ob der Satz stimmt, findet sie. */
|
||||
const z = f.zahlen || {};
|
||||
const teile = [];
|
||||
if (z.traegt) teile.push(`${z.traegt}× läuft`);
|
||||
if (z.waechst) teile.push(`${z.waechst}× wächst`);
|
||||
if (z.hakt) teile.push(`${z.hakt}× hakt`);
|
||||
if (z.unklar) teile.push(`${z.unklar}× ohne Antwort`);
|
||||
$('fazit-zahlen').textContent = teile.length
|
||||
? `${z.gefragt} Fragen aus ${z.felder} Feldern: ${teile.join(' · ')}.`
|
||||
: '';
|
||||
}
|
||||
|
||||
/* ---- Der Verlauf ----------------------------------------------------- */
|
||||
function verlauf() {
|
||||
const block = $('verlauf-block');
|
||||
@@ -381,6 +523,7 @@
|
||||
stufen = daten.stufen || [];
|
||||
takt();
|
||||
fragen();
|
||||
fazit();
|
||||
verlauf();
|
||||
analyse();
|
||||
}
|
||||
|
||||
@@ -132,6 +132,12 @@
|
||||
let ich = null;
|
||||
let bereich = null;
|
||||
let einstellung = null;
|
||||
/* DIE ACCOUNTS, so wie der Server sie schickt (20.09.2026).
|
||||
Auf Modulebene, weil zwei Stellen sie brauchen: die Galerie (drei
|
||||
Spalten in DIESER Reihenfolge) und das Formular (Auswahlliste).
|
||||
Beide aus derselben Auskunft -- eine zweite Liste im Browser waere
|
||||
die, die beim vierten Account vergessen wird. */
|
||||
let kanaeleDa = [];
|
||||
/* Ist dies ein Brett des Treffs, und was darf ich dort? Beides sagt
|
||||
der Server (daten.treff und /api/treff/lage) -- im Browser steht
|
||||
keine Liste der Bretter und keine Rechteregel, sondern nur die
|
||||
@@ -165,6 +171,62 @@
|
||||
aber nie beides. Und genau das will man, sobald aus drei Kanälen
|
||||
Videos nebeneinander liegen. */
|
||||
let kanalFilter = '';
|
||||
|
||||
/* =====================================================================
|
||||
DIE HIGHLIGHTS NACH ZEIT (20.09.2026)
|
||||
|
||||
Filipe: "die videos will ich auch kategorisiert haben nach woche,
|
||||
monat, jahr und ganz oben aktuell also heute."
|
||||
|
||||
WARUM DAS NOETIG WURDE: Highlights sammeln sich. Nach einem halben
|
||||
Jahr steht das Beste vom Mai zwischen dem von gestern, und die
|
||||
Galerie beantwortet die Frage, die man wirklich hat ("was ist
|
||||
GERADE gut gelaufen"), gar nicht mehr.
|
||||
|
||||
DIE ZEITRAEUME SIND KALENDERZEITRAEUME, KEINE RUECKBLICKE. "Diese
|
||||
Woche" heisst seit Montag, nicht "die letzten sieben Tage". Wer am
|
||||
Dienstag "diese Woche" fragt, meint Montag und Dienstag -- nicht
|
||||
den Mittwoch davor. Ein rollendes Fenster waere leichter zu rechnen
|
||||
und beantwortet eine andere Frage.
|
||||
|
||||
UND SIE STAPELN SICH NICHT. Man waehlt EINEN -- "heute" ist auch
|
||||
in "diese Woche" enthalten, und zwei sich enthaltende Filter
|
||||
gleichzeitig anzubieten erzeugt nur die Frage, was ihre
|
||||
Kombination bedeutet. */
|
||||
let zeitFilter = '';
|
||||
|
||||
const ZEIT_WORT = {
|
||||
heute: 'Aktuell', woche: 'Diese Woche',
|
||||
monat: 'Dieser Monat', jahr: 'Dieses Jahr',
|
||||
};
|
||||
|
||||
/** Ab wann zaehlt dieser Zeitraum? Gibt einen Zeitstempel in
|
||||
* Millisekunden -- oder 0 fuer "alles". */
|
||||
function zeitAb(was) {
|
||||
const d = new Date();
|
||||
d.setHours(0, 0, 0, 0);
|
||||
if (was === 'heute') return d.getTime();
|
||||
if (was === 'woche') {
|
||||
/* Montag als Wochenanfang. `getDay()` liefert Sonntag = 0 --
|
||||
ohne die Korrektur faenge die Woche am Sonntag an, und in
|
||||
Deutschland steht der am Ende. */
|
||||
const wt = (d.getDay() + 6) % 7;
|
||||
d.setDate(d.getDate() - wt);
|
||||
return d.getTime();
|
||||
}
|
||||
if (was === 'monat') { d.setDate(1); return d.getTime(); }
|
||||
if (was === 'jahr') { d.setMonth(0, 1); return d.getTime(); }
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Wann ist dieser Eintrag entstanden? `erstellt` ist die Wahrheit --
|
||||
* `datum` ist ein Feld, das jemand von Hand setzen kann, und ein
|
||||
* Highlight vom Mai mit dem Datum von heute gehoert nicht unter
|
||||
* "Aktuell". */
|
||||
const entstandAm = (e) => {
|
||||
const t = Date.parse(e.erstellt || e.datum || '');
|
||||
return Number.isNaN(t) ? 0 : t;
|
||||
};
|
||||
/* Nur beim ersten Laden springen -- sonst reisst es die Seite nach
|
||||
jeder Bewertung erneut an dieselbe Stelle. */
|
||||
let gesprungen = false;
|
||||
@@ -1487,6 +1549,59 @@
|
||||
tut. Ein Knopf, der immer dasselbe zeigt, macht misstrauisch.
|
||||
|
||||
ERST AB ZWEI KANÄLEN. Bei einem gibt es nichts zu wählen. */
|
||||
/* ---- DIE ZEITLEISTE, NUR AUF DEN HIGHLIGHTS (20.09.2026) -------
|
||||
|
||||
Filipe: "die videos will ich auch kategorisiert haben nach
|
||||
woche, monat, jahr und ganz oben aktuell also heute."
|
||||
|
||||
SIE STEHT VOR der Kanal-Abbruchstelle darunter. Dort steigt die
|
||||
Funktion aus, wenn es weniger als zwei Kanaele gibt -- stuende
|
||||
die Zeitleiste dahinter, waere sie auf einem Brett mit nur
|
||||
einem Account nicht da. Und genau dann braucht man sie:
|
||||
Ein Kanal, viele Monate.
|
||||
|
||||
JEDER KNOPF TRAEGT SEINE ZAHL. Ohne sie klickt man auf
|
||||
"Aktuell" und landet auf einer leeren Seite, ohne zu wissen, ob
|
||||
der Filter greift oder nichts passiert ist. Mit ihr sieht man
|
||||
vorher, dass heute nichts dazugekommen ist -- und klickt gar
|
||||
nicht erst. */
|
||||
if (bereich === 'highlight' && eintraege.length) {
|
||||
const zeile = el('div', 'filter__zeiten');
|
||||
zeile.setAttribute('role', 'group');
|
||||
zeile.setAttribute('aria-label', 'Nach Zeitraum filtern');
|
||||
const zknopf = (wert, text) => {
|
||||
const b = el('button', 'schritt filter__zeit', text);
|
||||
b.type = 'button';
|
||||
if (wert) b.dataset.zeit = wert;
|
||||
b.setAttribute('aria-pressed', String(zeitFilter === wert));
|
||||
const ab = zeitAb(wert);
|
||||
const zahl = wert
|
||||
? eintraege.filter((e) => entstandAm(e) >= ab).length
|
||||
: eintraege.length;
|
||||
b.append(el('span', 'filter__zeit-zahl', String(zahl)));
|
||||
/* Ein Zeitraum ohne einen einzigen Eintrag laesst sich nicht
|
||||
waehlen. Er bleibt aber STEHEN, mit seiner Null -- ihn
|
||||
auszublenden hiesse, die Leiste wechselt ihre Form, sobald
|
||||
jemand etwas einstellt, und man sucht den Knopf, den man
|
||||
gestern noch hatte. */
|
||||
if (!zahl && wert) { b.disabled = true; b.title = 'Dafür gibt es nichts'; }
|
||||
b.addEventListener('click', async () => {
|
||||
/* Zweiter Klick hebt auf -- wie beim Kanalfilter daneben.
|
||||
Zwei Filter nebeneinander, die sich verschieden bedienen
|
||||
lassen, sind einer zu viel. */
|
||||
zeitFilter = (zeitFilter === wert) ? '' : wert;
|
||||
await laden();
|
||||
});
|
||||
return b;
|
||||
};
|
||||
zeile.append(zknopf('heute', ZEIT_WORT.heute));
|
||||
zeile.append(zknopf('woche', ZEIT_WORT.woche));
|
||||
zeile.append(zknopf('monat', ZEIT_WORT.monat));
|
||||
zeile.append(zknopf('jahr', ZEIT_WORT.jahr));
|
||||
zeile.append(zknopf('', 'Alles'));
|
||||
ziel.append(zeile);
|
||||
}
|
||||
|
||||
const kanaele = [...new Set(eintraege.map((e) => e.quelle_kanal).filter(Boolean))];
|
||||
if (kanaele.length < 2) { kanalFilter = ''; return; }
|
||||
|
||||
@@ -1786,6 +1901,7 @@
|
||||
if (!a.ok) { melde('Einträge konnten nicht geladen werden.'); return; }
|
||||
const daten = await a.json();
|
||||
einstellung = daten.einstellung;
|
||||
kanaeleDa = Array.isArray(daten.kanaele) ? daten.kanaele : [];
|
||||
treffBrett = !!daten.treff;
|
||||
kalenderOffen = daten.kalender_offen !== false;
|
||||
weiterZeigen(daten.weiter);
|
||||
@@ -1799,8 +1915,14 @@
|
||||
Raster steht hier und nicht fest im HTML, weil dieselbe Seite
|
||||
acht Bretter bedient -- ein Raster in der Datei waere fuer
|
||||
sieben davon falsch. */
|
||||
if (bereich === 'highlight') ziel.dataset.galerie = 'ja';
|
||||
else delete ziel.dataset.galerie;
|
||||
/* SEIT DEN KANALSPALTEN LIEGT DAS RASTER EINE EBENE TIEFER
|
||||
(20.09.2026). `#liste[data-galerie]` ist selbst ein Raster --
|
||||
der Spaltenkasten wurde darin zu EINER Zelle von 376 px, und
|
||||
die drei Accounts standen untereinander statt nebeneinander.
|
||||
Gemessen, nicht vermutet: Elternbreite 1160, Kind 376.
|
||||
Die Galerie gehoert jetzt in die einzelne Spalte (siehe
|
||||
`.kanalspalte__inhalt[data-galerie]`). */
|
||||
delete ziel.dataset.galerie;
|
||||
/* DAS ANSCHLAGBRETT IST EIN SCHWARZES BRETT: wenige, GROSSE
|
||||
Aushaenge. Ein Aushang zwischen dreissig anderen ist kein
|
||||
Aushang -- die Wirkung entsteht durch die Groesse und die
|
||||
@@ -1813,6 +1935,11 @@
|
||||
Art-Filter nicht aus, er kommt dazu. „Nur offene Clips von
|
||||
HasiDog" ist eine sinnvolle Frage. */
|
||||
if (kanalFilter && e.quelle_kanal !== kanalFilter) return false;
|
||||
/* DER ZEITFILTER GILT NUR FUER HIGHLIGHTS. Auf den anderen
|
||||
Brettern steht er gar nicht erst da -- eine Regel, die
|
||||
unsichtbar mitfiltert, ist ein Brett, das Dinge verschluckt. */
|
||||
if (bereich === 'highlight' && zeitFilter
|
||||
&& entstandAm(e) < zeitAb(zeitFilter)) return false;
|
||||
if (!filter) return true;
|
||||
if (filter === 'offen') return e.status === 'offen';
|
||||
return e.art === filter;
|
||||
@@ -1850,6 +1977,94 @@
|
||||
}
|
||||
const karten = new Map();
|
||||
|
||||
/* =====================================================================
|
||||
DIE HIGHLIGHTS: DREI KANAELE NEBENEINANDER (20.09.2026)
|
||||
|
||||
Filipe: "die highlights sollen in 3 kategorien sein
|
||||
nebeneinander, dogfather, hasidog und dogfather clips. und die
|
||||
liste soll man auch zu und auf klappen koennen."
|
||||
|
||||
WARUM DAS MEHR IST ALS EINE SORTIERUNG: Die drei Accounts haben
|
||||
eigene Zuschauer und einen eigenen Ton -- das steht so an
|
||||
KANAELE im Server. Eine gemeinsame Galerie behauptet, sie
|
||||
waeren ein Topf. Wer fuer Clips schneidet, sucht dann in jeder
|
||||
Reihe nach den Kacheln, die ihn angehen.
|
||||
|
||||
DIE REIHENFOLGE KOMMT VOM SERVER, nicht aus einer Liste hier.
|
||||
Der Browser hatte dafuer bis heute `KANAL_WORT` -- dieselben
|
||||
drei Namen in anderer Reihenfolge, ohne Handles. Beim vierten
|
||||
Account waere genau diese Kopie die vergessene.
|
||||
|
||||
EINE VIERTE SPALTE GIBT ES NUR, WENN SIE GEBRAUCHT WIRD:
|
||||
Bilder und Fanart ohne TikTok-Link gehoeren zu keinem Kanal.
|
||||
Sie verschwinden zu lassen waere der schlimmere Fehler -- ein
|
||||
Eintrag, den man angelegt hat und nirgends wiederfindet.
|
||||
|
||||
ZUKLAPPEN ueber dieselben zwei Hilfen wie ueberall
|
||||
(`abschnittKlappbar`, `klappPfeil`), mit einem Merker je Kanal.
|
||||
Ein zweiter Klappmechanismus waere eine zweite Sache zum
|
||||
Lernen und eine zweite zum Kaputtgehen.
|
||||
===================================================================== */
|
||||
if (bereich === 'highlight') {
|
||||
const kanaele = kanaeleDa.length
|
||||
? kanaeleDa
|
||||
/* Faellt die Auskunft aus (alte Auslieferung, fremde Rolle),
|
||||
wird aus den Eintraegen selbst abgeleitet -- lieber die
|
||||
Namen ohne Reihenfolge als eine leere Seite. */
|
||||
: [...new Set(sichtbar.map((e) => e.quelle_kanal).filter(Boolean))]
|
||||
.map((w) => ({ wert: w, name: KANAL_WORT[w] || w }));
|
||||
|
||||
const faecher = new Map(kanaele.map((k) => [k.wert, []]));
|
||||
const ohne = [];
|
||||
for (const e of sichtbar) {
|
||||
if (e.quelle_kanal && faecher.has(e.quelle_kanal)) faecher.get(e.quelle_kanal).push(e);
|
||||
else ohne.push(e);
|
||||
}
|
||||
|
||||
const spalten = el('div', 'kanalspalten');
|
||||
const bauen = (schluessel, name, handle, fach) => {
|
||||
const sp = el('section', 'kanalspalte');
|
||||
sp.dataset.kanal = schluessel || 'ohne';
|
||||
const kopf = el('button', 'kanalspalte__kopf');
|
||||
kopf.type = 'button';
|
||||
const wort = el('span', 'kanalspalte__wort', name);
|
||||
if (handle) wort.append(el('span', 'kanalspalte__handle', handle));
|
||||
kopf.append(wort);
|
||||
kopf.append(el('span', 'kanalspalte__zahl', String(fach.length)));
|
||||
if (window.klappPfeil) kopf.append(window.klappPfeil());
|
||||
sp.append(kopf);
|
||||
|
||||
const kasten = el('div', 'kanalspalte__inhalt');
|
||||
/* Die Galerie-Form kommt hierher -- in der Spalte, nicht um
|
||||
sie herum. In einer schmalen Spalte ergibt das eine Kachel
|
||||
je Reihe, bei viel Platz zwei. Gerechnet wird nichts. */
|
||||
kasten.dataset.galerie = 'ja';
|
||||
if (!fach.length) {
|
||||
/* Eine leere Spalte sagt, dass sie leer IST -- sonst sieht
|
||||
sie aus, als haette die Seite nicht fertig geladen. */
|
||||
kasten.append(el('p', 'leise kanalspalte__leer',
|
||||
zeitFilter
|
||||
? `Für ${ZEIT_WORT[zeitFilter].toLowerCase()} steht hier nichts.`
|
||||
: 'Hier steht noch nichts.'));
|
||||
}
|
||||
for (const e of fach) {
|
||||
const k = karte(e);
|
||||
karten.set(e.id, k);
|
||||
kasten.append(k);
|
||||
}
|
||||
sp.append(kasten);
|
||||
window.abschnittKlappbar?.(kopf, kasten, `highlight:kanal:${schluessel || 'ohne'}`);
|
||||
spalten.append(sp);
|
||||
};
|
||||
|
||||
for (const k of kanaele) bauen(k.wert, k.name, k.handle, faecher.get(k.wert) || []);
|
||||
if (ohne.length) bauen('', 'Ohne Account', null, ohne);
|
||||
|
||||
ziel.append(spalten);
|
||||
sprungZeigen(ziel, karten);
|
||||
return;
|
||||
}
|
||||
|
||||
/* ---- STUFE 7: JEDES BRETT BEKOMMT SEINE FORM ------------------
|
||||
"Regeln & Hilfe" ist ein NACHSCHLAGEWERK, kein Feed: Man
|
||||
schlaegt es im Streitfall auf und will finden, nicht scrollen.
|
||||
@@ -2169,6 +2384,13 @@
|
||||
unsichtbaren Feld stehenbleibt, wandert sonst beim naechsten
|
||||
Speichern mit -- und niemand sieht, woher er kommt. */
|
||||
if (einstellung.einsatzFeld && $('f-einsatz')) daten.einsatz = $('f-einsatz').value.trim();
|
||||
/* Nur, wenn das Feld auch sichtbar ist -- ein Wert aus einem
|
||||
verborgenen Feld wandert sonst beim naechsten Speichern mit, und
|
||||
niemand sieht, woher er kommt. Dieselbe Regel wie eine Zeile
|
||||
darueber. */
|
||||
if ($('feld-kanal') && !$('feld-kanal').hidden) {
|
||||
daten.quelle_kanal = $('f-kanal').value || null;
|
||||
}
|
||||
if (einstellung.bewertung) daten.bewertung = $('f-bewertung').value || null;
|
||||
if (event) {
|
||||
daten.event_ende = $('f-ende').value || null;
|
||||
@@ -2235,6 +2457,7 @@
|
||||
} catch { return; }
|
||||
|
||||
einstellung = daten.einstellung;
|
||||
kanaeleDa = Array.isArray(daten.kanaele) ? daten.kanaele : [];
|
||||
document.title = einstellung.name + ' · Creator Workspace';
|
||||
$('marke-bereich').textContent = einstellung.name;
|
||||
|
||||
@@ -2368,6 +2591,44 @@
|
||||
if (einstellung.bewertungName && $('bewertung-schild')) {
|
||||
$('bewertung-schild').textContent = `${einstellung.bewertungName} (1–5)`;
|
||||
}
|
||||
/* ---- ZU WELCHEM ACCOUNT? (20.09.2026) -------------------------
|
||||
|
||||
Filipe: "und wenn man was hinzufuegt soll man auch den account
|
||||
auswaehlen koennen."
|
||||
|
||||
Nur auf den Highlights und nur, wenn der Server die Kanaele
|
||||
ueberhaupt mitgeschickt hat. Eine leere Auswahlliste waere eine
|
||||
Frage ohne moegliche Antwort.
|
||||
|
||||
DIE LISTE KOMMT VOM SERVER, nicht aus KANAL_WORT hier. Beim
|
||||
vierten Account ist die Kopie hier die vergessene -- das steht
|
||||
so schon am Kanal-Begriff selbst. */
|
||||
const kanalFeld = $('feld-kanal'), kanalWahl = $('f-kanal');
|
||||
if (kanalFeld && kanalWahl) {
|
||||
const liste = kanaeleDa;
|
||||
kanalFeld.hidden = !(bereich === 'highlight' && liste.length);
|
||||
if (!kanalFeld.hidden && kanalWahl.dataset.stand !== String(liste.length)) {
|
||||
kanalWahl.dataset.stand = String(liste.length);
|
||||
kanalWahl.textContent = '';
|
||||
/* LEER ZUERST UND ALS VORGABE. Ein Pflichtfeld erzwaenge hier
|
||||
einen falschen Kanal, und ein falscher Eintrag ist
|
||||
schlechter als ein leerer. */
|
||||
const leer = document.createElement('option');
|
||||
leer.value = ''; leer.textContent = 'Noch offen';
|
||||
kanalWahl.append(leer);
|
||||
for (const k of liste) {
|
||||
const o = document.createElement('option');
|
||||
o.value = k.wert;
|
||||
o.textContent = k.handle ? `${k.name} (${k.handle})` : k.name;
|
||||
kanalWahl.append(o);
|
||||
}
|
||||
}
|
||||
if ($('hinweis-kanal')) {
|
||||
$('hinweis-kanal').textContent = 'Bei einem TikTok-Link erkennt die '
|
||||
+ 'Seite den Account selbst – das hier ist für Bilder und Momente ohne Link.';
|
||||
}
|
||||
}
|
||||
|
||||
/* Das Einsatz-Feld nur, wo der Bereich es kennt. */
|
||||
if ($('feld-einsatz')) $('feld-einsatz').hidden = !einstellung.einsatzFeld;
|
||||
if (einstellung.einsatzName && $('einsatz-schild')) {
|
||||
|
||||
+171
-140
@@ -1,141 +1,172 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<title>Wie geht's dir? · Spicy & Dogi</title>
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<link rel="manifest" href="/workspace/app.webmanifest" />
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609201909" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609201909" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609201909" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609201909" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609201909" />
|
||||
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609201909" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609201909" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609201909" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
|
||||
<div class="schleier" aria-hidden="true"></div>
|
||||
|
||||
<header class="kopfleiste">
|
||||
<button type="button" class="zurueck-knopf" id="zurueck" hidden>
|
||||
<svg class="zurueck-knopf__pfeil" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M14.5 5.5 8 12l6.5 6.5" />
|
||||
</svg>
|
||||
<span class="zurueck-knopf__text">Zurück</span>
|
||||
</button>
|
||||
<p class="marke"><span class="marke__text"><a class="zurueck" href="start.html">Team Dogi</a> · Wie geht's dir?</span></p>
|
||||
<div class="kopfleiste__rechts">
|
||||
<span class="wer" id="wer">…</span>
|
||||
<button type="button" class="abmelden" id="abmelden">Abmelden</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="inhalt">
|
||||
|
||||
<!-- ==================================================================
|
||||
DIESE SEITE GEHÖRT DEM MENSCHEN, DER SIE ÖFFNET.
|
||||
|
||||
Hier steht kein anderer Name, keine Beobachtung über jemanden und
|
||||
keine Zahl, die jemand anders sieht. Genau das war der Grund für
|
||||
die eigene Seite: Die Kachel verspricht „die Antworten sieht nur
|
||||
du" – und führte vorher auf eine Seite voller anderer Leute.
|
||||
|
||||
Was DogFather sieht, ist eine einzige Zahl ohne Namen: wie viele
|
||||
im Team es gerade schwer haben. Nicht wer, nicht womit.
|
||||
============================================================== -->
|
||||
|
||||
<section class="kopf-zeile">
|
||||
<div>
|
||||
<p class="marke">Für dich</p>
|
||||
<h1 class="titel">Wie geht's dir?</h1>
|
||||
<!-- ================================================================
|
||||
DIE EINE AUSNAHME GEHÖRT DAZU (ergänzt 19.09.2026).
|
||||
|
||||
Hier stand: „Niemand aus dem Team sieht deine Antworten – auch
|
||||
DogFather nicht." Punkt. Das ist fast wahr und deshalb
|
||||
gefährlich: Aus denselben Antworten entsteht eine Ampel für die
|
||||
Leitung (workspace-entwicklung.js). Sie ist sauber gebaut – sie
|
||||
nennt keine Namen, keinen Punkt, kein Datum, und unter drei
|
||||
Personen schweigt sie ganz. Aber sie existiert.
|
||||
|
||||
Der ehrliche Satz war längst geschrieben und wurde vom Server
|
||||
sogar mitgeliefert (`block.text`) – nur hat ihn nie jemand
|
||||
angezeigt. Er steht jetzt hier im HTML und nicht im Skript:
|
||||
Wer diese Seite öffnet, soll ihn lesen, BEVOR er tippt, nicht
|
||||
nachdem ein Abruf zurückgekommen ist.
|
||||
|
||||
Wenn jemand später davon erfährt und die Seite hatte „niemand"
|
||||
gesagt, ist das Vertrauen weg – und mit ihm die ganze Funktion.
|
||||
============================================================= -->
|
||||
<p class="unterzeile">
|
||||
Ein paar Fragen über dich. Deine Antworten liest niemand – auch
|
||||
DogFather nicht.
|
||||
</p>
|
||||
<p class="unterzeile unterzeile--zusatz">
|
||||
Das Einzige, was nach außen dringt: ob es im Team gerade
|
||||
<em>jemandem</em> zu viel ist. Ohne Namen, ohne Frage, ohne Datum –
|
||||
und erst ab drei Personen im Team, weil es darunter dasselbe wäre
|
||||
wie ein Name.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<p class="fehler" id="fehler" role="alert" aria-live="polite"></p>
|
||||
|
||||
<!-- Der Rhythmus. Steht VOR den Fragen: Wer hereinkommt, soll zuerst
|
||||
wissen, ob gerade etwas von ihm erwartet wird. -->
|
||||
<section class="b-takt" id="takt" hidden>
|
||||
<p class="b-takt__satz" id="takt-satz">…</p>
|
||||
<button type="button" class="knopf knopf--klein" id="takt-knopf" hidden>Runde abschließen</button>
|
||||
</section>
|
||||
|
||||
<section id="fragen-block">
|
||||
<div id="fragen" aria-busy="true"><p class="leise">wird geladen …</p></div>
|
||||
</section>
|
||||
|
||||
<!-- Der Verlauf. Nur für die Person selbst – er wird nirgends sonst
|
||||
im Haus gelesen. Er steht unter den Fragen, weil er sie erklärt
|
||||
und nicht ersetzt. -->
|
||||
<section class="b-verlauf" id="verlauf-block" hidden>
|
||||
<h2 class="e-abschnitt">Wie es sich entwickelt</h2>
|
||||
<p class="b-verlauf__satz" id="verlauf-satz">…</p>
|
||||
<div id="verlauf" class="b-verlauf__gitter"></div>
|
||||
</section>
|
||||
|
||||
<!-- ==================================================================
|
||||
WAS DEIN VERLAUF SAGT (15.09.2026)
|
||||
|
||||
Filipe: „mach mir auch eine analyse […] eine professionelle auch
|
||||
mit infos und so aus aller welt was tiktok angeht."
|
||||
|
||||
Sie steht UNTER dem Verlauf: erst die Antworten, dann das Muster
|
||||
darin. Andersherum läse man die Deutung, bevor man weiß, worauf
|
||||
sie sich bezieht.
|
||||
|
||||
Sie wird gerechnet, nicht gespeichert – und sie bewertet nicht.
|
||||
Keine Punktzahl über das eigene Befinden: Eine Zahl lädt dazu
|
||||
ein, sie zu verbessern statt ehrlich zu antworten.
|
||||
============================================================== -->
|
||||
<section class="b-analyse" id="analyse-block" hidden>
|
||||
<h2 class="e-abschnitt">Was dein Verlauf sagt</h2>
|
||||
<p class="b-analyse__satz" id="analyse-satz">…</p>
|
||||
<div id="analyse" class="b-analyse__liste"></div>
|
||||
<p class="b-analyse__quelle" id="analyse-quelle"></p>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<title>Wie geht's dir? · Spicy & Dogi</title>
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<link rel="manifest" href="/workspace/app.webmanifest" />
|
||||
<meta name="theme-color" content="#06090f" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609201909" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609201909" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609201909" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609201909" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609201909" />
|
||||
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609201909" />
|
||||
<link rel="stylesheet" href="assets/css/module.css?v=202609201909" />
|
||||
<link rel="stylesheet" href="assets/css/haus.css?v=202609201909" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
|
||||
<div class="schleier" aria-hidden="true"></div>
|
||||
|
||||
<header class="kopfleiste">
|
||||
<button type="button" class="zurueck-knopf" id="zurueck" hidden>
|
||||
<svg class="zurueck-knopf__pfeil" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M14.5 5.5 8 12l6.5 6.5" />
|
||||
</svg>
|
||||
<span class="zurueck-knopf__text">Zurück</span>
|
||||
</button>
|
||||
<p class="marke"><span class="marke__text"><a class="zurueck" href="start.html">Team Dogi</a> · Wie geht's dir?</span></p>
|
||||
<div class="kopfleiste__rechts">
|
||||
<span class="wer" id="wer">…</span>
|
||||
<button type="button" class="abmelden" id="abmelden">Abmelden</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="inhalt">
|
||||
|
||||
<!-- ==================================================================
|
||||
DIESE SEITE GEHÖRT DEM MENSCHEN, DER SIE ÖFFNET.
|
||||
|
||||
Hier steht kein anderer Name, keine Beobachtung über jemanden und
|
||||
keine Zahl, die jemand anders sieht. Genau das war der Grund für
|
||||
die eigene Seite: Die Kachel verspricht „die Antworten sieht nur
|
||||
du" – und führte vorher auf eine Seite voller anderer Leute.
|
||||
|
||||
Was DogFather sieht, ist eine einzige Zahl ohne Namen: wie viele
|
||||
im Team es gerade schwer haben. Nicht wer, nicht womit.
|
||||
============================================================== -->
|
||||
|
||||
<section class="kopf-zeile">
|
||||
<div>
|
||||
<p class="marke">Für dich</p>
|
||||
<h1 class="titel">Wie geht's dir?</h1>
|
||||
<!-- ================================================================
|
||||
DIE EINE AUSNAHME GEHÖRT DAZU (ergänzt 19.09.2026).
|
||||
|
||||
Hier stand: „Niemand aus dem Team sieht deine Antworten – auch
|
||||
DogFather nicht." Punkt. Das ist fast wahr und deshalb
|
||||
gefährlich: Aus denselben Antworten entsteht eine Ampel für die
|
||||
Leitung (workspace-entwicklung.js). Sie ist sauber gebaut – sie
|
||||
nennt keine Namen, keinen Punkt, kein Datum, und unter drei
|
||||
Personen schweigt sie ganz. Aber sie existiert.
|
||||
|
||||
Der ehrliche Satz war längst geschrieben und wurde vom Server
|
||||
sogar mitgeliefert (`block.text`) – nur hat ihn nie jemand
|
||||
angezeigt. Er steht jetzt hier im HTML und nicht im Skript:
|
||||
Wer diese Seite öffnet, soll ihn lesen, BEVOR er tippt, nicht
|
||||
nachdem ein Abruf zurückgekommen ist.
|
||||
|
||||
Wenn jemand später davon erfährt und die Seite hatte „niemand"
|
||||
gesagt, ist das Vertrauen weg – und mit ihm die ganze Funktion.
|
||||
============================================================= -->
|
||||
<p class="unterzeile">
|
||||
Ein paar Fragen über dich. Deine Antworten liest niemand – auch
|
||||
DogFather nicht.
|
||||
</p>
|
||||
<p class="unterzeile unterzeile--zusatz">
|
||||
Das Einzige, was nach außen dringt: ob es im Team gerade
|
||||
<em>jemandem</em> zu viel ist. Ohne Namen, ohne Frage, ohne Datum –
|
||||
und erst ab drei Personen im Team, weil es darunter dasselbe wäre
|
||||
wie ein Name.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<p class="fehler" id="fehler" role="alert" aria-live="polite"></p>
|
||||
|
||||
<!-- Der Rhythmus. Steht VOR den Fragen: Wer hereinkommt, soll zuerst
|
||||
wissen, ob gerade etwas von ihm erwartet wird. -->
|
||||
<section class="b-takt" id="takt" hidden>
|
||||
<p class="b-takt__satz" id="takt-satz">…</p>
|
||||
<button type="button" class="knopf knopf--klein" id="takt-knopf" hidden>Runde abschließen</button>
|
||||
</section>
|
||||
|
||||
<section id="fragen-block">
|
||||
<div id="fragen" aria-busy="true"><p class="leise">wird geladen …</p></div>
|
||||
</section>
|
||||
|
||||
<!-- ===================================================================
|
||||
DIE EINE ZUSAMMENFASSUNG (20.09.2026)
|
||||
|
||||
Filipe: „am ende von allen fragen will ich eine gesamte
|
||||
zusammenfassung, nicht bei jeder frage oder kategorie eine,
|
||||
sondern EINE gesamte am ende. eine motivierende zusammenfassung
|
||||
für die person."
|
||||
|
||||
SIE STEHT DIREKT UNTER DEN FRAGEN und über dem Verlauf. Das ist
|
||||
Absicht: Sie handelt von dem, was man gerade angetippt hat —
|
||||
Verlauf und Musteranalyse handeln von den Runden davor und
|
||||
brauchen mindestens zwei. Diese hier gibt es beim ersten Mal.
|
||||
|
||||
GERECHNET HAT SIE DER SERVER (workspace-befinden-resuemee.js).
|
||||
Dort steht auch, warum hier bewusst KEINE KI hängt: Das sind
|
||||
Gesundheitsdaten, ein Sprachmodell erfindet, und ein fremder
|
||||
Dienst fällt aus. Alle drei Gründe reichen einzeln.
|
||||
=================================================================== -->
|
||||
<section class="b-fazit" id="fazit-block" hidden aria-live="polite">
|
||||
<h2 class="e-abschnitt">Was das zusammen heißt</h2>
|
||||
<p class="b-fazit__kopf" id="fazit-kopf"></p>
|
||||
<div class="b-fazit__traegt" id="fazit-traegt" hidden>
|
||||
<p class="b-fazit__schild">Das trägt gerade</p>
|
||||
<ul class="b-fazit__liste" id="fazit-traegt-liste"></ul>
|
||||
</div>
|
||||
<p class="b-fazit__thema" id="fazit-thema" hidden></p>
|
||||
<p class="b-fazit__schluss" id="fazit-schluss"></p>
|
||||
<p class="b-fazit__zahlen leise" id="fazit-zahlen"></p>
|
||||
</section>
|
||||
|
||||
<!-- Der Verlauf. Nur für die Person selbst – er wird nirgends sonst
|
||||
im Haus gelesen. Er steht unter den Fragen, weil er sie erklärt
|
||||
und nicht ersetzt. -->
|
||||
<section class="b-verlauf" id="verlauf-block" hidden>
|
||||
<h2 class="e-abschnitt">Wie es sich entwickelt</h2>
|
||||
<p class="b-verlauf__satz" id="verlauf-satz">…</p>
|
||||
<div id="verlauf" class="b-verlauf__gitter"></div>
|
||||
</section>
|
||||
|
||||
<!-- ==================================================================
|
||||
WAS DEIN VERLAUF SAGT (15.09.2026)
|
||||
|
||||
Filipe: „mach mir auch eine analyse […] eine professionelle auch
|
||||
mit infos und so aus aller welt was tiktok angeht."
|
||||
|
||||
Sie steht UNTER dem Verlauf: erst die Antworten, dann das Muster
|
||||
darin. Andersherum läse man die Deutung, bevor man weiß, worauf
|
||||
sie sich bezieht.
|
||||
|
||||
Sie wird gerechnet, nicht gespeichert – und sie bewertet nicht.
|
||||
Keine Punktzahl über das eigene Befinden: Eine Zahl lädt dazu
|
||||
ein, sie zu verbessern statt ehrlich zu antworten.
|
||||
============================================================== -->
|
||||
<section class="b-analyse" id="analyse-block" hidden>
|
||||
<h2 class="e-abschnitt">Was dein Verlauf sagt</h2>
|
||||
<p class="b-analyse__satz" id="analyse-satz">…</p>
|
||||
<div id="analyse" class="b-analyse__liste"></div>
|
||||
<p class="b-analyse__quelle" id="analyse-quelle"></p>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/meldung.js?v=202609201909" defer></script>
|
||||
<script src="assets/js/nachfrage.js?v=202609201909" defer></script>␍
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609201909" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609201909" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609201909" defer></script>
|
||||
<script src="assets/js/befinden.js?v=202609201909" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
<script src="assets/js/nachfrage.js?v=202609201909" defer></script>
|
||||
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609201909" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609201909" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609201909" defer></script>
|
||||
<script src="assets/js/befinden.js?v=202609201909" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -114,6 +114,27 @@
|
||||
<label class="feld-schild" for="f-ende">Bis</label>
|
||||
<input id="f-ende" type="date" />
|
||||
</div>
|
||||
<!-- ZU WELCHEM ACCOUNT GEHOERT DAS? (20.09.2026)
|
||||
|
||||
Filipe: "und wenn man was hinzufuegt soll man auch den
|
||||
account auswaehlen koennen."
|
||||
|
||||
NUR AUF DEN HIGHLIGHTS, und nur dort auch noetig: Bei einem
|
||||
TikTok-Link leitet der Server den Kanal aus dem Handle ab
|
||||
(kanalVonHandle) -- das ist genauer als jede Auswahl, weil
|
||||
niemand sich vertippen kann. Ein Bild, ein Moment oder eine
|
||||
Fanart hat aber keinen Link, und bisher landete so etwas
|
||||
unweigerlich in "Ohne Account".
|
||||
|
||||
"Noch offen" steht an erster Stelle und ist die Vorgabe. Ein
|
||||
Pflichtfeld haette hier einen falschen Kanal erzwungen, und
|
||||
ein falscher Eintrag ist schlechter als ein leerer -- genau
|
||||
das steht so schon an KANAELE im Server. -->
|
||||
<div id="feld-kanal" hidden>
|
||||
<label class="feld-schild" for="f-kanal">Zu welchem Account?</label>
|
||||
<select id="f-kanal"></select>
|
||||
<p class="feld-hinweis" id="hinweis-kanal"></p>
|
||||
</div>
|
||||
<!-- Was DogFather dafuer tun muesste. Nur in Bereichen, die das
|
||||
Feld kennen (siehe BEREICHE.angebote) -- sonst bleibt es
|
||||
verborgen und wird auch nicht mitgeschickt. -->
|
||||
|
||||
Reference in New Issue
Block a user