Entwicklung: die Auswertung je Person -- und elf Seiten ohne Farbe

Filipe: "dan perfektionierst du eine neue wo diagramme texte und so zu
jedem modi gemacht wird und diese kategorie kannst du entwicklung nenen."

1. DAS EIGENTLICHE PROBLEM WAR, DASS ES KEINE VERGANGENHEIT GAB.

`entwicklung_stand` hat den Schluessel (person, punkt, beurteiler) und
wird bei jeder Aenderung UEBERSCHRIEBEN -- dort richtig, eine Beobachtung
ueber einen Menschen soll aktuell sein. Nur: Damit existierte nichts,
woraus sich ein Verlauf zeichnen liesse. Nachgemessen: 43 Zeilen Stand,
NULL Zeilen Geschichte. Ein Diagramm ueber einen einzigen Zeitpunkt ist
kein Diagramm, sondern ein Balken.

Neu: `entwicklung_lauf` haengt jede Aenderung an, statt zu ueberschreiben.
Sie steht in workspace-entwicklung.js, bei der Tabelle, zu der sie
gehoert -- die Auswertung liest nur. Andersherum gaebe es einen Ring
zwischen zwei Modulen, und Ringe halten in JavaScript genau so lange,
bis jemand eine Konstante auf oberster Ebene benutzt.

KEIN ANLASS, NIE. Neben jedem "da hakt es" steht ein Satz, woran man es
gemerkt hat -- fuer ein Gespraech aufgeschrieben. Eine Chronik dieser
Saetze waere das Belastendste, was dieses Haus speichern kann. Die Spur
hat gar keine Spalte dafuer, und die Pruefung liest die TABELLE, nicht
die Ausgabe: Eine Spalte, die es gibt, wird eines Tages gefuellt.

Die Startaufnahme uebernimmt den heutigen Stand mit seinen ECHTEN Daten,
damit die Seite am ersten Tag nicht leer ist -- und zaehlt ausdruecklich
NICHT als Bewegung. Der Tag, an dem die Spur entstand, war kein Tag, an
dem 43 Dinge gleichzeitig passiert sind.

2. WAS GEZEIGT WIRD: BEWEGUNG, KEINE NOTE.

Keine Punktzahl, kein Prozentwert, kein Durchschnitt, keine Rangliste,
kein Vergleich zwischen zwei Menschen. Das ist die Recherche, nicht
Vorsicht: Ueberrechtfertigung (Deci), sozialer Vergleich senkt die
Leistung derer, die schlechter dastehen, Schwaechenfokus ebenso.

Stattdessen das progress principle (Amabile/Kramer, ~12 000
Tagebucheintraege): Sichtbarer Fortschritt ist der staerkste einzelne
Treiber. Deshalb Diagramm 1 "Was sich bewegt hat" -- zwei Richtungen um
eine Nulllinie, ein Massstab fuer beide. Diagramm 2 der Streifen (Punkt
x Monat), fortgeschrieben, aber nach 90 Tagen sichtbar verblasst.
Und die Texte in dieser Reihenfolge: was sich bewegt hat, was traegt,
erst danach was liegt.

Selbst gezeichnet, kein Diagrammpaket: 60 kB ueber die Leitung von
jemandem, der mit dem Handy im Stream steht -- und es braechte eigene
Farben neben die Hausfarben. Jede Zelle traegt ihr Zeichen als Text,
jeder Balken seine Zahl, darunter eine Legende: Die Farbe ist nie die
Auskunft.

Zwei Gesichter: Die Leitung sieht die Karten, ein Modi ausschliesslich
die Bewegung in der eigenen -- ohne Namen, ohne Anlass, ohne Streifen.

3. NEBENBEFUND, GROESSER ALS ERWARTET: ELF SEITEN OHNE FARBE.

Der Umbau von heute frueh heisst "Jede Seite traegt die Farbe ihrer
Kachel". Nachgemessen traf das auf 18 von 34 Seiten zu. Elf Seiten mit
echter Kachel gingen leer aus -- ausgerechnet die von Team Dogi:
befinden, bewerben, bewerbungen, entwicklung, hilfe, rechte, talente,
teamlage, teilen, treff-moderation, treff-regeln, unsere-seiten.

Der Grund: `zuSeite()` sucht nur in `Bereiche.GRUPPEN`, und dort stehen
ausschliesslich die Kacheln der Agentur. Die von Team Dogi liefert der
Server (`ich.bereiche`, `ich.bereiche_zusatz`). Auffallen konnte das
nicht -- eine Seite ohne Farbe sieht aus wie eine, die eben keine hat.

kopf.js faerbt jetzt zweimal: sofort aus GRUPPEN (Agenturseiten ohne
Flackern wie bisher), und noch einmal aus den Serverkacheln, sobald
`werZeigen` sie bringt. Nur, wenn beim ersten Mal nichts gefunden wurde.

pruef-buehne misst den Kontrast an echten Bildpunkten und kannte diese
zwoelf Seiten nicht. Jetzt stehen sie in der Liste; ihre Frist waechst
mit der Laenge, statt als feste Zahl irgendwann zu reissen. Gemessen
fuer werdegang.html: 4,83:1 am Computer, 6,22:1 am Handy.

4. TON 38 SAH FREI AUS UND WAR ES NICHT.

Erster Anlauf war Ton 38, weil 37 die hoechste Nummer in workspace.js
ist. Gezaehlt in start.css sind es 39, alle vergeben. Genau davor warnt
das Farbwerkzeug im eigenen Kopf ("Ton 22, weil er frei AUSSAH").

Dabei aufgefallen: Die zwei Kacheln von heute Nacht wurden ohne das
Werkzeug gesetzt. Ton 39 lag 0,0362 von Ton 15 entfernt -- der engste
Abstand im ganzen Haus, zwei praktisch gleiche Cyan-Toene. Filipe dazu
mehrfach: "keine die sich irgendwie aehnlich sind".

Neues Werkzeug `tools/kachel-farbe-einzeln.mjs`: zieht EINE Farbe nach,
ohne die anderen anzufassen. Das grosse Werkzeug haette alle 40 neu
gefaerbt -- jede Kachel im Haus, ungefragt. Es bricht ab, wenn es nicht
alle Toene lesen kann: Der erste Anlauf las 31 von 40, weil einstellige
Nummern mit ZWEI Leerzeichen dastehen, und rechnete froehlich weiter.

Ergebnis: kleinster Abstand 0,0362 -> 0,0840, durch Aendern von zwei
Kacheln, die es erst seit gestern Nacht gibt.

5. anruf-probe.html lud `basis.css` und `kopf.css` -- beide gibt es
nicht. Ausgerechnet dort faellt es nicht auf, weil die Seite ihre eigene
Stilangabe im Kopf hat. pruef-struktur war deshalb rot (5 Befunde) und
ist jetzt gruen.

GEMESSEN: pruef-werdegang 95/0 (neu), pruef-entwicklung 43/0,
pruef-befinden 75/0, pruef-rechtetafel 19/0, pruef-zwischenspeicher 21/0,
pruef-start-ansicht 0 Fehler, pruef-struktur 0 Fehler (vorher 5),
pruef-buehne fuer werdegang.html 0 Fehler.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-19 10:22:02 +02:00
co-authored by Claude Opus 5
parent 0b73fbb501
commit 19e9f471d3
50 changed files with 3307 additions and 464 deletions
+4
View File
@@ -25,6 +25,9 @@ import { dateienRouter } from "./workspace-dateien.js";
import { bereicheRouter } from "./workspace-bereiche.js";
import { treffRouter } from "./workspace-treff.js";
import { entwicklungRouter } from "./workspace-entwicklung.js";
/* Die Auswertung UEBER den Katalog -- eigene Datei, eigener Gegenstand.
Sie liest nur; geschrieben wird die Spur in workspace-entwicklung.js. */
import { werdegangRouter } from "./workspace-werdegang.js";
import { rechteRouter, tafelLaden } from "./workspace-rechte.js";
import { aufbewahrungRouter, aufbewahrungStarten } from "./workspace-aufbewahrung.js";
import { auskunftRouter } from "./workspace-auskunft.js";
@@ -287,6 +290,7 @@ app.use(treffRouter);
-- beides nebeneinander ist Absicht: Der Katalog ist das, was man
regelmaessig durchgeht, die Notiz das, was dazwischen auffaellt. */
app.use(entwicklungRouter);
app.use(werdegangRouter);
/* Die Rechtetafel -- ansehen und umstellen (11.09.2026). */
app.use(rechteRouter);
app.use(aufbewahrungRouter);
+57 -4
View File
@@ -48,6 +48,56 @@ function dunkelsterStopp(backgroundImage) {
}
const DUNKELSTER_QUELLE = dunkelsterStopp.toString();
/* ZWOELF SEITEN KAMEN AM 19.09.2026 DAZU -- und zwar nicht, weil sie neu
sind, sondern weil sie bis dahin GAR KEINE Buehne hatten.
Der Umbau vom 19.09. frueh heisst "Jede Seite traegt die Farbe ihrer
Kachel". Nachgemessen traf das auf 18 von 34 Seiten zu: `zuSeite()`
sucht nur in `Bereiche.GRUPPEN`, und dort stehen ausschliesslich die
Kacheln der AGENTUR. Die Kacheln von Team Dogi und vom Treff liefert
der Server -- die kannte die Stelle nicht. Seit der Reparatur in
kopf.js bekommen auch sie Ton UND Buehne, und damit steht auf ihnen
zum ersten Mal Text ueber einem Foto. Genau das misst diese Datei.
NUR EINE TEILMENGE LAUFEN LASSEN: `BUEHNE_SEITEN=werdegang.html`.
Ohne die Angabe laeuft die ganze Liste -- das dauert spuerbar und
gehoert deshalb in einen abgesprochenen Lauf, nicht in jede
Zwischenpruefung. */
const ALLE_SEITEN = [
"start.html", "uebersicht.html", "personen.html", "content.html",
"aufgaben.html", "kalender.html",
/* Die zwoelf ohne Buehne, bis zum 19.09.2026: */
"werdegang.html", "entwicklung.html", "talente.html", "befinden.html",
"hilfe.html", "rechte.html", "teamlage.html", "teilen.html",
"bewerbungen.html", "treff-moderation.html", "treff-regeln.html",
"unsere-seiten.html",
];
const SEITEN = process.env.BUEHNE_SEITEN
? process.env.BUEHNE_SEITEN.split(",").map((s) => s.trim()).filter(Boolean)
: ALLE_SEITEN;
/* DRITTER AUSGANG: Wer sich vertippt, soll es merken, statt eine
Prüfung über null Seiten für bestanden zu halten. */
{
const unbekannt = SEITEN.filter((s) => !ALLE_SEITEN.includes(s));
if (!SEITEN.length || unbekannt.length) {
console.log("ABBRUCH: unbekannte Seite(n) in BUEHNE_SEITEN: " + unbekannt.join(", "));
process.exit(2);
}
}
console.log(`Gemessen werden ${SEITEN.length} von ${ALLE_SEITEN.length} Seiten.`);
/* WIE LANGE DAS DAUERN DARF -- aus der Listenlaenge, nicht aus dem Kopf.
Sechs Seiten auf zwei Bildschirmgroessen brauchten bisher knapp
220 Sekunden, also rund 18 s je Seite und Groesse. Aufgerundet auf 22,
damit ein langsamer Lauf nicht faelschlich als Fehlschlag endet.
Eine feste Zahl waere hier eine Zeitbombe: Mit den zwoelf neuen
Seiten waere die alte Frist gerissen, und der Abbruch saehe aus wie
ein Befund. */
const SEITEN_ZEIT = SEITEN.length * 2 * 22_000;
console.log(`Zeitrahmen: ${Math.round((100_000 + SEITEN_ZEIT) / 1000)} s.`);
const ordner = mkdtempSync(join(tmpdir(), "ws-buehne-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
process.env.PORT = "4193";
@@ -66,10 +116,14 @@ await import("./index.js");
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-buehne");
notbremse(120_000 + SEITEN_ZEIT, "pruef-buehne");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4193";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 220_000).unref?.();
/* Die Frist waechst mit der Liste: 220 s reichten fuer sechs Seiten.
Eine feste Zahl waere hier eine Zeitbombe -- die zwoelf neuen
haetten sie gerissen, und der Abbruch saehe aus wie ein Befund. */
setTimeout(() => { console.log("ABBRUCH: Zeit ueberschritten"); process.exit(1); },
100_000 + SEITEN_ZEIT).unref?.();
let fehler = 0;
const ok = (b, t) => { console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
@@ -109,8 +163,7 @@ const browser = await chromium.launch();
/* Der Kalender kam am 01.09.2026 dazu -- er bringt die meisten neuen
Farbflächen mit (vier Arten, Feiertagsfärbung, Anlassmarken), und
genau dort entscheidet sich, ob der hellere Hintergrund noch trägt. */
const SEITEN = ["start.html", "uebersicht.html", "personen.html", "content.html",
"aufgaben.html", "kalender.html"];
for (const [breite, hoehe, wie] of [[1440, 950, "Computer"], [390, 780, "Handy"]]) {
console.log(`\n=== ${wie} (${breite}x${hoehe})`);
+626
View File
@@ -0,0 +1,626 @@
/* =====================================================================
ENTWICKLUNG — die Auswertung je Person (19.09.2026)
Filipe: „dan perfektionierst du eine neue wo diagramme texte und so
zu jedem modi gemacht wird und diese kategorie kannst du entwicklung
nenen."
---------------------------------------------------------------------
WAS HIER GEPRÜFT WIRD — UND WARUM GENAU DAS
Eine Seite, die Diagramme über MENSCHEN zeigt, kann auf drei Arten
falsch sein, und nur eine davon ist ein Programmfehler:
1. SIE ZEIGT ZU VIEL. Der Anlass eines „da hakt es“ ist für ein
Gespräch aufgeschrieben. Landete er in der Spur, wäre jede
längst geklärte Beobachtung auf Dauer lesbar. Block 5 („Wie
geht es dir damit?“) darf überhaupt nicht hierherkommen.
2. SIE BEWERTET. Punktzahl, Prozentwert, Rangliste, Vergleich
zwischen zwei Menschen — nichts davon darf entstehen, auch
nicht versehentlich über einen Umweg.
3. SIE BEHAUPTET EINEN VERLAUF, DEN ES NICHT GIBT. Ein Diagramm
über einen einzigen Zeitpunkt ist kein Diagramm. Dann muss die
Seite das SAGEN — der dritte Ausgang.
ZU JEDER PRÜFUNG, DIE „ALLES IN ORDNUNG“ SAGEN KANN, GEHÖRT EINE
GEGENPROBE, die beweist, dass sie auch „nicht in Ordnung“ sagen
kann. Eine Prüfung, die immer bestätigt, bestätigt nichts.
Aufrufen mit: node server/pruef-werdegang.mjs
===================================================================== */
import { mkdtempSync, rmSync, readFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { request as httpAnfrage } from "node:http";
import { portMussFreiSein } from "./helfer-port.mjs";
const PORT = await portMussFreiSein(4422, "pruef-werdegang");
const ordner = mkdtempSync(join(tmpdir(), "ws-weg-"));
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 }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
notbremse(180_000, "pruef-werdegang");
await new Promise((r) => setTimeout(r, 700));
process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); });
process.on("unhandledRejection", (f) => { console.error("ABSTURZ (Versprechen):", f); process.exit(7); });
let fehler = 0, geprueft = 0;
const melde = (t) => console.log(t);
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
const CREW = "crew.dogfather-universe.com";
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
const W = await import("./workspace-werdegang.js");
const kat = await import("./workspace-entwicklungs-punkte.js");
const rechte = await import("./rechte.js");
const ent = await import("./workspace-entwicklung.js");
const oeffne = () => new DatabaseSync(process.env.WORKSPACE_DB);
const jetzt = new Date().toISOString();
/* ABGELEITET, NICHT ABGESCHRIEBEN. Jede feste Zahl in einer Prüfung ist
eine Rechnung von gestern -- am 17.09. wurden aus sechs Fragen zwölf
und vier Prüfungen waren rot, ohne dass etwas kaputt war. */
const PUNKTE = kat.ENTWICKLUNG_FREMD;
const SELBST = kat.ENTWICKLUNG_SELBST;
let idDogi, idHand, idModi, idCreator;
{
const d = oeffne();
const schluessel = randomBytes(32).toString("hex");
d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)"
+ " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING")
.run("code_kennung_schluessel", schluessel, jetzt);
const anlegen = (name, rolle, code) => {
const salt = randomBytes(16).toString("hex");
const hash = scryptSync(code, salt, 64, { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, code_kennung, aktiv, erstellt)"
+ " VALUES (?,?,?,?,?,?,1,?)")
.run(name, rolle, hash, salt, 32768, createHmac("sha256", schluessel).update(code).digest("hex"), jetzt);
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
};
idDogi = anlegen("Filipe", "admin", "CODE-DOGI-0001");
idHand = anlegen("Rieke", "hand", "CODE-HAND-0001");
idModi = anlegen("Diene", "modi", "CODE-MODI-0001");
idCreator = anlegen("Tili", "creator", "CODE-CREA-0001");
d.close();
}
function ruf(pfad, host, methode, kopf = {}, koerper = null) {
return new Promise((fertig, schief) => {
const a = httpAnfrage({ host: "127.0.0.1", port: PORT, path: pfad, method: methode,
headers: { Host: host, ...(koerper ? { "Content-Type": "application/json" } : {}), ...kopf } },
(antwort) => {
let text = "";
antwort.on("data", (s) => { text += s; });
antwort.on("end", () => fertig({ code: antwort.statusCode, text, kopf: antwort.headers }));
});
a.on("error", schief);
if (koerper) a.write(JSON.stringify(koerper));
a.end();
});
}
const json = (a) => { try { return JSON.parse(a.text); } catch { return {}; } };
async function anmelden(rolle, code, host) {
const r = await ruf("/workspace/api/anmelden", host, "POST", {}, { rolle, code });
return [].concat(r.kopf?.["set-cookie"] || []).map((c) => c.split(";")[0]).join("; ");
}
const kDogi = await anmelden("admin", "CODE-DOGI-0001", CREW);
const kHand = await anmelden("hand", "CODE-HAND-0001", CREW);
const kModi = await anmelden("modi", "CODE-MODI-0001", CREW);
ok(!!kDogi && !!kHand && !!kModi, "DogFather, rechte Hand und ein Modi sind angemeldet");
/* =======================================================================
1. DIE KACHEL HEISST „ENTWICKLUNG“ UND FÜHRT AUCH DORTHIN
Der ganze Auftrag hängt an einer Umbenennung: Die alte Kachel heißt
seit heute früh „Eure Aufgaben“, damit der Name frei wird. Wenn die
neue Kachel auf die alte Seite zeigt, ist genau nichts gewonnen.
======================================================================= */
melde("");
melde("=== 1. Die Kachel ===");
{
const ich = json(await ruf("/workspace/api/ich", CREW, "GET", { cookie: kDogi }));
const alle = [...(ich.bereiche || []), ...(ich.bereiche_zusatz || [])];
const neu = alle.find((k) => k.name === "Entwicklung");
ok(!!neu, "eine Kachel „Entwicklung“ steht bei DogFather");
ok(neu?.ziel === "werdegang.html",
`und führt auf die Auswertung (${neu?.ziel})`);
/* GEGENPROBE. Ohne sie bewiese die Zeile darüber nichts: Der Katalog
muss weiterhin erreichbar sein, unter seinem neuen Namen. */
const alt = alle.find((k) => k.ziel === "entwicklung.html");
ok(alt?.name === "Eure Aufgaben",
`der Katalog heißt weiter „${alt?.name}“ und liegt woanders`);
ok(neu?.ziel !== alt?.ziel, "beide Kacheln zeigen auf VERSCHIEDENE Seiten");
/* ZWEI KACHELN, ZWEI FARBEN. Gleiche Farbe hieße, dass man sie auf
der Startseite verwechselt -- und sie stehen nebeneinander. */
ok(neu?.ton && alt?.ton && neu.ton !== alt.ton,
`verschiedene Farbtöne (${alt?.ton} / ${neu?.ton})`);
const css = readFileSync(
new URL("../workspace/assets/css/start.css", import.meta.url), "utf8");
ok(new RegExp(`\\[data-ton="${neu?.ton}"\\]`).test(css),
`Ton ${neu?.ton} ist in start.css wirklich definiert`);
/* UND BEI DER RECHTEN HAND AUCH. Sie arbeitet an denselben Dingen --
eine Kachel nur bei DogFather wäre die Über-/Unterordnung, die es
in diesem Haus nicht gibt. */
const beiHand = json(await ruf("/workspace/api/ich", CREW, "GET", { cookie: kHand }));
ok([...(beiHand.bereiche || []), ...(beiHand.bereiche_zusatz || [])]
.some((k) => k.ziel === "werdegang.html"),
"die rechte Hand hat dieselbe Kachel");
}
/* =======================================================================
2. WER HINEINDARF
======================================================================= */
melde("");
melde("=== 2. Wer hineindarf ===");
{
const tafel = rechte.tafelJetzt();
const erlaubt = tafel["/workspace/werdegang.html"] || [];
ok(erlaubt.length > 0, "die Seite steht in der Rechtetafel");
ok(erlaubt.includes("admin") && erlaubt.includes("hand"),
"DogFather und die rechte Hand dürfen sie öffnen");
ok(erlaubt.includes("modi"),
"ein Modi auch — er sieht dort seine eigene Bewegung");
/* GEGENPROBE: Die Agentur hat gar keine Entwicklungskarte. Eine Seite,
die man öffnen kann und die dann leer bleibt, sieht aus wie ein
Fehler -- und ist einer. */
ok(!erlaubt.includes("creator") && !erlaubt.includes("gast"),
"die Agentur und die Community nicht");
ok(json(await ruf("/workspace/api/werdegang/liste", CREW, "GET", { cookie: kDogi })).liste !== undefined,
"DogFather bekommt die Liste");
const beiModi = await ruf("/workspace/api/werdegang/liste", CREW, "GET", { cookie: kModi });
ok(beiModi.code === 404,
`ein Modi bekommt sie nicht (${beiModi.code} — nicht 403: wer „kein Zugriff“ liest, fragt als Nächstes, wer denn dann)`);
const fremdeKarte = await ruf(`/workspace/api/werdegang/person/${idHand}`, CREW, "GET", { cookie: kModi });
ok(fremdeKarte.code === 404, `und auch keine fremde Karte (${fremdeKarte.code})`);
const ohne = await ruf("/workspace/api/werdegang/liste", CREW, "GET", {});
ok(ohne.code === 401, `ohne Anmeldung gar nichts (${ohne.code})`);
}
/* =======================================================================
3. DIE SPUR — SIE MERKT SICH, WAS DIE TABELLE VERGISST
======================================================================= */
melde("");
melde("=== 3. Die Spur ===");
{
const p1 = PUNKTE[0].schluessel, p2 = PUNKTE[1].schluessel;
const a = await ruf(`/workspace/api/entwicklung/person/${idModi}/${p1}`, CREW, "PUT",
{ cookie: kDogi }, { stufe: "waechst" });
ok(a.code === 200, "eine Einschätzung lässt sich setzen");
const b = await ruf(`/workspace/api/entwicklung/person/${idModi}/${p1}`, CREW, "PUT",
{ cookie: kDogi }, { stufe: "laeuft" });
ok(b.code === 200, "und ändern");
const d = oeffne();
const spur = d.prepare(
"SELECT * FROM entwicklung_lauf WHERE person_id = ? ORDER BY id").all(idModi);
ok(spur.length === 2, `beide Schritte stehen in der Spur (${spur.length})`);
ok(spur[0].vorher === null && spur[0].stufe === "waechst",
"der erste hat kein „vorher“ — er war der erste");
ok(spur[1].vorher === "waechst" && spur[1].stufe === "laeuft",
"der zweite weiß, was vorher dastand (waechst → laeuft)");
ok(spur.every((z) => z.nachgetragen === 0),
"beide sind beobachtet, nicht nachgetragen");
/* ==== DIE WICHTIGSTE PRÜFUNG DER GANZEN DATEI =====================
KEIN ANLASS IN DER SPUR. Neben einem „da hakt es“ steht ein Satz,
woran man es gemerkt hat. Eine Chronik dieser Sätze wäre das
Belastendste, was dieses Haus speichern kann.
GEPRÜFT WIRD DIE TABELLE SELBST, nicht die Ausgabe: Eine Spalte,
die es gibt, wird eines Tages gefüllt.
============================================================== */
const spalten = d.prepare("PRAGMA table_info(entwicklung_lauf)").all().map((z) => z.name);
ok(!spalten.includes("anlass"),
`die Spur hat gar keine Spalte für den Anlass (${spalten.join(", ")})`);
/* Und der Anlass steht trotzdem dort, wo er hingehört -- sonst wäre
die Zeile oben nur deshalb grün, weil die Funktion gar nichts tut. */
await ruf(`/workspace/api/entwicklung/person/${idModi}/${p2}`, CREW, "PUT",
{ cookie: kDogi }, { stufe: "hakt", anlass: "Am Dienstag im Stream, zweimal nacheinander." });
const stand = d.prepare(
"SELECT anlass FROM entwicklung_stand WHERE person_id = ? AND schluessel = ?")
.get(idModi, p2);
ok(/Dienstag/.test(stand?.anlass || ""),
"im Katalog steht er weiterhin — dort gehört er hin");
const inSpur = d.prepare(
"SELECT COUNT(*) AS n FROM entwicklung_lauf WHERE person_id = ? AND schluessel = ?")
.get(idModi, p2).n;
ok(inSpur === 1, "die Spur kennt den Punkt, nicht den Satz dazu");
/* BLOCK 5 DARF NIE HIERHERKOMMEN. „Wie geht es dir damit?“ beantwortet
nur die Person selbst; diese Antworten haben eine eigene Tabelle,
die niemand sonst liest. */
const selbst = SELBST[0].schluessel;
await ruf(`/workspace/api/entwicklung/mich/${selbst}`, CREW, "PUT",
{ cookie: kModi }, { stufe: "hakt" });
const inSpur2 = d.prepare(
"SELECT COUNT(*) AS n FROM entwicklung_lauf WHERE schluessel = ?").get(selbst).n;
ok(inSpur2 === 0, "eine Selbstauskunft aus Block 5 landet NICHT in der Spur");
/* Gegenprobe: sie ist trotzdem gespeichert worden. */
const selbstStand = d.prepare(
"SELECT stufe FROM entwicklung_stand WHERE person_id = ? AND schluessel = ?")
.get(idModi, selbst);
ok(selbstStand?.stufe === "hakt",
"sie steht aber sehr wohl im Katalog — die Zeile darüber ist keine Ausrede");
d.close();
}
/* =======================================================================
4. DIE ÜBERGÄNGE HEISSEN RICHTIG
Reine Rechnung, ohne Server -- und mit Gegenprobe: Wer nur prüft,
dass „vor“ herauskommt, wenn „vor“ gemeint ist, hätte auch mit einer
Funktion recht, die IMMER „vor“ sagt.
======================================================================= */
melde("");
melde("=== 4. Die Übergänge ===");
{
ok(W.artVon(null, "laeuft", 0) === "neu", "zum ersten Mal gesetzt → neu");
ok(W.artVon("hakt", "waechst", 0) === "vor", "hakt → wächst ist ein Schritt nach vorn");
ok(W.artVon("waechst", "laeuft", 0) === "vor", "wächst → läuft auch");
ok(W.artVon("laeuft", "hakt", 0) === "zurueck", "läuft → hakt ist einer zurück");
ok(W.artVon("unklar", "laeuft", 0) === "geklaert",
"aus „kann ich nicht sagen“ wird etwas → geklärt, nicht „vor“");
ok(W.artVon("laeuft", "unklar", 0) === "offen",
"und umgekehrt → offen, nicht „zurück“");
ok(W.artVon("laeuft", "laeuft", 0) === "gleich", "derselbe Wert ist keine Bewegung");
ok(W.artVon(null, "laeuft", 1) === "anfang",
"die Startaufnahme ist der Nullpunkt, nicht ein Ereignis");
/* GEGENPROBE zur Rangfolge: „kann ich nicht sagen“ hat KEINEN Rang.
Bekäme sie einen, würde „ich habe es nicht gesehen“ irgendwann wie
ein halbes „da hakt es“ gelesen. */
ok(W.artVon("unklar", "hakt", 0) === "geklaert",
"auch unklar → hakt ist geklärt und kein Rückschritt");
}
/* =======================================================================
5. DIE MONATSSPALTEN — UND DIE FALLE AM MONATSENDE
======================================================================= */
melde("");
melde("=== 5. Die Monatsspalten ===");
{
const s = W.monatsspalten();
ok(s.length === W.MONATE, `${s.length} Spalten`);
ok(s.join(",") === [...s].sort().join(","), "älteste zuerst");
/* DIE FALLE: Wer am 31. März einen Monat abzieht, landet in
JavaScript wieder im März -- den 31. Februar gibt es nicht, und
das Datum rollt über. Eine Spalte fiele aus, und zwar still. */
const ende = W.monatsspalten(6, new Date("2026-03-31T12:00:00Z"));
ok(ende.join(" ") === "2025-10 2025-11 2025-12 2026-01 2026-02 2026-03",
`am 31. März stimmen alle sechs Monate (${ende.join(" ")})`);
ok(new Set(ende).size === 6, "kein Monat doppelt, keiner übersprungen");
/* UND DIE ZWEITE FALLE: toISOString rechnet in UTC. Zwischen
Mitternacht und zwei Uhr am Monatsersten liefert das den VORMONAT.
Gerechnet wird deshalb in Europe/Berlin. */
ok(W.monatVon("2026-04-01T00:30:00+02:00") === "2026-04",
"am 1. April um 00:30 Ortszeit ist es April, nicht März");
ok(W.monatVon("2026-03-31T23:30:00+02:00") === "2026-03",
"und am 31. März um 23:30 noch März");
ok(W.monatVon("kaputt") === null, "ein unlesbares Datum ergibt null, nicht „jetzt“");
}
/* =======================================================================
6. DIE AUSWERTUNG — UND DER DRITTE AUSGANG
======================================================================= */
melde("");
melde("=== 6. Die Auswertung ===");
{
const d = json(await ruf(`/workspace/api/werdegang/person/${idModi}`, CREW, "GET", { cookie: kDogi }));
ok(d.person?.name === "Diene", "die Karte gehört der richtigen Person");
ok(d.gesetzt === 2, `zwei Punkte sind beantwortet (${d.gesetzt})`);
ok(d.gesamt === PUNKTE.length,
`von ${PUNKTE.length} im Katalog (${d.gesamt}) — gezählt, nicht abgeschrieben`);
/* DER DRITTE AUSGANG. Alles ist heute passiert, also gibt es genau
EINEN Monat mit Inhalt. Die Seite muss das SAGEN statt eine Linie
zwischen einem Wert und dem Rand zu zeichnen. */
ok(d.verlauf_moeglich === false,
"mit einem einzigen Monat wird kein Verlauf behauptet");
ok(d.monate_mit_inhalt === 1 && d.verlauf_ab === W.VERLAUF_AB,
`und die Antwort sagt, warum (${d.monate_mit_inhalt} von ${d.verlauf_ab} nötigen Monaten)`);
/* KEINE NOTE. Nirgends im ganzen Datenpaket darf eine Punktzahl, ein
Prozentwert oder ein Durchschnitt stehen. Geprüft am ROHTEXT --
ein Feld, das ich hier namentlich abfrage, müsste ich kennen; ein
neues, das jemand später hinzufügt, fiele durch. */
const roh = JSON.stringify(d).toLowerCase();
ok(!/"(punktzahl|score|prozent|durchschnitt|note|rang|platz)"/.test(roh),
"kein Feld heißt Punktzahl, Prozent, Durchschnitt, Note oder Rang");
ok(!/anlass/.test(roh), "und der Anlass kommt nicht mit");
ok(!roh.includes("dienstag"),
"auch nicht sein Inhalt — die Zeile darüber ist keine Wortklauberei");
/* KEIN FREMDER NAME. Auf einer Karte steht genau ein Mensch. */
ok(!roh.includes("rieke") && !roh.includes("tili"),
"auf der Karte steht kein anderer Name");
ok(typeof d.satz === "string" && d.satz.length > 20,
"ein Satz fasst die Karte zusammen");
ok(Array.isArray(d.texte), "und es gibt Texte dazu");
const arten = d.texte.map((t) => t.art);
ok(arten.includes("offen"),
"darunter der Hinweis, worüber die Auswertung schweigt");
/* DIE REIHENFOLGE IST DIE AUSSAGE: Was sich bewegt hat und was trägt,
steht VOR dem, was liegt. Rückmeldung, die auf Defizite zeigt,
senkt die Leistung messbar. */
const iVor = arten.indexOf("vor"), iLiegt = arten.indexOf("liegt");
ok(iLiegt === -1 || iVor === -1 || iVor < iLiegt,
"und was sich bewegt hat steht vor dem, was liegt");
}
/* =======================================================================
7. ZWEI MONATE — JETZT ENTSTEHT EIN VERLAUF
GEGENPROBE zu Abschnitt 6: Ohne sie wäre „kein Verlauf möglich“ nur
deshalb grün, weil die Funktion vielleicht NIE einen Verlauf sieht.
======================================================================= */
melde("");
melde("=== 7. Zwei Monate ===");
{
/* Eine Zeile mit einem Datum aus dem Vormonat -- von Hand in die
Spur, weil man die Uhr nicht vorstellen kann. Der Weg dorthin ist
derselbe, den die Anwendung benutzt. */
const d = oeffne();
const vormonat = new Date();
vormonat.setDate(15);
vormonat.setMonth(vormonat.getMonth() - 1);
const p3 = PUNKTE[2].schluessel;
d.prepare(`INSERT INTO entwicklung_lauf
(person_id, schluessel, von_id, stufe, vorher, nachgetragen, am)
VALUES (?,?,?,?,NULL,0,?)`)
.run(idModi, p3, idDogi, "hakt", vormonat.toISOString());
d.prepare(`INSERT INTO entwicklung_stand
(person_id, schluessel, von_id, stufe, anlass, am)
VALUES (?,?,?,?,NULL,?)`)
.run(idModi, p3, idDogi, "hakt", vormonat.toISOString());
d.close();
const k = json(await ruf(`/workspace/api/werdegang/person/${idModi}`, CREW, "GET", { cookie: kDogi }));
ok(k.verlauf_moeglich === true, "mit zwei Monaten gibt es einen Verlauf");
ok(k.spalten.length === W.MONATE, `${k.spalten.length} Monatsspalten`);
ok(k.streifen.length === 3, `drei Punkte im Streifen (${k.streifen.length})`);
/* FORTGESCHRIEBEN, NICHT LÜCKENHAFT: Was im Vormonat galt, gilt auch
diesen Monat -- niemand hat es geändert. Eine leere Zelle hieße
„keine Einschätzung“, und das wäre falsch. */
const zeile = k.streifen.find((z) => z.schluessel === p3);
const letzte = zeile.zellen.at(-1);
ok(letzte.stufe === "hakt",
"eine Einschätzung aus dem Vormonat gilt auch diesen Monat weiter");
ok(zeile.zellen.filter((c) => c.stufe).length === 2,
"davor steht nichts — vor der ersten Einschätzung gab es keine");
/* DIE BEWEGUNG ZÄHLT DAS RICHTIGE. */
const summe = (f) => k.bewegung.reduce((s, m) => s + m[f], 0);
ok(summe("neu") === 3, `drei Punkte wurden zum ersten Mal gesetzt (${summe("neu")})`);
ok(summe("vor") === 1, `einer ging nach vorn (${summe("vor")})`);
ok(summe("zurueck") === 0, `keiner zurück (${summe("zurueck")})`);
}
/* =======================================================================
8. DIE STARTAUFNAHME ZÄHLT NICHT ALS BEWEGUNG
Der Tag, an dem die Spur entstand, war kein Tag, an dem 43 Dinge
gleichzeitig passiert sind.
======================================================================= */
melde("");
melde("=== 8. Die Startaufnahme ===");
{
const d = oeffne();
const p4 = PUNKTE[3].schluessel;
const alt = new Date(); alt.setDate(15); alt.setMonth(alt.getMonth() - 2);
d.prepare(`INSERT INTO entwicklung_lauf
(person_id, schluessel, von_id, stufe, vorher, nachgetragen, am)
VALUES (?,?,?,?,NULL,1,?)`)
.run(idHand, p4, idDogi, "laeuft", alt.toISOString());
d.prepare(`INSERT INTO entwicklung_stand
(person_id, schluessel, von_id, stufe, anlass, am)
VALUES (?,?,?,?,NULL,?)`)
.run(idHand, p4, idDogi, "laeuft", alt.toISOString());
d.close();
const k = json(await ruf(`/workspace/api/werdegang/person/${idHand}`, CREW, "GET", { cookie: kDogi }));
const summe = (f) => k.bewegung.reduce((s, m) => s + m[f], 0);
ok(summe("neu") + summe("vor") + summe("zurueck") + summe("geklaert") === 0,
"eine nachgetragene Zeile ist keine Bewegung");
/* Gegenprobe: sie ist trotzdem da -- im Streifen. Sonst wäre die
Zeile oben nur deshalb grün, weil gar nichts gespeichert wurde. */
ok(k.streifen.length === 1 && k.streifen[0].zellen.some((c) => c.stufe === "laeuft"),
"im Streifen steht sie sehr wohl");
ok(/nichts bewegt/.test(k.satz),
`und der Satz sagt es ehrlich: ${(k.satz || "").slice(0, 60)}…`);
}
/* =======================================================================
9. DIE EIGENE SICHT — OHNE NAMEN, OHNE ANLASS, OHNE STREIFEN
======================================================================= */
melde("");
melde("=== 9. Die eigene Sicht ===");
{
const m = json(await ruf("/workspace/api/werdegang/mich", CREW, "GET", { cookie: kModi }));
ok(m.hat_karte === true, "ein Modi hat eine eigene Karte");
ok(Array.isArray(m.bewegung) && m.bewegung.length === W.MONATE,
"und sieht seine Bewegung über dieselben Monate");
ok(m.streifen === undefined,
"aber KEINEN Streifen — welcher Punkt wackelte, sagt ihm nur ein Mensch");
const roh = JSON.stringify(m).toLowerCase();
ok(!roh.includes("filipe") && !roh.includes("rieke"),
"kein Name eines Beurteilers");
ok(!roh.includes("dienstag"), "kein Anlasstext");
ok(/frag/i.test(m.hinweis || ""),
"stattdessen der Hinweis, danach zu fragen");
/* Seine eigene Antwort aus Block 5 darf hier nicht auftauchen -- sie
hat er über SICH gegeben, nicht jemand über ihn. */
ok(m.bewegung.reduce((s, x) => s + x.neu, 0) === 3,
"gezählt werden nur die Einschätzungen anderer");
/* GEGENPROBE: DogFather hat keine Karte -- und bekommt einen Satz
statt einer leeren Seite. */
const beiDogi = json(await ruf("/workspace/api/werdegang/mich", CREW, "GET", { cookie: kDogi }));
ok(beiDogi.hat_karte === false, "für DogFather wird keine Karte geführt");
}
/* =======================================================================
10. DIE LISTE VERGLEICHT NIEMANDEN
======================================================================= */
melde("");
melde("=== 10. Die Liste ===");
{
const l = json(await ruf("/workspace/api/werdegang/liste", CREW, "GET", { cookie: kDogi }));
ok(l.liste.length === 2, `zwei Karten — die rechte Hand und der Modi (${l.liste.length})`);
/* GEGENPROBE: Ein Creator steht NICHT drin. Für die Agentur wird
keine Entwicklungskarte geführt. */
ok(!l.liste.some((p) => p.name === "Tili"), "ein Creator steht nicht darin");
ok(!l.liste.some((p) => p.name === "Filipe"), "DogFather auch nicht");
/* NACH NAMEN SORTIERT, NICHT NACH AKTIVITÄT. Eine Liste, die die mit
der meisten Bewegung nach oben stellt, ist eine Rangliste mit einem
anderen Namen. */
const namen = l.liste.map((p) => p.name);
ok(namen.join(",") === [...namen].sort((a, b) => a.localeCompare(b, "de")).join(","),
`alphabetisch (${namen.join(", ")})`);
ok(/kein vergleich/i.test(l.hinweis || ""),
"und die Seite sagt selbst, dass sie niemanden vergleicht");
}
/* =======================================================================
11. IM BROWSER
Der Teil, den keine Schnittstellenprüfung ersetzt: Zeichnet die Seite
wirklich etwas, und bleibt die Konsole still?
UND DER NEBENBEFUND VOM 19.09.: Bis heute bekamen elf Seiten von
Team Dogi KEINE Kachelfarbe, weil `zuSeite()` nur die Agenturliste
kennt. Hier wird nachgemessen, dass diese Seite ihre bekommt.
======================================================================= */
melde("");
melde("=== 11. Im Browser ===");
{
const { chromium } = await import(
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
const browser = await chromium.launch();
const kontext = await browser.newContext({ viewport: { width: 1280, height: 1000 } });
const seite = await kontext.newPage();
const konsole = [];
seite.on("console", (m) => { if (m.type() === "error" && !/favicon|ERR_/i.test(m.text())) konsole.push(m.text()); });
seite.on("pageerror", (e) => konsole.push("Absturz: " + e.message));
await kontext.addCookies(kDogi.split("; ").filter(Boolean).map((teil) => {
const i = teil.indexOf("=");
return { name: teil.slice(0, i), value: teil.slice(i + 1), domain: "127.0.0.1", path: "/" };
}));
await seite.goto(`http://127.0.0.1:${PORT}/workspace/werdegang.html`, { waitUntil: "networkidle" });
await seite.waitForTimeout(900);
const liste = await seite.evaluate(() => ({
karten: document.querySelectorAll('.w-karte').length,
hinweis: document.getElementById('liste-hinweis')?.textContent || '',
ton: document.body.dataset.ton || null,
buehne: document.body.dataset.buehne || null,
zeichen: !!document.querySelector('.kopf-zeichen'),
}));
ok(liste.karten === 2, `zwei Karten stehen auf der Seite (${liste.karten})`);
ok(/kein vergleich/i.test(liste.hinweis), "der Hinweis steht sichtbar darüber");
ok(liste.ton === "40",
`die Seite trägt die Farbe ihrer Kachel (data-ton=${liste.ton}) — das ging bis heute nur auf den Agenturseiten`);
ok(liste.buehne === "wald", `und ihre Bühne (${liste.buehne})`);
ok(liste.zeichen, "das Zeichen der Kachel steht im Kopf");
await seite.locator('.w-karte').first().click();
await seite.waitForTimeout(700);
const karte = await seite.evaluate(() => ({
name: document.getElementById('karte-name')?.textContent || '',
balken: document.querySelectorAll('.w-bewegung__balken').length,
zellen: document.querySelectorAll('.w-streifen__zelle').length,
zeichenInZellen: [...document.querySelectorAll('.w-streifen__zelle')]
.every((z) => (z.textContent || '').trim().length > 0),
legende: document.querySelectorAll('.w-legende__punkt').length,
texte: document.querySelectorAll('.w-text').length,
breiter: document.documentElement.scrollWidth > document.documentElement.clientWidth,
}));
ok(/Diene|Rieke/.test(karte.name), `eine Karte öffnet sich (${karte.name})`);
ok(karte.zellen > 0, `der Streifen ist gezeichnet (${karte.zellen} Zellen)`);
/* DIE FARBE IST NIE DIE AUSKUNFT. Jede Zelle trägt ihr Zeichen als
Text -- wer Farben schlecht unterscheidet, liest dasselbe. */
ok(karte.zeichenInZellen, "jede Zelle trägt ihr Zeichen, nicht nur ihre Farbe");
ok(karte.legende === 2, `unter dem Diagramm steht eine Legende (${karte.legende} Einträge)`);
ok(karte.texte > 0, `und die Texte dazu (${karte.texte})`);
ok(!karte.breiter, "nichts steht über den rechten Rand hinaus");
/* HANDY. Zwölf Seiten hatten am 06.09. genau hier ihren Fehler: ein
Knopf lag über einem anderen, weil eine feste Schwelle nicht mehr
passte. */
await seite.setViewportSize({ width: 412, height: 915 });
await seite.waitForTimeout(500);
const handy = await seite.evaluate(() => ({
breiter: document.documentElement.scrollWidth > document.documentElement.clientWidth + 1,
balkenSichtbar: [...document.querySelectorAll('.w-bewegung__balken')]
.every((b) => b.getBoundingClientRect().width >= 8),
}));
ok(!handy.breiter, "auf 412 px steht nichts über den Rand hinaus");
ok(handy.balkenSichtbar, "und die Balken sind noch breit genug, um sie zu sehen");
if (process.env.SCHIRM) {
await seite.setViewportSize({ width: 1280, height: 1100 });
await seite.waitForTimeout(400);
await seite.screenshot({
path: "C:/Users/qciga/Documents/Obelix/DogiHompage/pruef-werdegang.png",
fullPage: true,
});
}
ok(konsole.length === 0,
konsole.length ? "Fehler im Browser: " + konsole[0].slice(0, 120) : "keine Fehler im Browser");
await browser.close();
}
/* =======================================================================
12. DIE BEIDEN ROLLENLISTEN DÜRFEN NICHT AUSEINANDERLAUFEN
======================================================================= */
melde("");
melde("=== 12. Was zusammengehört ===");
{
const hier = W.KARTEN_ROLLEN.join(",");
const quelle = readFileSync(
new URL("./workspace-entwicklung.js", import.meta.url), "utf8");
const dort = (quelle.match(/const KARTEN_ROLLEN = \[([^\]]+)\]/) || [])[1] || "";
const sauber = dort.replace(/["\s]/g, "");
ok(sauber === hier,
`dieselben Rollen in beiden Dateien (${hier} / ${sauber})`);
}
/* ------------------------------------------------------------------- */
melde("");
melde(`${geprueft} Pruefungen, ${fehler} Fehler`);
melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest */ }
process.exit(fehler ? 1 : 0);
+15
View File
@@ -345,6 +345,21 @@ export const SEITEN = {
"/workspace/befinden.html": ["admin", "hand", "modi"],
"/workspace/entwicklung.html": ["admin", "hand", "modi"],
/* DIE AUSWERTUNG (19.09.2026) -- Kachel "Entwicklung".
MIT "modi", obwohl die Liste aller Karten nur die Leitung sieht.
Die Seite hat zwei Gesichter: Wer das Team fuehrt, sieht die
Karten; wer dazugehoert, sieht ausschliesslich die Bewegung in der
EIGENEN -- ohne Namen, ohne Anlass, ohne wer-hat-was.
Dass die Person ihren eigenen Fortschritt sieht, ist kein
Zugestaendnis, sondern der Punkt: Sichtbarer Fortschritt ist nach
der Forschung der staerkste einzelne Treiber guter Arbeit. Die
Schranke dahinter sitzt in der Route (`/werdegang/liste` und
`/werdegang/person/:id` antworten einem Modi mit 404), nicht hier.
Das hier ist der Riegel davor, nicht statt. */
"/workspace/werdegang.html": ["admin", "hand", "modi"],
/* TALENTE. Hier stehen Namen von Menschen, die nichts davon wissen --
ausdruecklich OHNE "modi": Wer beobachtet wird, ohne es zu wissen,
hat ein Recht darauf, dass der Kreis klein bleibt. */
+161 -22
View File
@@ -13,7 +13,7 @@
---------------------------------------------------------------------
EINE EIGENE TABELLE — UND WARUM DER PLAN DARIN FALSCH LAG
Der Plan im Vault sagt: „zwei Spalten, keine neue Tabelle" — der
Der Plan im Vault sagt: „zwei Spalten, keine neue Tabelle“ — der
Stand solle in `punkt_stand` liegen, derselben Tabelle wie die
Checkliste. Nachgemessen geht das NICHT:
@@ -32,7 +32,7 @@
---------------------------------------------------------------------
DREI RIEGEL, DIE VON ANFANG AN STEHEN
1. BLOCK 5 GEHOERT DER PERSON. „Wie geht es dir damit?" beantwortet
1. BLOCK 5 GEHOERT DER PERSON. „Wie geht es dir damit?“ beantwortet
nur sie selbst — lesend UND schreibend. Auch DogFather nicht. Was
nach aussen dringt, ist eine Ampel ohne Namen.
@@ -50,10 +50,10 @@
UND EINE ABWEICHUNG VOM PLAN, DIE FILIPES SATZ GESCHULDET IST
Der Plan verlangt einen Anlass (die drei SBI-Felder) bei JEDEM Klick
ausser „kann ich nicht sagen". Bei 28 Punkten je Person waeren das 28
Textfelder — und „so einfach wie moeglich" war die andere Haelfte des
ausser „kann ich nicht sagen“. Bei 28 Punkten je Person waeren das 28
Textfelder — und „so einfach wie moeglich“ war die andere Haelfte des
Auftrags. Pflicht ist der Anlass deshalb dort, wo er etwas
entscheidet: bei **„Da hakt es"**. Bei „Laeuft" und „Waechst gerade"
entscheidet: bei **„Da hakt es“**. Bei „Laeuft“ und „Waechst gerade“
steht dasselbe Feld da, freiwillig — wer etwas Konkretes gesehen hat,
schreibt es hin, und beim Gespraech steht es da.
===================================================================== */
@@ -171,6 +171,55 @@ export function tabellen() {
CREATE INDEX IF NOT EXISTS idx_entwicklung_person
ON entwicklung_stand (person_id, am);
/* =================================================================
DIE SPUR (19.09.2026) — jede Aenderung, angehaengt statt ueberschrieben.
Die Tabelle darueber hat den Schluessel (person, punkt, beurteiler)
und wird bei jeder Aenderung UEBERSCHRIEBEN. Das ist dort richtig:
Eine Beobachtung ueber einen Menschen soll aktuell sein, nicht
historisch.
Nur: Damit existierte nichts, woraus man einen Verlauf zeichnen
koennte. Am 19.09. nachgemessen: 43 Zeilen Stand, NULL Zeilen
Geschichte. Ein Diagramm ueber einen einzigen Zeitpunkt ist kein
Diagramm, sondern ein Balken.
SIE STEHT HIER UND NICHT IN workspace-werdegang.js, obwohl sie
dort ausgewertet wird: Eine Tabelle gehoert zu dem Modul, das sie
SCHREIBT. Andersherum gaebe es einen Ring zwischen zwei Modulen,
und Ringe halten in JavaScript genau so lange, bis jemand eine
Konstante auf oberster Ebene benutzt.
KEIN ANLASS, NIE. Neben jedem "da hakt es" steht in der Tabelle
oben ein Satz, woran man es gemerkt hat. Der ist fuer ein
Gespraech aufgeschrieben. Eine CHRONIK dieser Saetze waere das
Belastendste, was dieses Haus speichern kann -- jede laengst
geklaerte Beobachtung bliebe auf Dauer lesbar. Dasselbe Prinzip
wie beim Befinden-Verlauf.
VORHER steht dabei, obwohl es aus der vorigen Zeile abzuleiten
waere: Fuer die Startaufnahme (siehe unten) gibt es kein
"vorher", und NULL sagt das, waehrend eine abgeleitete Kette es
verschweigen wuerde.
NACHGETRAGEN unterscheidet beobachtete Uebergaenge von der
Startaufnahme. Ohne dieses Feld saehe der Tag, an dem die Spur
entstand, wie ein Tag aus, an dem 43 Dinge gleichzeitig passiert
sind.
============================================================= */
CREATE TABLE IF NOT EXISTS entwicklung_lauf (
id INTEGER PRIMARY KEY AUTOINCREMENT,
person_id INTEGER NOT NULL REFERENCES personen(id) ON DELETE CASCADE,
schluessel TEXT NOT NULL,
von_id INTEGER NOT NULL REFERENCES personen(id) ON DELETE CASCADE,
stufe TEXT NOT NULL CHECK (stufe IN ('laeuft','waechst','hakt','unklar')),
vorher TEXT,
nachgetragen INTEGER NOT NULL DEFAULT 0,
am TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_lauf_person
ON entwicklung_lauf (person_id, am);
/* Ein angeklicktes Merkmal an einem Kandidaten.
KEIN von_id IM SCHLUESSEL, anders als oben -- und das ist
@@ -293,7 +342,7 @@ export function tabellen() {
liegen -- und die Standzeit an der Karte sagt zwar, DASS etwas
liegt, aber nicht, wer es aufheben sollte.
`zustaendig_seit` kommt mit: „seit gestern uebernommen" und „seit
`zustaendig_seit` kommt mit: „seit gestern uebernommen“ und „seit
drei Wochen uebernommen" sind zwei verschiedene Auskuenfte, und
die zweite ist die, die man sehen will. */
["zustaendig_id", "INTEGER"],
@@ -307,9 +356,83 @@ export function tabellen() {
}
} catch (f) { console.error(`[talente] Spalte '${spalte}':`, f?.message); }
}
startaufnahme();
bereit = true;
}
/** Die Spur einmalig aus dem heutigen Stand fuellen (19.09.2026).
*
* WARUM UEBERHAUPT: Ohne sie waere die Auswertungsseite am ersten Tag
* leer, und zwar fuer Monate -- bis jemand genug Einschaetzungen
* geaendert hat. Eine Seite, die nach dem Bauen leer bleibt, sieht aus
* wie ein Fehler und wird kein zweites Mal geoeffnet.
*
* WARUM DAS EHRLICH IST: Jede Zeile in `entwicklung_stand` traegt ihr
* echtes `am` -- den Tag, an dem jemand wirklich geklickt hat. Es wird
* nichts erfunden, nur uebernommen, was ohnehin dasteht. Was sich
* NICHT rekonstruieren laesst -- ob vorher schon etwas anderes
* dastand --, bleibt NULL und traegt `nachgetragen = 1`. Die
* Auswertung zaehlt diese Zeilen ausdruecklich NICHT als Bewegung:
* Der Tag der Startaufnahme ist der Nullpunkt, nicht ein Tag, an dem
* etwas passiert ist.
*
* GENAU EINMAL: nur wenn die Spur leer ist. Ein zweiter Lauf traege
* jeden Stand ein zweites Mal ein, und ab da zaehlte jede Bewegung
* doppelt.
*
* UND SIE DARF NIE DEN SERVER AUFHALTEN. Schlaegt sie fehl, ist die
* Auswertung duenn -- das ist aergerlich. Wuerde dadurch `tabellen()`
* scheitern, waere die ganze Entwicklungsseite mit 503 tot, und das
* waere ungleich schlimmer. */
function startaufnahme() {
try {
const da = db().prepare("SELECT COUNT(*) AS n FROM entwicklung_lauf").get();
if (da.n > 0) return;
const staende = db().prepare(
`SELECT person_id, schluessel, von_id, stufe, am
FROM entwicklung_stand ORDER BY am`).all();
if (!staende.length) return;
const rein = db().prepare(
`INSERT INTO entwicklung_lauf
(person_id, schluessel, von_id, stufe, vorher, nachgetragen, am)
VALUES (?,?,?,?,NULL,1,?)`);
let n = 0;
for (const s of staende) {
/* DIE SELBSTAUSKUNFT FAELLT HERAUS. Block 5 ("Wie geht es dir
damit?") hat mit dem Befinden-Verlauf eine eigene Tabelle, die
niemand sonst liest. Hier waere sie eine Aussage, die ein
Mensch ueber sich selbst gemacht hat -- und die ploetzlich in
einer Auswertung fuer die Leitung stuende. */
if (!ENTWICKLUNG_FREMD.some((x) => x.schluessel === s.schluessel)) continue;
rein.run(s.person_id, s.schluessel, s.von_id, s.stufe, s.am);
n++;
}
if (n) console.log(`[entwicklung] Startaufnahme: ${n} Staende in die Spur uebernommen.`);
} catch (f) {
console.error("[entwicklung] Startaufnahme:", f?.message);
}
}
/** Eine Aenderung in der Spur festhalten.
*
* SIE DARF NIE DEN KLICK VERHINDERN. Geht hier etwas schief, ist die
* Auswertung lueckenhaft. Wuerde dadurch die Einschaetzung nicht
* gespeichert, waere die Arbeit weg. Deshalb faengt sie ihren Fehler
* selbst und meldet ihn nur ins Protokoll der Konsole. */
function spurLegen({ personId, schluessel, vonId, stufe, vorher }) {
try {
db().prepare(
`INSERT INTO entwicklung_lauf
(person_id, schluessel, von_id, stufe, vorher, nachgetragen, am)
VALUES (?,?,?,?,?,0,?)`)
.run(personId, schluessel, vonId, stufe, vorher ?? null, jetzt());
} catch (f) {
console.error("[entwicklung] Spur:", f?.message);
}
}
/* =====================================================================
DIE HUERDEN
===================================================================== */
@@ -498,7 +621,7 @@ entwicklungRouter.get("/workspace/api/entwicklung/person/:id", nurLeitung, (req,
aufgefallen ist.
Das ist keine Nebensaechlichkeit. Beim ersten Beurteilen ist
genau das die Frage: „Woher kommt der eigentlich?" -- und wer sie
genau das die Frage: „Woher kommt der eigentlich?“ -- und wer sie
nicht beantwortet bekommt, faengt bei null an, obwohl vier Wochen
Beobachtung vorliegen.
@@ -556,7 +679,7 @@ entwicklungRouter.get("/workspace/api/entwicklung/person/:id", nurLeitung, (req,
Die Quellen zur laufenden Entwicklungsbegleitung sagen übereinstimmend
zweierlei: weg von der Bewertung, hin zum Gespräch -- und ein
Entwicklungsplan wirkt dann, wenn er an einer ECHTEN Aufgabe hängt.
Nicht „daran arbeiten wir", sondern etwas, das auf dem Aufgabenbrett
Nicht „daran arbeiten wir“, sondern etwas, das auf dem Aufgabenbrett
steht, einen Verantwortlichen hat und irgendwann erledigt ist.
Genau dieses Muster gibt es im Haus schon: Aus einer Idee auf einem
@@ -610,8 +733,8 @@ entwicklungRouter.post("/workspace/api/entwicklung/person/:id/:schluessel/schrit
const punkt = ENTWICKLUNG_FREMD.find((x) => x.schluessel === schluessel);
if (!punkt) return res.status(404).json({ fehler: "nicht_gefunden" });
/* NUR AUS EINEM „DA HAKT ES" WIRD EIN SCHRITT -- und nur aus dem
EIGENEN. Aus „läuft" einen Schritt zu machen hieße, jemandem
/* NUR AUS EINEM „DA HAKT ES“ WIRD EIN SCHRITT -- und nur aus dem
EIGENEN. Aus „läuft“ einen Schritt zu machen hieße, jemandem
Arbeit zu geben für etwas, das gut läuft; aus der Beobachtung
eines anderen hieße, in seinem Namen zu handeln. */
const stand = db().prepare(
@@ -682,7 +805,7 @@ entwicklungRouter.put("/workspace/api/entwicklung/person/:id/:schluessel",
if (!STUFEN_SATZ.has(stufe)) return res.status(400).json({ fehler: "unbekannte_stufe" });
/* DER ANLASS IST PFLICHT, WO ER ETWAS ENTSCHEIDET.
„Da hakt es" ohne Beispiel ist eine Meinung; mit Beispiel ist
„Da hakt es“ ohne Beispiel ist eine Meinung; mit Beispiel ist
es ein Gespraech. Bei den anderen beiden steht dasselbe Feld
freiwillig da. */
if (stufe === "hakt" && anlass.length < 10) {
@@ -692,6 +815,15 @@ entwicklungRouter.put("/workspace/api/entwicklung/person/:id/:schluessel",
});
}
/* WAS VORHER DASTAND -- VOR dem Schreiben gelesen.
Danach ist es weg: Die Tabelle ueberschreibt. Genau deshalb
gibt es die Spur, und genau deshalb steht diese Zeile hier
oben und nicht unten. */
const vorher = db().prepare(
`SELECT stufe FROM entwicklung_stand
WHERE person_id = ? AND schluessel = ? AND von_id = ?`)
.get(id, schluessel, req.person.id)?.stufe || null;
db().prepare(`INSERT INTO entwicklung_stand
(person_id, schluessel, von_id, stufe, anlass, am)
VALUES (?,?,?,?,?,?)
@@ -699,6 +831,13 @@ entwicklungRouter.put("/workspace/api/entwicklung/person/:id/:schluessel",
stufe = excluded.stufe, anlass = excluded.anlass, am = excluded.am`)
.run(id, schluessel, req.person.id, stufe, anlass || null, jetzt());
/* ERST SPEICHERN, DANN MITSCHREIBEN. Die Reihenfolge ist die
Aussage: Wenn die Spur scheitert, fehlt eine Zeile in einer
Auswertung. Wenn das Speichern scheitert, ist die Arbeit weg. */
spurLegen({
personId: id, schluessel, vonId: req.person.id, stufe, vorher,
});
res.json({ ok: true });
} catch (f) {
console.error("[entwicklung] Setzen:", f?.message);
@@ -851,7 +990,7 @@ entwicklungRouter.get("/workspace/api/entwicklung/meine-karte", (req, res) => {
/* WAS HIER HERAUSKOMMT UND WAS NICHT.
Heraus kommt: wie viele im Team gerade mindestens einen Punkt aus
Block 5 auf „da hakt es" stehen haben -- und ab zwei Personen auch
Block 5 auf „da hakt es“ stehen haben -- und ab zwei Personen auch
das nicht mehr genau, sondern als Spanne.
Nicht heraus kommt: wer, welcher Punkt, seit wann.
@@ -861,7 +1000,7 @@ entwicklungRouter.get("/workspace/api/entwicklung/meine-karte", (req, res) => {
daraus niemanden erkennen kann.
AB WELCHER ZAHL SIE UEBERHAUPT ETWAS SAGT: Bei einem Team von zwei
Personen waere „einer hat gerade viel" dasselbe wie ein Name.
Personen waere „einer hat gerade viel“ dasselbe wie ein Name.
Deshalb schweigt sie unter MINDESTENS_TEAM Personen -- mit Grund,
nicht stillschweigend. */
const MINDESTENS_TEAM = 3;
@@ -908,7 +1047,7 @@ entwicklungRouter.get("/workspace/api/entwicklung/ampel", nurLeitung, (req, res)
res.json({
sagbar: true, team, geantwortet,
/* Keine genaue Zahl: „einer" waere bei kleinen Teams wieder ein
/* Keine genaue Zahl: „einer“ waere bei kleinen Teams wieder ein
Name. Es gibt drei Antworten, und mehr braucht es nicht. */
lage: betroffen === 0 ? "ruhig" : betroffen === 1 ? "jemand" : "mehrere",
text: betroffen === 0
@@ -1180,7 +1319,7 @@ entwicklungRouter.get("/workspace/api/talente/lage", (req, res) => {
heraussuchen, Kategorie und Stufe waehlen und uebernehmen.
Vier Schritte, die niemand macht, waehrend er gerade jemanden
aufnimmt. Genau dieses Muster hat heute schon einmal zu einer
Karte auf „Im Team" ohne Menschen darin gefuehrt.
Karte auf „Im Team“ ohne Menschen darin gefuehrt.
ES BLEIBT EIN ANGEBOT, KEINE AUTOMATIK. Filipes Entscheidung
bei der Live-Checkliste gilt hier genauso: „vierzehn Aufgaben,
@@ -1189,8 +1328,8 @@ entwicklungRouter.get("/workspace/api/talente/lage", (req, res) => {
hier nur, WAS es gaebe -- angelegt wird auf Klick, eine
Kategorie auf einmal.
KEIN ROLLENNAME, nirgends. Die Kategorien heissen „chat",
„team", „technik" -- aus keiner davon laesst sich schliessen,
KEIN ROLLENNAME, nirgends. Die Kategorien heissen „chat“,
„team“, „technik“ -- aus keiner davon laesst sich schliessen,
fuer wen sie gedacht sind. Wer den Katalog nicht bekommen
darf, bekommt `null` und die Seite baut nichts; ein leeres
Feld sagt nichts, ein Feld mit sprechendem Namen saehe jeder
@@ -1205,7 +1344,7 @@ entwicklungRouter.get("/workspace/api/talente/lage", (req, res) => {
/* WAS SIE SCHON HABEN -- je Person, in EINER Abfrage.
Ohne diese Angabe legt man beim zweiten Hinsehen dasselbe noch
einmal an; der Knopf sagt dann „6 Aufgaben" und meint die
einmal an; der Knopf sagt dann „6 Aufgaben“ und meint die
siebte bis zwoelfte. Der Server verhindert Doppelte nicht --
er soll es auch nicht, denn dieselbe Aufgabe ein zweites Mal
kann gewollt sein. Sichtbar muss es trotzdem sein. */
@@ -1444,7 +1583,7 @@ entwicklungRouter.put("/workspace/api/talente/:id/kuemmern", gleicheHerkunft,
} else {
/* Noch keine Stufenzeile -- die entsteht erst beim ersten
Schritt. Wer sich vorher kuemmern will, soll das koennen:
„Ich sehe ihn mir an" ist der haeufigste erste Schritt
„Ich sehe ihn mir an“ ist der haeufigste erste Schritt
ueberhaupt, und er faellt sonst durch. */
db().prepare(`INSERT INTO talent_stufe
(eintrag_id, stufe, seit, von_id, zustaendig_id, zustaendig_seit)
@@ -1506,7 +1645,7 @@ entwicklungRouter.put("/workspace/api/talente/:id/wieder", gleicheHerkunft,
/** Buddy und Datum prüfen. Gibt `{ fehlt: [...] }` oder `{ buddyId, schicht }`.
*
* EINE STELLE, ZWEI AUFRUFER: der Stufenwechsel auf „Probe" und das
* EINE STELLE, ZWEI AUFRUFER: der Stufenwechsel auf „Probe“ und das
* spaetere Aendern. Zwei Abschriften derselben Regeln waeren zwei
* Regeln, und die zweite ist immer die, die etwas durchlaesst. */
function uebergangPruefen(koerper, { pflicht }) {
@@ -1576,13 +1715,13 @@ entwicklungRouter.put("/workspace/api/talente/:id/stufe", gleicheHerkunft, (req,
/* AUF DIE PROBE KOMMT NIEMAND OHNE BUDDY UND DATUM.
In der Stufenbeschreibung stand „fester Buddy" seit dem ersten
In der Stufenbeschreibung stand „fester Buddy“ seit dem ersten
Tag -- ein Feld dafuer gab es nicht. Ein Versprechen ohne Feld
ist eines, an das sich nach zwei Wochen niemand erinnert, und
genau an dieser Stelle verlieren Teams die Leute, die sie gerade
gewonnen haben.
EINE ABSAGE MIT GRUND, nicht ein „ging nicht": Die Oberflaeche
EINE ABSAGE MIT GRUND, nicht ein „ging nicht“: Die Oberflaeche
fragt beides vorher ab, aber die Regel gehoert hierher. Was nur
im Browser steht, gilt nur fuer den, der den Browser benutzt. */
const p = stufe === "probe"
+753
View File
@@ -0,0 +1,753 @@
/* =====================================================================
ENTWICKLUNG — die Auswertung je Person (19.09.2026)
Filipe, mit dem Bildschirmfoto der alten Kachel:
„diese kategorie ist ja die aufgaben die ich an die modis habe,
also eher aufgabe und dan perfektionierst du eine neue wo
diagramme texte und so zu jedem modi gemacht wird und diese
kategorie kannst du entwicklung nenen.“
Die Umbenennung war der erste Teil und ist seit dem 19.09. früh
erledigt: Die alte Kachel heißt „Eure Aufgaben“, weil sie das ist —
ein Katalog dessen, was ein Modi können soll. Hier kommt der zweite
Teil: die Auswertung, die es noch nie gab.
---------------------------------------------------------------------
WARUM DIESE DATEI NICHT „entwicklung.js“ HEISST
`workspace-entwicklung.js` gibt es seit dem 11.09. und es ist der
KATALOG. Diese Datei ist die AUSWERTUNG darüber. Zwei Dateien mit
fast demselben Namen wären die zuverlässigste Art, in einem halben
Jahr die falsche zu ändern.
Dasselbe gilt für die Seite: `entwicklung.html` ist der Katalog
(„Eure Aufgaben“), `werdegang.html` ist diese Auswertung
(„Entwicklung“). Dass die Kachel anders heißt als die Datei, ist
Absicht und geprüft — `pruef-werdegang` verlangt, dass die Kachel
„Entwicklung“ auf `werdegang.html` zeigt und nicht woanders hin.
Ein Umbenennen der alten Datei wäre die sauberere Endform, aber es
wären zwei Änderungen in einer: Wenn danach etwas klemmt, weiß
niemand, welche von beiden es war.
---------------------------------------------------------------------
DAS EIGENTLICHE PROBLEM: ES GAB KEINE VERGANGENHEIT
`entwicklung_stand` hat den Schlüssel (person, punkt, beurteiler)
und wird bei jeder Änderung ÜBERSCHRIEBEN. Das ist dort richtig —
eine Beobachtung über einen Menschen soll aktuell sein, nicht
historisch. Nur: Damit existiert nichts, woraus man einen Verlauf
zeichnen könnte. Am 19.09.2026 nachgemessen standen 43 Zeilen in der
Tabelle und NULL Zeilen Geschichte.
Ein Diagramm über einen einzigen Zeitpunkt ist kein Diagramm,
sondern ein Balken. Deshalb legt diese Datei als Erstes an, was
gefehlt hat: eine Spur, die mitschreibt, statt zu überschreiben.
---------------------------------------------------------------------
WAS HIER AUSDRÜCKLICH NICHT ENTSTEHT
Keine Punktzahl. Kein Prozentwert. Kein Durchschnitt. Keine
Rangliste. Kein Vergleich zwischen zwei Menschen — nirgends, auch
nicht versteckt als „Team-Mittelwert“.
Das ist keine Vorsicht, das ist die Recherche:
* ÜBERRECHTFERTIGUNG (Deci 1971, Selbstbestimmungstheorie). Wer
für etwas, das er gern tut, eine äußere Bewertung bekommt,
verliert die innere Motivation. Die Ausnahme — und die ist der
ganze Trick — ist Rückmeldung, die KOMPETENZ BESTÄTIGT, ohne eine
Zahl daneben.
* SOZIALER VERGLEICH. Beurteilungen, die Menschen nebeneinander
stellen, erzeugen Neid und senken die Leistung derer, die
schlechter dastehen. Ein Balken „Diene 71 %, VanVan 88 %“ wäre
genau das, und er wäre nie wieder aus den Köpfen zu bekommen.
* SCHWÄCHENFOKUS. Rückmeldung, die auf Defizite zeigt, senkt die
Leistung messbar. Deshalb steht hier IMMER zuerst, was sich
bewegt hat und was trägt — und erst danach, was liegt.
Quellen:
https://hbr.org/2011/05/the-power-of-small-wins
https://www.simplypsychology.org/self-determination-theory.html
https://www.profit.co/blog/performance-management/adverse-effects-of-employee-comparison-during-performance-appraisals/
https://www.cambridge.org/core/journals/industrial-and-organizational-psychology/article/abs/working-with-social-comparisons-in-the-appraisal-and-management-of-performance/2366D9737F8A543A16E6C884D1E8A5F8
---------------------------------------------------------------------
WAS STATTDESSEN GEZEIGT WIRD: BEWEGUNG
Amabile und Kramer haben rund 12 000 Tagebucheinträge von 238
Menschen ausgewertet. Das Ergebnis, das seitdem „progress principle“
heißt: Der stärkste einzelne Treiber guter Arbeit ist SICHTBARER
FORTSCHRITT in einer Sache, die einem wichtig ist. Nicht Lob, nicht
Belohnung, nicht Druck — und die Schritte dürfen klein sein.
Deshalb ist die erste Zahl auf dieser Seite nicht, WIE GUT jemand
ist, sondern WAS SICH BEWEGT HAT. Und die zweite sagt etwas über
UNS: wie alt die Einschätzungen sind. Eine Karte, die seit vier
Monaten niemand angefasst hat, ist keine Auskunft über den
Menschen — sie ist eine über die, die hinsehen sollten.
---------------------------------------------------------------------
DER ANLASS KOMMT NICHT MIT
In `entwicklung_stand` steht neben jedem „da hakt es“ ein Satz, woran
man es gemerkt hat. Der ist für ein Gespräch aufgeschrieben. Eine
CHRONIK dieser Sätze wäre das Belastendste, was dieses Haus
speichern könnte — jede alte Beobachtung bliebe auf Dauer lesbar,
auch die, die längst geklärt ist.
Die Spur hier merkt sich deshalb nur: welcher Punkt, welche Stufe,
wann, von wem. Kein Anlass, nie. Dasselbe Prinzip wie beim
Befinden-Verlauf — dort steht im Protokoll auch nur, DASS eine Runde
war.
===================================================================== */
import express from "express";
import {
db, sitzungLesen, fuehrtTeamDogi, ROLLEN_NAME,
} from "./workspace.js";
import {
ENTWICKLUNG_BLOECKE, ENTWICKLUNG_PUNKTE, ENTWICKLUNG_STUFEN,
ENTWICKLUNG_ERWARTUNG, ENTWICKLUNG_FREMD, ENTWICKLUNG_NACH_SCHLUESSEL,
} from "./workspace-entwicklungs-punkte.js";
/* NUR IN EINE RICHTUNG. Diese Datei liest aus `workspace-entwicklung.js`;
jene weiß von dieser nichts. Die Spur-Tabelle steht deshalb DORT, bei
`entwicklung_stand`, zu dem sie gehört — nicht hier.
Der bequeme Weg wäre gewesen, die Tabelle hier anzulegen und von dort
ein `spurLegen()` zu importieren. Das ergäbe einen Ring zwischen zwei
Modulen, und Ringe funktionieren in JavaScript so lange, bis jemand
eine Konstante auf oberster Ebene benutzt statt in einer Funktion.
Dann kommt ein Fehler, den niemand mit dem Import in Verbindung
bringt. */
import {
VERBLASST_NACH_TAGEN, tabellen as entwicklungTabellen,
} from "./workspace-entwicklung.js";
export const werdegangRouter = express.Router();
const TAG = 86400000;
/** Die Rollen, über die eine Karte geführt wird.
*
* ABGELEITET WÄRE HIER FALSCH, und das ist die Ausnahme: Es ist
* dieselbe Liste wie in `workspace-entwicklung.js`, und sie MUSS
* dieselbe sein — eine Auswertung über jemanden, der gar keine Karte
* hat, wäre leer. Sie steht trotzdem hier noch einmal, weil ein
* Import nur wegen zweier Zeichenketten die beiden Module
* aneinanderbindet; `pruef-werdegang` vergleicht sie und wird rot,
* wenn sie auseinanderlaufen. */
export const KARTEN_ROLLEN = ["hand", "modi"];
/** Wie viele Monatsspalten der Verlauf zeigt.
*
* SECHS. Genug, um ein halbes Jahr zu sehen — und wenig genug, dass
* die Spalten auf einem Handy noch lesbar sind. Mehr Spalten heißt
* nicht mehr Auskunft: Was vor einem Jahr war, ist für die Frage
* „bewegt sich etwas?“ ohne Belang. */
export const MONATE = 6;
/** Ab wann ein „läuft“ als getragen gilt und ausdrücklich gesagt wird.
*
* 60 TAGE. Anerkennung wirkt nur, wenn sie sich auf etwas Bestimmtes
* bezieht — „du machst das gut“ ist Höflichkeit, „das steht seit zwei
* Monaten und ist nie gekippt“ ist eine Beobachtung. Unter zwei
* Monaten wäre es ein Zwischenstand, kein Befund. */
export const TRAEGT_AB_TAGEN = 60;
/** Ab wann ein „da hakt es“ von selbst zur Sprache kommt.
*
* 30 TAGE OHNE BEWEGUNG. Die Hausregel dazu steht im Plan vom 11.09.:
* „Ein ‚Da hakt es', das nirgends hinführt, ist eine Notiz über einen
* Menschen — und die will hier niemand sammeln.“ */
export const LIEGT_AB_TAGEN = 30;
/** Ab wie vielen Monatsspalten mit Inhalt überhaupt etwas gezeichnet wird.
*
* ZWEI. Ein Punkt ist kein Verlauf. Darunter sagt die Seite das —
* sie zeigt keine Linie zwischen einem einzigen Wert und dem Rand.
* Das ist der dritte Ausgang: nicht in Ordnung / in Ordnung / KONNTE
* NOCH NICHT NACHSEHEN, mit Grund. */
export const VERLAUF_AB = 2;
/* ---------------------------------------------------------------------
DIE SPUR LIEGT NEBENAN
`entwicklung_lauf` wird in `workspace-entwicklung.js` angelegt und
dort auch geschrieben — bei `entwicklung_stand`, zu dem sie gehört.
Diese Datei liest sie nur. Ein `tabellen()` hier wäre eine zweite
Stelle, an der dieselbe Tabelle entstehen kann, und die zweite ist
immer die, die eine Spalte vergisst.
------------------------------------------------------------------- */
function tabellen() { entwicklungTabellen(); }
/* ---------------------------------------------------------------------
RECHNEN
------------------------------------------------------------------- */
/** Die Rangfolge der drei aussagekräftigen Stufen.
*
* „Kann ich nicht sagen“ hat KEINEN Rang und bekommt auch keinen. Es
* ist keine schlechtere Antwort als „läuft“, sondern eine andere Art
* von Antwort — sie sagt etwas über den Beurteiler, nicht über den
* Beurteilten. Ihr eine Zahl zu geben hieße, sie in dieselbe Reihe zu
* stellen, und ab da würde „ich habe es nicht gesehen“ wie ein
* halbes „da hakt es“ gelesen. */
const RANG = { hakt: 0, waechst: 1, laeuft: 2 };
/** Wie ein Übergang zu nennen ist. Gerechnet, nicht gespeichert. */
export function artVon(vorher, stufe, nachgetragen) {
if (nachgetragen) return "anfang";
if (!vorher) return "neu";
if (vorher === stufe) return "gleich";
if (vorher === "unklar") return "geklaert";
if (stufe === "unklar") return "offen";
const a = RANG[vorher], b = RANG[stufe];
if (a === undefined || b === undefined) return "gleich";
return b > a ? "vor" : "zurueck";
}
/** Der Monat eines Zeitpunkts in Ortszeit — „2026-09“.
*
* ÜBER Intl UND NICHT ÜBER slice(0,7). `toISOString()` rechnet in UTC;
* zwischen Mitternacht und zwei Uhr liefert das den VORMONAT, und zwar
* genau am Monatsersten. Ein Diagramm, das in den ersten Stunden des
* Monats eine Spalte zu wenig hat, fällt niemandem auf — es sieht
* einfach nach wenig Bewegung aus. Dieselbe Falle hat am 06.09. eine
* Prüfung zwei Stunden pro Tag rot gemacht. */
const MONATSFORM = new Intl.DateTimeFormat("sv-SE", {
timeZone: "Europe/Berlin", year: "numeric", month: "2-digit",
});
export function monatVon(iso) {
const t = Date.parse(iso || "");
if (!Number.isFinite(t)) return null;
return MONATSFORM.format(new Date(t)).slice(0, 7);
}
/** Die letzten N Monate, älteste zuerst — „2026-04“ … „2026-09“. */
export function monatsspalten(anzahl = MONATE, bis = new Date()) {
const spalten = [];
/* ÜBER DEN 15. GERECHNET und nicht über den heutigen Tag: Wer am
31. März einen Monat abzieht, landet in JavaScript im März (der
31. Februar existiert nicht und rollt über). Der 15. ist in jedem
Monat vorhanden. */
const roh = monatVon(bis.toISOString()) || monatVon(new Date().toISOString());
const [j, m] = roh.split("-").map(Number);
for (let i = anzahl - 1; i >= 0; i--) {
const d = new Date(Date.UTC(j, m - 1 - i, 15, 12));
spalten.push(`${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}`);
}
return spalten;
}
const tageSeit = (iso) => {
const t = Date.parse(iso || "");
return Number.isFinite(t) ? Math.floor((Date.now() - t) / TAG) : null;
};
/** Alle Spur-Zeilen einer Person, älteste zuerst. */
function spurVon(personId) {
return db().prepare(
`SELECT l.schluessel, l.von_id, l.stufe, l.vorher, l.nachgetragen, l.am,
p.name AS von_name
FROM entwicklung_lauf l
LEFT JOIN personen p ON p.id = l.von_id
WHERE l.person_id = ?
ORDER BY l.am ASC, l.id ASC`).all(personId);
}
/* =====================================================================
DIAGRAMM 1 — WAS SICH BEWEGT HAT
Je Monat: wie viele Punkte sind nach vorn gegangen, wie viele zurück,
wie viele wurden zum ersten Mal angesehen.
ES IST BEWUSST EIN DIAGRAMM MIT ZWEI RICHTUNGEN und nicht zwei
nebeneinander: Vorwärts und rückwärts sind dieselbe Größe mit
verschiedenem Vorzeichen. Zwei getrennte Diagramme würden dazu
verleiten, sie zu vergleichen, als wären es zwei Themen.
RÜCKWÄRTS IST NICHT ROT. Auf dieser Seite gibt es kein Rot — das ist
seit dem 11.09. die wichtigste Farbentscheidung des Bereichs. Ein
Rückschritt ist eine Beobachtung, kein Alarm, und oft genug heißt er
nur, dass jemand genauer hingesehen hat als beim letzten Mal.
===================================================================== */
export function bewegungVon(spur, spalten) {
const leer = () => ({ vor: 0, zurueck: 0, neu: 0, geklaert: 0, offen: 0 });
const nach = new Map(spalten.map((s) => [s, leer()]));
for (const z of spur) {
const m = monatVon(z.am);
if (!nach.has(m)) continue;
const art = artVon(z.vorher, z.stufe, z.nachgetragen);
/* „anfang“ und „gleich“ zählen nicht als Bewegung. Die
Startaufnahme ist keine Veränderung, sondern der Nullpunkt — und
wer denselben Wert noch einmal setzt, hat bestätigt, nicht
bewegt. Beides mitzuzählen wäre der bequemste Weg zu einem
Diagramm, das immer etwas zeigt. */
if (art === "anfang" || art === "gleich") continue;
nach.get(m)[art] += 1;
}
return spalten.map((m) => ({ monat: m, ...nach.get(m) }));
}
/* =====================================================================
DIAGRAMM 2 — DER STREIFEN
Eine Zeile je Punkt, eine Spalte je Monat, jede Zelle die Stufe, die
AM ENDE DIESES MONATS galt.
FORTGESCHRIEBEN, NICHT LÜCKENHAFT: Wenn im Juli niemand etwas
geändert hat, galt im Juli weiter, was im Juni gesetzt wurde. Eine
leere Zelle würde „keine Einschätzung“ bedeuten, und das wäre falsch.
ABER SICHTBAR ALT: Eine fortgeschriebene Zelle, deren Ursprung länger
als 90 Tage zurückliegt, wird gedämpft gezeichnet. Sonst sähe eine
vier Monate alte Einschätzung genauso frisch aus wie die von gestern
— und das ist die Lüge mit Zeitstempel, vor der der Plan vom 11.09.
ausdrücklich warnt.
DIE FARBE IST NICHT DIE AUSKUNFT. Jede Zelle trägt das Zeichen ihrer
Stufe (✓ ↗ ! –) als Text. Wer Farben schlecht unterscheidet, liest
dasselbe. Das ist im Haus seit dem Befinden-Verlauf so und bleibt so.
===================================================================== */
export function streifenVon(spur, spalten, beurteilerId) {
/* NUR DER BLICK EINES BEURTEILERS je Streifen. Zwei Meinungen in
einer Zelle zu mischen hieße, einen Mittelwert zu bilden — und ein
Mittelwert aus „läuft“ und „da hakt es“ ist „wächst gerade“, was
niemand gesagt hat. Die Abweichung zwischen den beiden ist das
Ergebnis (so steht es im Plan) und hat ihren eigenen Platz. */
const meine = spur.filter((z) => z.von_id === beurteilerId);
if (!meine.length) return [];
/* Nur Punkte, zu denen es überhaupt etwas gibt — der ganze Katalog
wäre eine Wand aus Strichen. */
const punkte = [...new Set(meine.map((z) => z.schluessel))];
const grenze = Date.now() - VERBLASST_NACH_TAGEN * TAG;
return punkte.map((schluessel) => {
const pt = ENTWICKLUNG_NACH_SCHLUESSEL.get(schluessel);
const zeilen = meine.filter((z) => z.schluessel === schluessel);
let stufe = null, seit = null;
const zellen = spalten.map((m) => {
/* Alles, was BIS EINSCHLIESSLICH dieses Monats passiert ist. */
for (const z of zeilen) {
if (monatVon(z.am) <= m) { stufe = z.stufe; seit = z.am; }
}
if (!stufe) return { monat: m, stufe: null };
return {
monat: m, stufe, seit,
/* Nur die LETZTE Spalte kann verblassen — bei den früheren
sagt die Spalte selbst schon, wann es war. Ein gedämpfter
Märzwert im März wäre Unsinn. */
alt: m === spalten.at(-1) && Date.parse(seit) < grenze,
};
});
return {
schluessel,
titel: pt?.titel || schluessel,
block: pt?.block || null,
zellen,
};
});
}
/* =====================================================================
DIE TEXTE
Filipe wollte „diagramme texte und so“. Die Texte sind der Teil, den
ein Diagramm nicht kann: Es zeigt, DASS sich etwas bewegt hat — nicht
WAS, und schon gar nicht, was man damit macht.
DIE REIHENFOLGE IST DIE AUSSAGE, und sie ist aus der Forschung
abgeleitet, nicht aus Höflichkeit:
1. WAS SICH BEWEGT HAT — progress principle: sichtbarer
Fortschritt ist der stärkste Treiber.
2. WAS TRÄGT — Anerkennung wirkt nur konkret; deshalb
mit Punkt und Dauer, nicht als Lob.
3. WAS LIEGT — erst an dritter Stelle. Schwächenfokus
senkt die Leistung messbar.
4. WAS NIEMAND ANSIEHT — die einzige Zeile, die von UNS handelt.
5. WAS ALS NÄCHSTES KOMMT— der Weg nach vorn, nicht die Lücke.
JEDER ABSATZ NENNT DEN PUNKT, auf den er sich bezieht. Ein Satz wie
„es gibt Bewegung“ ist keine Auskunft, sondern eine Behauptung.
===================================================================== */
const zaehl = (n, eins, viele) => (n === 1 ? eins : viele.replace("%n", n));
export function texteVon({ spur, beurteilerId, stand, person }) {
const texte = [];
const meine = spur.filter((z) => z.von_id === beurteilerId);
const titel = (s) => ENTWICKLUNG_NACH_SCHLUESSEL.get(s)?.titel || s;
/* ---- 1. Was sich bewegt hat ------------------------------------- */
const bewegt = meine.filter((z) => {
const art = artVon(z.vorher, z.stufe, z.nachgetragen);
return art === "vor" || art === "zurueck" || art === "geklaert";
});
const vor = bewegt.filter((z) => artVon(z.vorher, z.stufe, z.nachgetragen) === "vor");
const zurueck = bewegt.filter((z) => artVon(z.vorher, z.stufe, z.nachgetragen) === "zurueck");
if (vor.length) {
texte.push({
art: "vor",
titel: "Was sich bewegt hat",
satz: zaehl(vor.length,
`Ein Punkt ist weitergekommen: „${titel(vor[0].schluessel)}“.`,
`%n Punkte sind weitergekommen.`)
+ " Das ist die Sorte Veränderung, die man aussprechen sollte –"
+ " sichtbarer Fortschritt hält Menschen bei der Sache, stärker"
+ " als Lob es je könnte.",
punkte: vor.slice(-5).reverse().map((z) => ({
titel: titel(z.schluessel), von: z.vorher, zu: z.stufe, am: z.am,
})),
});
}
if (zurueck.length) {
texte.push({
art: "zurueck",
titel: "Wo du es anders siehst als vorher",
satz: zaehl(zurueck.length,
"Bei einem Punkt hast du zurückgesetzt.",
"Bei %n Punkten hast du zurückgesetzt.")
+ " Das ist kein Rückschritt dieser Person – es heißt zuerst,"
+ " dass du genauer hingesehen hast als beim letzten Mal."
+ " Beides gehört ins Gespräch, und zwar in dieser Reihenfolge.",
punkte: zurueck.slice(-5).reverse().map((z) => ({
titel: titel(z.schluessel), von: z.vorher, zu: z.stufe, am: z.am,
})),
});
}
/* ---- 2. Was trägt ------------------------------------------------ */
const traegt = stand
.filter((s) => s.stufe === "laeuft" && (tageSeit(s.am) ?? 0) >= TRAEGT_AB_TAGEN)
.sort((a, b) => Date.parse(a.am) - Date.parse(b.am));
if (traegt.length) {
const t = traegt[0];
texte.push({
art: "traegt",
titel: "Was seit Längerem trägt",
satz: zaehl(traegt.length,
`„${titel(t.schluessel)}“ steht seit ${tageSeit(t.am)} Tagen auf „Läuft“ und ist nie gekippt.`,
`%n Punkte stehen seit über ${Math.round(TRAEGT_AB_TAGEN / 30)} Monaten auf „Läuft“ und sind nie gekippt.`)
+ " Genau so etwas wird nie gesagt, weil nichts passiert –"
+ " und fehlende Anerkennung ist der am häufigsten genannte"
+ " Grund, warum Leute in dieser Arbeit aufhören.",
punkte: traegt.slice(0, 5).map((s) => ({
titel: titel(s.schluessel), zu: "laeuft", am: s.am, tage: tageSeit(s.am),
})),
});
}
/* ---- 3. Was liegt ------------------------------------------------ */
const liegt = stand
.filter((s) => s.stufe === "hakt" && (tageSeit(s.am) ?? 0) >= LIEGT_AB_TAGEN)
.sort((a, b) => Date.parse(a.am) - Date.parse(b.am));
if (liegt.length) {
texte.push({
art: "liegt",
titel: "Was seit Längerem unverändert steht",
satz: zaehl(liegt.length,
`„${titel(liegt[0].schluessel)}“ steht seit ${tageSeit(liegt[0].am)} Tagen unverändert auf „Da hakt es“.`,
`%n Punkte stehen seit über ${LIEGT_AB_TAGEN} Tagen unverändert auf „Da hakt es“.`)
+ " Ein „da hakt es“, das nirgends hinführt, ist nach einer Weile"
+ " keine Beobachtung mehr, sondern eine Notiz über einen Menschen."
+ " Auf der Karte steht neben jedem Punkt „Daraus einen Schritt machen“.",
punkte: liegt.slice(0, 5).map((s) => ({
titel: titel(s.schluessel), zu: "hakt", am: s.am, tage: tageSeit(s.am),
})),
});
}
/* ---- 4. Was niemand mehr angesehen hat --------------------------- */
const alt = stand.filter((s) => (tageSeit(s.am) ?? 0) >= VERBLASST_NACH_TAGEN);
if (alt.length) {
texte.push({
art: "alt",
titel: "Das sagt etwas über uns, nicht über sie",
satz: zaehl(alt.length,
`Eine Einschätzung ist älter als ${VERBLASST_NACH_TAGEN} Tage.`,
`%n Einschätzungen sind älter als ${VERBLASST_NACH_TAGEN} Tage.`)
+ " Sie zählen in der Auswertung nicht mehr mit. Eine Einschätzung"
+ " vom Frühjahr, heute unverändert angezeigt, wäre eine Lüge mit"
+ " Zeitstempel – deshalb verblasst sie, statt zu verschwinden.",
punkte: alt.slice(0, 5).map((s) => ({
titel: titel(s.schluessel), zu: s.stufe, am: s.am, tage: tageSeit(s.am),
})),
});
}
/* ---- 5. Was als Nächstes kommt ----------------------------------- */
const gesetzt = new Set(stand.map((s) => s.schluessel));
const offen = ENTWICKLUNG_FREMD.filter((p) => !gesetzt.has(p.schluessel));
if (offen.length) {
/* NACH ERWARTUNGSSTUFE SORTIERT, niedrigste zuerst: Was ab Tag eins
gilt, ist die sinnvollere nächste Frage als das, wofür man
jemanden fragt. */
const rang = new Map(ENTWICKLUNG_ERWARTUNG.map((e) => [e.schluessel, e.rang]));
const sortiert = [...offen].sort(
(a, b) => (rang.get(a.stufe) ?? 9) - (rang.get(b.stufe) ?? 9));
texte.push({
art: "offen",
titel: "Wozu du noch nichts gesagt hast",
satz: zaehl(offen.length,
"Ein Punkt des Katalogs ist von dir noch unbeantwortet.",
"%n Punkte des Katalogs sind von dir noch unbeantwortet.")
+ " Das ist kein Versäumnis – „Kann ich nicht sagen“ ist eine"
+ " gültige Antwort und ehrlicher als zu raten. Es steht hier,"
+ " damit man weiß, worüber die Auswertung schweigt.",
punkte: sortiert.slice(0, 5).map((p) => ({
titel: p.titel,
/* DER NAME, NICHT DER SCHLUESSEL -- und schon gar nicht der
`rang` daneben. ENTWICKLUNG_ERWARTUNG traegt eine Zahl je
Stufe ("rang"), die es nur zum Sortieren gibt. Sie mitzuliefern
hiesse, eine Zahl ueber einen Menschen auszuliefern, die
irgendwann jemand addiert. Aufgefallen ist das der eigenen
Pruefung, nicht dem Lesen. */
erwartung: ENTWICKLUNG_ERWARTUNG.find((e) => e.schluessel === p.stufe)?.name
|| p.stufe,
})),
});
}
return texte;
}
/** Der eine Satz oben — die ganze Auswertung in einer Zeile.
*
* WER NUR IHN LIEST, SOLL RICHTIG INFORMIERT SEIN. Dieselbe Regel wie
* bei der Befinden-Analyse: Der Satz ist kein Aufhänger, er ist die
* Kurzfassung. */
export function satzVon({ bewegung, stand, texte, spalten }) {
const vor = bewegung.reduce((s, m) => s + m.vor, 0);
const zurueck = bewegung.reduce((s, m) => s + m.zurueck, 0);
const neu = bewegung.reduce((s, m) => s + m.neu + m.geklaert, 0);
if (!stand.length) {
return "Zu dieser Person hast du noch nichts eingetragen. Die Auswertung "
+ "beginnt mit der ersten Einschätzung – vorher stünde hier eine Grafik "
+ "über nichts.";
}
if (vor + zurueck + neu === 0) {
const alt = texte.find((t) => t.art === "alt");
return alt
? `In den letzten ${spalten.length} Monaten hat sich an dieser Karte nichts `
+ "bewegt – und ein Teil davon ist inzwischen älter als drei Monate. "
+ "Das ist eine Auskunft über uns, nicht über sie."
: `In den letzten ${spalten.length} Monaten hat sich an dieser Karte nichts `
+ "bewegt. Das kann heißen, dass alles ruhig läuft – oder dass "
+ "länger niemand hingesehen hat. Die Karte selbst kann das nicht "
+ "unterscheiden.";
}
const teile = [];
if (vor) teile.push(zaehl(vor, "ein Punkt nach vorn", "%n Punkte nach vorn"));
if (zurueck) teile.push(zaehl(zurueck, "einer zurück", "%n zurück"));
if (neu) teile.push(zaehl(neu, "einer zum ersten Mal beantwortet",
"%n zum ersten Mal beantwortet"));
return `In den letzten ${spalten.length} Monaten: ${teile.join(", ")}. `
+ (vor >= zurueck
? "Das Erste gehört ausgesprochen – es ist der Teil, den sonst niemand bemerkt."
: "Das ist kein schlechtes Zeichen: Wer zurücksetzt, hat hingesehen.");
}
/* =====================================================================
DIE WEGE
===================================================================== */
function angemeldet(req, res, next) {
const person = sitzungLesen(req);
if (!person) return res.status(401).json({ fehler: "nicht_angemeldet" });
req.person = person;
try { tabellen(); } catch (f) {
console.error("[werdegang] Tabellen:", f?.message);
return res.status(503).json({ fehler: "nicht_verfuegbar" });
}
next();
}
/** Nur DogFather und die rechte Hand. 404 statt 403 — wer „kein
* Zugriff“ liest, fragt als Nächstes, wer denn dann. */
function nurLeitung(req, res, next) {
if (fuehrtTeamDogi(req.person)) return next();
return res.status(404).json({ fehler: "nicht_gefunden" });
}
werdegangRouter.use("/workspace/api/werdegang", angemeldet);
/* ---- Die Liste ------------------------------------------------------
Wer eine Karte hat, und was sich dort zuletzt getan hat.
AUSDRÜCKLICH OHNE SORTIERUNG NACH „AKTIVITÄT“. Eine Liste, die die
mit der meisten Bewegung nach oben stellt, ist eine Rangliste mit
einem anderen Namen. Sortiert wird nach Namen.
------------------------------------------------------------------- */
werdegangRouter.get("/workspace/api/werdegang/liste", nurLeitung, (req, res) => {
try {
const leute = db().prepare(
`SELECT id, name, rolle FROM personen
WHERE aktiv = 1 AND rolle IN (${KARTEN_ROLLEN.map(() => "?").join(",")})
ORDER BY name COLLATE NOCASE`).all(...KARTEN_ROLLEN);
const spalten = monatsspalten();
const liste = leute.map((p) => {
const spur = spurVon(p.id);
const bewegung = bewegungVon(spur.filter((z) => z.von_id === req.person.id), spalten);
const stand = db().prepare(
"SELECT schluessel, stufe, am FROM entwicklung_stand WHERE person_id = ? AND von_id = ?")
.all(p.id, req.person.id)
.filter((s) => ENTWICKLUNG_FREMD.some((x) => x.schluessel === s.schluessel));
const juengste = stand.map((s) => s.am).sort().at(-1) || null;
return {
id: p.id,
name: p.name,
rolle_name: ROLLEN_NAME[p.rolle] ?? p.rolle,
gesetzt: stand.length,
gesamt: ENTWICKLUNG_FREMD.length,
bewegt: bewegung.reduce((s, m) => s + m.vor + m.zurueck + m.neu + m.geklaert, 0),
vor: bewegung.reduce((s, m) => s + m.vor, 0),
zuletzt: juengste,
zuletzt_tage: juengste ? tageSeit(juengste) : null,
};
});
res.json({
liste,
monate: spalten.length,
/* WAS DIESE SEITE NICHT TUT, steht in der Antwort und nicht nur
im Kopf des Entwicklers: Die Oberfläche zeigt den Satz. */
hinweis: "Keine Punktzahl, kein Vergleich zwischen Personen. "
+ "Jede Karte wird nur mit sich selbst über die Zeit verglichen.",
});
} catch (f) {
console.error("[werdegang] Liste:", f?.message);
res.status(503).json({ fehler: "nicht_verfuegbar" });
}
});
/* ---- Eine Person ---------------------------------------------------- */
werdegangRouter.get("/workspace/api/werdegang/person/:id", nurLeitung, (req, res) => {
try {
const id = Number(req.params.id);
const p = Number.isInteger(id) && id > 0
&& db().prepare("SELECT id, name, rolle FROM personen WHERE id = ?").get(id);
if (!p || !KARTEN_ROLLEN.includes(p.rolle)) {
return res.status(404).json({ fehler: "nicht_gefunden" });
}
const spur = spurVon(p.id);
const spalten = monatsspalten();
const meine = spur.filter((z) => z.von_id === req.person.id);
const bewegung = bewegungVon(meine, spalten);
const streifen = streifenVon(spur, spalten, req.person.id);
const stand = db().prepare(
"SELECT schluessel, stufe, am FROM entwicklung_stand WHERE person_id = ? AND von_id = ?")
.all(p.id, req.person.id)
.filter((s) => ENTWICKLUNG_FREMD.some((x) => x.schluessel === s.schluessel));
const texte = texteVon({ spur, beurteilerId: req.person.id, stand, person: p });
/* MONATE MIT INHALT, nicht Monate überhaupt. Sechs leere Spalten
sind kein Verlauf — sie sehen nur aus wie einer. */
const gefuellt = streifen.length
? spalten.filter((m) => streifen.some(
(z) => z.zellen.find((c) => c.monat === m)?.stufe)).length
: 0;
res.json({
person: { id: p.id, name: p.name, rolle_name: ROLLEN_NAME[p.rolle] ?? p.rolle },
stufen: ENTWICKLUNG_STUFEN,
/* ENTWICKLUNG_ERWARTUNG wird NICHT mitgeschickt. Die Seite braucht
die Liste nicht, und sie traegt ein Feld `rang` -- eine Zahl je
Erwartungsstufe, die es nur zum Sortieren gibt. Auf einer Seite
ueber Menschen hat keine Zahl etwas verloren, die man addieren
koennte. Der Name der Stufe steht dort, wo er gebraucht wird. */
bloecke: ENTWICKLUNG_BLOECKE.filter((b) => !b.nurSelbst)
.map((b) => ({ schluessel: b.schluessel, name: b.name })),
spalten,
bewegung,
streifen,
texte,
satz: satzVon({ bewegung, stand, texte, spalten }),
gesetzt: stand.length,
gesamt: ENTWICKLUNG_FREMD.length,
/* DER DRITTE AUSGANG. Nicht „in Ordnung“ und nicht „nicht in
Ordnung“, sondern: konnte noch nicht nachsehen — mit Grund.
Die Oberfläche zeichnet dann KEIN Diagramm, sondern schreibt
den Grund hin. */
verlauf_moeglich: gefuellt >= VERLAUF_AB,
verlauf_ab: VERLAUF_AB,
monate_mit_inhalt: gefuellt,
verblasst_nach_tagen: VERBLASST_NACH_TAGEN,
});
} catch (f) {
console.error("[werdegang] Person:", f?.message);
res.status(503).json({ fehler: "nicht_verfuegbar" });
}
});
/* ---- Die eigene Entwicklung -----------------------------------------
Der Teil, der den Menschen selbst angeht — und der nach der
Forschung der wirksamste ist: Wer seinen eigenen Fortschritt sieht,
bleibt dabei.
OHNE NAMEN, OHNE ANLASS, OHNE WER-HAT-WAS. Genau wie bei
`meine-karte`: Sie sieht, DASS ein Punkt sich bewegt hat und wann —
nicht, wer ihn gesetzt hat und was danebensteht. Der Anlass ist für
ein Gespräch aufgeschrieben, nicht zum Lesen ohne den Menschen dazu.
UND ERST, WENN ÜBERHAUPT JEMAND GESETZT HAT. Eine leere eigene Seite
würde wie ein Fehler aussehen; ein Satz ist eine Auskunft.
------------------------------------------------------------------- */
werdegangRouter.get("/workspace/api/werdegang/mich", (req, res) => {
try {
if (!KARTEN_ROLLEN.includes(req.person.rolle)) {
return res.json({ hat_karte: false });
}
const spur = spurVon(req.person.id).filter((z) => z.von_id !== req.person.id);
const spalten = monatsspalten();
/* ZUSAMMENGEFASST ÜBER ALLE BEURTEILER. Hier geht es nicht darum,
wer was gesagt hat — sondern darum, ob sich etwas bewegt.
Deshalb ist eine Bewegung eine Bewegung, egal von wem. */
const bewegung = bewegungVon(spur, spalten);
const vor = bewegung.reduce((s, m) => s + m.vor, 0);
const zurueck = bewegung.reduce((s, m) => s + m.zurueck, 0);
let satz;
if (!spur.length) {
satz = "Zu dir wurde noch nichts eingetragen. Sobald das passiert ist, "
+ "steht hier, was sich über die Zeit bewegt hat.";
} else if (!vor && !zurueck) {
satz = `In den letzten ${spalten.length} Monaten hat sich an den `
+ "Einschätzungen zu dir nichts geändert.";
} else if (vor) {
satz = vor === 1
? "Seit dem letzten Mal ist ein Punkt weitergekommen."
: `Seit dem letzten Mal sind ${vor} Punkte weitergekommen.`;
} else {
satz = "Seit dem letzten Mal wurde ein Punkt anders eingeschätzt als vorher. "
+ "Das ist kein Urteil – frag danach, dann weißt du, worum es geht.";
}
res.json({
hat_karte: true,
spalten,
bewegung,
satz,
/* KEIN STREIFEN. Er würde zeigen, WELCHER Punkt wann gewackelt
hat — und damit mittelbar, was jemand über sie gesagt hat,
ohne dass derjenige dabei ist. Die Zahl reicht; für alles
Weitere ist das Gespräch da, und der Satz sagt das auch. */
hinweis: "Welche Punkte das sind, steht nicht hier. Frag danach – "
+ "eine Einschätzung ohne den Menschen dazu ist eine Behauptung.",
});
} catch (f) {
console.error("[werdegang] Mich:", f?.message);
res.status(503).json({ fehler: "nicht_verfuegbar" });
}
});
+44 -1
View File
@@ -750,6 +750,48 @@ const ENTWICKLUNG_KACHEL = {
zeichen: "steckbrief", ton: 26, ziel: "entwicklung.html", szene: "studio",
};
/* DIE KACHEL, FUER DIE DER NAME FREI GEMACHT WURDE (19.09.2026).
Filipe: "dan perfektionierst du eine neue wo diagramme texte und so
zu jedem modi gemacht wird und diese kategorie kannst du entwicklung
nenen."
SIE ZEIGT AUF werdegang.html UND NICHT AUF entwicklung.html. Das ist
Absicht und sieht beim ersten Hinsehen verdreht aus:
entwicklung.html = der KATALOG, Kachel "Eure Aufgaben"
werdegang.html = die AUSWERTUNG, Kachel "Entwicklung"
Die saubere Endform waere, die alte Datei umzubenennen. Das waeren
25 Fundstellen in 11 Dateien -- und damit zwei Aenderungen in einer:
Wenn danach etwas klemmt, weiss niemand, welche der beiden es war.
Deshalb steht es hier als Kommentar und in `pruef-werdegang` als
Pruefung, die rot wird, wenn die Kachel woanders hinzeigt.
EIGENER TON 40: GEMESSEN, NICHT GERATEN -- und der erste Anlauf lag
falsch. 38 sah frei aus, weil 37 die hoechste Nummer in DIESER Datei
ist. Gezaehlt in start.css sind es aber 39, alle vergeben: 38 ist
"Unsere Seiten", 39 "Vertraulich melden" -- beide kamen am 19.09.
frueh dazu und stehen nicht als `ton:` in workspace.js.
Genau davor warnt das Farbwerkzeug in seinem eigenen Kopf ("Zweiter
Anlauf war Ton 22, weil er frei AUSSAH"). Gezaehlt wird deshalb in
der Datei, die die Farben wirklich enthaelt.
Nach dem Hinzufuegen laeuft `node tools/kachel-farben.mjs --schreiben`
-- es rechnet alle 40 Toene neu gegeneinander, damit sich keine zwei
aehneln.
EIGENES ZEICHEN "werdegang": Die vorhandenen 22 sind entweder
vergeben (steckbrief viermal) oder sagen etwas anderes -- "zahlen"
ist eine Messkurve, "teamlage" sind zwei Koepfe. Hier geht es um
EINEN Menschen und seinen Weg. */
const WERDEGANG_KACHEL = {
...FUEHRUNG_GRUPPE,
name: "Entwicklung", unter: "Wie sich jemand über die Zeit bewegt – ohne Note",
zeichen: "werdegang", ton: 40, ziel: "werdegang.html", szene: "wald",
};
const TALENTE_KACHEL = {
...FUEHRUNG_GRUPPE,
name: "Talente", unter: "Zuschauer, die ins Team passen könnten",
@@ -875,7 +917,7 @@ const BEFINDEN_KACHEL = {
const HAND_BEREICHE = vorGruppe(
[...MODI_BEREICHE, EINGANG_KACHEL, PERSONEN_KACHEL],
"Rund ums Live",
ENTWICKLUNG_KACHEL, TALENTE_KACHEL,
ENTWICKLUNG_KACHEL, WERDEGANG_KACHEL, TALENTE_KACHEL,
).concat(BEFINDEN_KACHEL, {
gruppe: "Für dich", gruppeUnter: "Dein Platz im Team",
...RECHTE_KACHEL,
@@ -1640,6 +1682,7 @@ const ZUSATZ_BEREICHE = {
gruppe: TEAM_GRUPPE.gruppe, gruppeUnter: TEAM_GRUPPE.gruppeUnter,
},
{ ...ENTWICKLUNG_KACHEL, gruppeNach: TEAM_GRUPPE.gruppe },
{ ...WERDEGANG_KACHEL, gruppeNach: TEAM_GRUPPE.gruppe },
{ ...TALENTE_KACHEL, gruppeNach: TEAM_GRUPPE.gruppe },
/* DER TREFF -- GANZ UNTEN, UND ZWAR ABSICHTLICH.
+259
View File
@@ -0,0 +1,259 @@
/* =====================================================================
EINE KACHELFARBE NACHZIEHEN — ohne die anderen anzufassen (19.09.2026)
---------------------------------------------------------------------
WOZU ES DAS BRAUCHT, OBWOHL kachel-farben.mjs EXISTIERT
Das große Werkzeug rechnet ALLE Töne gegeneinander neu. Das ist die
richtige Rechnung und das falsche Mittel, wenn eine einzige Kachel
dazukommt: Am 19.09.2026 nachgemessen hätte ein Lauf für die 40.
Farbe alle 39 anderen verändert — jede Kachel im Haus bekäme eine
neue Farbe, ohne dass jemand danach gefragt hat.
Dieses Werkzeug macht das Gegenteil: Es hält alle vorhandenen Töne
fest und sucht für EINEN die Farbe, die am weitesten von allen
anderen entfernt ist.
Gemessen am selben Tag: Für die neue Kachel „Entwicklung“ fand es
einen Abstand von 0,0988 — fast das Dreifache des engsten Paares,
das im Haus ohnehin schon stand (0,0362 zwischen Ton 15 und 39).
Ein Gesamtneuanstrich war also gar nicht nötig.
---------------------------------------------------------------------
DIE VIER BEDINGUNGEN sind dieselben wie im großen Werkzeug:
1. LESBAR. Mindestens 4,5:1 gegen den dunklen Grund (#0a121e).
Nicht verhandelbar — eine Kachelfarbe trägt auch Text.
2. SPECIELL. Buntheit bis 0,30. Augenschonend heißt dunkles
Schema, kein Flackern — NICHT blass.
3. TRENNBAR. Der Abstand zur nächstliegenden vorhandenen Farbe
wird so groß wie möglich gemacht. Gerechnet in OKLab, weil
dort der Zahlenabstand dem entspricht, was das Auge sieht.
4. NICHT SCHLECHTER ALS VORHER. Findet sich nichts Besseres,
bleibt die alte Farbe stehen — und das Werkzeug sagt es.
---------------------------------------------------------------------
UND DIE FÜNFTE, DIE HIER DAZUKOMMT: ES MUSS ALLES LESEN KÖNNEN
Der erste Anlauf dieser Datei las 31 von 40 Tönen und rechnete
fröhlich weiter. Der Grund: Einstellige Nummern stehen in start.css
mit ZWEI Leerzeichen da (`[data-ton="1"] { …`), zur Ausrichtung.
Das Ergebnis war eine plausible, falsche Farbe — sie hätte mit einer
der neun ungelesenen kollidieren können, ohne dass es auffällt.
Deshalb bricht es jetzt ab, wenn die Zahl der gelesenen Töne nicht
zur Zahl der vorhandenen passt. Das ist der dritte Ausgang:
in Ordnung / nicht in Ordnung / KONNTE NICHT NACHSEHEN, mit Grund.
---------------------------------------------------------------------
AUFRUF
node tools/kachel-farbe-einzeln.mjs 40 nur zeigen
node tools/kachel-farbe-einzeln.mjs 40 --schreiben eintragen
node tools/kachel-farbe-einzeln.mjs --pruefen nur nachmessen
Mehrere Nummern gehen auch: `… 38 39 --schreiben` zieht sie
NACHEINANDER nach, jede gegen den Stand INKLUSIVE der vorherigen.
===================================================================== */
import { readFileSync, writeFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
const HIER = dirname(fileURLToPath(import.meta.url));
const CSS = join(HIER, "..", "workspace", "assets", "css", "start.css");
const GRUND = "#0a121e";
/* ---------- Farbrechnung (gleich wie in kachel-farben.mjs) ---------- */
const linear = (c) => (c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
const zurueck = (c) => (c <= 0.0031308 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055);
const srgb = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255);
function oklab(hex) {
const [r, g, b] = srgb(hex).map(linear);
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
return [
0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s,
1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s,
0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s,
];
}
function ausOklch(L, C, Hgrad) {
const h = (Hgrad * Math.PI) / 180;
const a = C * Math.cos(h), b2 = C * Math.sin(h);
const l_ = (L + 0.3963377774 * a + 0.2158037573 * b2) ** 3;
const m_ = (L - 0.1055613458 * a - 0.0638541728 * b2) ** 3;
const s_ = (L - 0.0894841775 * a - 1.2914855480 * b2) ** 3;
const r = +4.0767416621 * l_ - 3.3077115913 * m_ + 0.2309699292 * s_;
const g = -1.2684380046 * l_ + 2.6097574011 * m_ - 0.3413193965 * s_;
const b = -0.0041960863 * l_ - 0.7034186147 * m_ + 1.7076147010 * s_;
const kanal = (x) => {
const v = zurueck(x);
if (v < -0.001 || v > 1.001) return null;
return Math.round(Math.min(1, Math.max(0, v)) * 255);
};
const [R, G, B] = [kanal(r), kanal(g), kanal(b)];
if (R === null || G === null || B === null) return null;
return "#" + [R, G, B].map((x) => x.toString(16).padStart(2, "0")).join("");
}
const lum = (hex) => {
const [r, g, b] = srgb(hex).map(linear);
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
};
const kontrast = (hex) => (lum(hex) + 0.05) / (lum(GRUND) + 0.05);
const abstand = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
/* ---------- Lesen, und zwar vollstaendig ---------------------------- */
/** Alle Toene als {nummer, hex, lab, zeile}.
*
* `\s*` UND NICHT EIN LEERZEICHEN: Einstellige Nummern stehen mit zwei
* Leerzeichen da. Genau daran ist der erste Anlauf gescheitert. */
function lies(css) {
const aus = [];
for (const zeile of css.split("\n")) {
const m = zeile.match(/\[data-ton="(\d+)"\]\s*\{\s*--ton:\s*(#[0-9a-fA-F]{6})/);
if (!m) continue;
const wofuer = (zeile.match(/\/\*\s*([^*]+?)\s*\*\//) || [])[1] || "";
aus.push({ n: Number(m[1]), hex: m[2].toLowerCase(), lab: oklab(m[2]), zeile, wofuer });
}
return aus;
}
/** Bricht ab, wenn nicht jeder vorhandene Ton auch gelesen wurde. */
function vollstaendigOderAbbruch(css) {
const roh = new Set([...css.matchAll(/\[data-ton="(\d+)"\]/g)].map((m) => Number(m[1])));
const gelesen = lies(css);
const nummern = gelesen.map((t) => t.n);
if (roh.size !== nummern.length) {
console.error(`ABBRUCH: ${roh.size} Toene stehen in der Datei, gelesen wurden ${nummern.length}.`);
console.error(" nicht gelesen: " + [...roh].filter((n) => !nummern.includes(n)).join(", "));
console.error(" Wahrscheinlich hat sich die Schreibweise der Zeile geaendert.");
process.exit(2);
}
return gelesen;
}
function engstesPaar(liste) {
let best = { d: Infinity, a: null, b: null };
for (let i = 0; i < liste.length; i++) {
for (let j = i + 1; j < liste.length; j++) {
const d = abstand(liste[i].lab, liste[j].lab);
if (d < best.d) best = { d, a: liste[i], b: liste[j] };
}
}
return best;
}
/** Die Farbe mit dem groessten Abstand zu allen uebergebenen. */
function beste(andere) {
let treffer = null, wert = -1;
for (let H = 0; H < 360; H += 1) {
for (let L = 0.56; L <= 0.94; L += 0.01) {
/* Von oben herunter, damit die Farbe so satt wie erlaubt wird
und nicht blass -- Filipe: „richtig geil und speziell“. */
for (let C = 0.30; C >= 0.04; C -= 0.004) {
const hex = ausOklch(L, C, H);
if (!hex) continue;
if (kontrast(hex) < 4.5) break;
const lab = oklab(hex);
let naechster = Infinity;
for (const v of andere) naechster = Math.min(naechster, abstand(lab, v.lab));
if (naechster > wert) { wert = naechster; treffer = { hex, lab, L, C, H }; }
break;
}
}
}
return { ...treffer, abstand: wert };
}
/* ---------- Ablauf --------------------------------------------------- */
const argumente = process.argv.slice(2);
const schreiben = argumente.includes("--schreiben");
const nurPruefen = argumente.includes("--pruefen");
const nummern = argumente.filter((a) => /^\d+$/.test(a)).map(Number);
let css = readFileSync(CSS, "utf8");
const anfang = vollstaendigOderAbbruch(css);
console.log(`${anfang.length} Toene gelesen — vollstaendig.`);
{
const p = engstesPaar(anfang);
console.log(`Engstes Paar jetzt: ${p.d.toFixed(4)} `
+ `Ton ${p.a.n} ${p.a.hex} (${p.a.wofuer}) <-> Ton ${p.b.n} ${p.b.hex} (${p.b.wofuer})`);
}
if (nurPruefen || !nummern.length) {
if (!nummern.length && !nurPruefen) {
console.log("\nKeine Nummer angegeben. Aufruf: node tools/kachel-farbe-einzeln.mjs <nr> [--schreiben]");
}
const sortiert = [];
for (let i = 0; i < anfang.length; i++) {
for (let j = i + 1; j < anfang.length; j++) {
sortiert.push({ d: abstand(anfang[i].lab, anfang[j].lab), a: anfang[i], b: anfang[j] });
}
}
sortiert.sort((x, y) => x.d - y.d);
console.log("\nDie fuenf engsten Paare:");
for (const p of sortiert.slice(0, 5)) {
console.log(` ${p.d.toFixed(4)} Ton ${p.a.n} (${p.a.wofuer}) <-> Ton ${p.b.n} (${p.b.wofuer})`);
}
const schwach = anfang.filter((t) => kontrast(t.hex) < 4.5);
console.log(schwach.length
? `\nZU DUNKEL (< 4,5:1): ${schwach.map((t) => `Ton ${t.n}`).join(", ")}`
: "\nAlle Toene erreichen 4,5:1 gegen den Grund.");
process.exit(0);
}
for (const ziel of nummern) {
/* JEDES MAL NEU LESEN: Wer zwei Nummern nacheinander nachzieht, soll
die zweite gegen den Stand INKLUSIVE der ersten rechnen. Sonst
bekommen beide dieselbe Farbe -- genau das ist beim ersten Anlauf
passiert. */
const alle = lies(css);
const vorher = alle.find((t) => t.n === ziel);
if (!vorher) { console.error(`Ton ${ziel} gibt es nicht.`); process.exit(2); }
const andere = alle.filter((t) => t.n !== ziel);
const alt = Math.min(...andere.map((t) => abstand(vorher.lab, t.lab)));
const neu = beste(andere);
if (neu.abstand <= alt) {
console.log(`\nTon ${ziel} (${vorher.wofuer}): bleibt ${vorher.hex} — `
+ `nichts Besseres gefunden (${alt.toFixed(4)}).`);
continue;
}
console.log(`\nTon ${ziel} (${vorher.wofuer})`);
console.log(` vorher ${vorher.hex} Abstand zum Nachbarn ${alt.toFixed(4)}`);
console.log(` nachher ${neu.hex} Abstand zum Nachbarn ${neu.abstand.toFixed(4)}`
+ ` (${(neu.abstand / alt).toFixed(1)}-fach)`);
console.log(` Kontrast ${kontrast(neu.hex).toFixed(2)}:1, Buntheit ${neu.C.toFixed(3)}`);
if (!schreiben) { console.log(" (nur gezeigt — mit --schreiben eintragen)"); continue; }
const neueZeile = vorher.zeile.replace(vorher.hex, neu.hex);
if (neueZeile === vorher.zeile) {
console.error(" ABBRUCH: Die Zeile liess sich nicht ersetzen.");
process.exit(2);
}
css = css.replace(vorher.zeile, neueZeile);
}
if (schreiben) {
writeFileSync(CSS, css);
const nachher = vollstaendigOderAbbruch(readFileSync(CSS, "utf8"));
const p = engstesPaar(nachher);
console.log(`\nGeschrieben. ${nachher.length} Toene, engstes Paar jetzt ${p.d.toFixed(4)} `
+ `(Ton ${p.a.n} <-> Ton ${p.b.n}).`);
const schwach = nachher.filter((t) => kontrast(t.hex) < 4.5);
if (schwach.length) {
console.error(`WARNUNG: ${schwach.map((t) => `Ton ${t.n}`).join(", ")} unter 4,5:1.`);
process.exit(1);
}
}
+21 -4
View File
@@ -5,9 +5,26 @@
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>Anruf-Probe · Team Dogi</title>
<meta name="robots" content="noindex, nofollow" />
<link rel="stylesheet" href="assets/css/basis.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/kopf.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<!-- KORRIGIERT AM 19.09.2026. Hier standen `basis.css` und `kopf.css` --
zwei Dateien, die es im Haus gar nicht gibt. Die Seite lud sie bei
jedem Aufruf ins Leere und stand damit OHNE die Hausfarben da; die
Angaben darunter (var(--text-leise) usw.) liefen ins Nichts.
Ausgerechnet auf dieser Seite faellt das nicht auf: Sie hat ihre
eigene Stilangabe im Kopf, damit sie auch dann noch etwas anzeigt,
wenn die Stilvorlagen klemmen -- und genau die hat den Mangel
verdeckt. Gefunden hat es pruef-struktur.
Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. -->
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
<!-- Ohne diese drei nimmt ein iPhone einen Bildschirmausschnitt als
Symbol, Android zeichnet die Leiste weiss, und die Seite oeffnet
ausserhalb der App -- auf einer Seite, die genau erklaeren soll,
WARUM das Telefon nicht klingelt, ist das besonders unpassend. -->
<link rel="manifest" href="/workspace/crew.webmanifest?v=202609191018" />
<meta name="theme-color" content="#06090f" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<style>
/* Bewusst in der Seite und nicht in einer eigenen Datei: Diese Seite
soll auch dann noch etwas anzeigen, wenn genau das schiefgeht,
@@ -58,6 +75,6 @@
<p class="probe__ende" id="rat"></p>
</main>
<script src="assets/js/anruf-probe.js?v=202609190208" defer></script>
<script src="assets/js/anruf-probe.js?v=202609191018" defer></script>
</body>
</html>
+4 -4
View File
@@ -22,25 +22,25 @@
},
"icons": [
{
"src": "/assets/img/app-symbole/workspace-192.png?v=202609190208",
"src": "/assets/img/app-symbole/workspace-192.png?v=202609191018",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/img/app-symbole/workspace-512.png?v=202609190208",
"src": "/assets/img/app-symbole/workspace-512.png?v=202609191018",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609190208",
"src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609191018",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609190208",
"src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609191018",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
+4 -3
View File
@@ -1027,7 +1027,7 @@ body.start > main { position: relative; z-index: 1; }
Wenn eine achtundzwanzigste Kachel kommt, ist das der Zeitpunkt, an
dem die Kacheln Gruppenfarben brauchen statt Einzelfarben -- nicht
der, an dem man den naechsten Ton dazwischenquetscht. */
[data-ton="26"] { --ton: #07fff1; } /* Entwicklung */
[data-ton="26"] { --ton: #07fff1; } /* Eure Aufgaben (bis 19.09. "Entwicklung") */
[data-ton="27"] { --ton: #ff6bfa; } /* Talente */
/* =====================================================================
@@ -1115,7 +1115,7 @@ body.start > main { position: relative; z-index: 1; }
Ergebnis: OKLCH L=0.59 C=0.185 H=254. Abstand 0.310 zum nächsten
(Ton 33), Kontrast 4.51:1. Blau ist die einzige Ecke, die im Treff
noch frei war — das ist kein Zufall, sondern das Ergebnis. */
[data-ton="38"] { --ton: #087ce7; } /* Unsere Seiten */
[data-ton="38"] { --ton: #0286e5; } /* Unsere Seiten */
/* VERTRAULICH MELDEN (19.09.2026) -- ebenfalls abgesucht, nicht gewaehlt.
140 973 lesbare Kandidaten, gewonnen hat der mit dem groessten
Mindestabstand zu allen neun belegten Toenen: OKLCH L=0.77 C=0.130
@@ -1126,7 +1126,8 @@ body.start > main { position: relative; z-index: 1; }
sind. Mehr ist im lesbaren Bereich nicht zu holen; wer den naechsten
hinzufuegt, sollte das wissen und notfalls einen alten verschieben,
statt die Schwelle stillschweigend zu senken. */
[data-ton="39"] { --ton: #1acae2; } /* Vertraulich melden */
[data-ton="39"] { --ton: #ff57ba; } /* Vertraulich melden */
[data-ton="40"] { --ton: #feb9b8; } /* Entwicklung (die Auswertung) */
/* Rückfallwert für alles, was einen Ton braucht, aber keine Nummer hat.
Steht bewusst in :where() -- damit zählt die Zeile beim Wettstreit der
+300
View File
@@ -0,0 +1,300 @@
/* =====================================================================
ENTWICKLUNG — die Auswertung je Person (19.09.2026)
Sie erbt die Farben von `entwicklung.css` und hält sich an dessen
wichtigste Entscheidung: ES GIBT HIER KEIN ROT.
kühl-grün #7fb08a läuft · nach vorn
babyblau #8ec9ff wächst gerade
bernstein #d8a76a da hakt es · zurückgesetzt
grau kann ich nicht sagen · noch nichts
„Da hakt es" ist eine Beobachtung, kein Alarm. Ein Rückschritt heißt
oft nur, dass jemand genauer hingesehen hat als beim letzten Mal.
Rot würde aus einem Gesprächsanlass ein Urteil machen.
---------------------------------------------------------------------
NACHGERECHNET, NICHT GESCHÄTZT (19.09.2026)
Die vier Töne wurden gegen die Fläche dieser Seite geprüft
(#14161c — das ist `color-mix(var(--tinte) 94%, #ffffff 6%)`
ausgerechnet):
Kontrast gegen die Fläche alle vier ≥ 3:1 bestanden
Unterscheidbarkeit schlechtestes Paar ΔE 16,6 (normal)
bei Farbsehschwäche ΔE 14,9 (Protan)
ΔE 11,2 (Tritan)
Sie liegen bewusst UNTER der Sättigung, die ein Diagrammwerkzeug
vorschlagen würde. Das ist die Hausregel „augenschonend, gedeckt
statt grell" — und sie ist hier vertretbar, weil die Farbe nie
allein trägt: Jede Zelle hat ihr Zeichen, jeder Balken seine Zahl,
und unter dem Diagramm steht eine Legende.
---------------------------------------------------------------------
UND WAS ES WEITERHIN NICHT GIBT
Keinen Fortschrittsbalken je Person, keinen Prozentwert, keine
Punktzahl. Eine Zahl über einen Menschen wird gelesen, verglichen und
weitererzählt.
Die Balken hier messen etwas anderes: WIE VIEL SICH BEWEGT HAT. Das
ist keine Eigenschaft der Person, sondern ein Ereignis zwischen zwei
Zeitpunkten — und es kann nicht „hoch" oder „niedrig" gut sein.
===================================================================== */
/* ---------------------------------------------------------------------
Gemeinsam
------------------------------------------------------------------- */
.w-hinweis {
margin: 0 0 18px; max-width: 70ch;
font-size: .84rem; line-height: 1.6; color: var(--text-still);
}
.w-satz {
margin: 0 0 20px; max-width: 72ch;
font-size: .95rem; line-height: 1.65; color: var(--text-leise);
}
.w-teil { margin: 30px 0 0; }
.w-teil__satz {
margin: 0 0 14px; max-width: 72ch;
font-size: .83rem; line-height: 1.6; color: var(--text-still);
}
.w-fuss {
margin: 34px 0 0; max-width: 72ch;
font-size: .8rem; line-height: 1.6; color: var(--text-still);
}
.w-fuss a { color: var(--text-leise); }
/* ---------------------------------------------------------------------
DIE LISTE
Kacheln statt Tabelle: Eine Tabelle mit Zahlenspalten lädt dazu ein,
die Spalte zu sortieren — und eine sortierte Spalte über Menschen ist
eine Rangliste, egal wie sie überschrieben ist.
------------------------------------------------------------------- */
.w-liste__gitter {
display: grid; gap: 12px;
grid-template-columns: repeat(auto-fill, minmax(min(100%, 19rem), 1fr));
}
.w-karte {
display: grid; gap: 3px; text-align: left;
padding: 15px 17px;
background: color-mix(in srgb, var(--tinte) 94%, #ffffff 6%);
border: 1px solid var(--rand);
border-left: 3px solid var(--rand-hell, var(--rand));
border-radius: var(--radius);
color: inherit; font: inherit; cursor: pointer;
transition: border-color .15s ease, background .15s ease;
}
.w-karte:hover,
.w-karte:focus-visible {
background: color-mix(in srgb, var(--tinte) 90%, #ffffff 10%);
border-left-color: color-mix(in srgb, var(--ton, #8ec9ff) 60%, transparent);
}
.w-karte__name { font-size: 1rem; font-weight: 640; color: var(--text); }
.w-karte__rolle {
font-size: .72rem; letter-spacing: .05em; text-transform: uppercase;
color: var(--text-still);
}
.w-karte__zeilen { display: grid; gap: 2px; margin-top: 7px; }
.w-karte__zeile { font-size: .82rem; line-height: 1.5; color: var(--text-leise); }
/* Die einzige Farbe in der Liste — und sie sagt etwas über UNS. */
.w-karte__zeile[data-alt="ja"] { color: #d8a76a; }
.w-name {
margin: 16px 0 6px; font-size: 1.32rem; font-weight: 650; color: var(--text);
}
/* ---------------------------------------------------------------------
DIAGRAMM 1 — BEWEGUNG
Eine Nulllinie, darüber „nach vorn", darunter „zurück". Beide Hälften
teilen sich denselben Maßstab: Sonst sähe ein einzelner Rückschritt
so groß aus wie fünf Fortschritte.
------------------------------------------------------------------- */
.w-bewegung {
padding: 16px 16px 12px;
background: color-mix(in srgb, var(--tinte) 94%, #ffffff 6%);
border: 1px solid var(--rand);
border-radius: var(--radius);
}
.w-bewegung__gitter {
display: grid;
grid-template-columns: repeat(var(--spalten, 6), 1fr);
gap: 8px;
align-items: end;
}
.w-bewegung__spalte { display: grid; grid-template-rows: 5.2rem auto 3.4rem auto; }
.w-bewegung__halb { display: flex; flex-direction: column; min-height: 0; }
.w-bewegung__halb--oben { justify-content: flex-end; align-items: center; }
.w-bewegung__halb--unten { justify-content: flex-start; align-items: center; }
/* Die Nulllinie. Zurückhaltend — sie ordnet, sie behauptet nichts. */
.w-bewegung__null {
height: 1px; background: var(--rand);
margin: 3px 0;
}
.w-bewegung__balken {
width: min(100%, 2.4rem);
min-height: 3px;
/* 4px runde Enden am ABGEWANDTEN Ende, das Ende an der Nulllinie
bleibt eckig: Ein Balken, der an der Achse rund ist, sieht aus, als
schwebe er. */
border-radius: 4px 4px 0 0;
background: color-mix(in srgb, #7fb08a 78%, transparent);
}
.w-bewegung__halb--unten .w-bewegung__balken { border-radius: 0 0 4px 4px; }
.w-bewegung__balken[data-richtung="zurueck"] {
background: color-mix(in srgb, #d8a76a 78%, transparent);
}
.w-bewegung__zahl {
font-size: .78rem; font-weight: 650; font-variant-numeric: tabular-nums;
color: var(--text-leise);
padding: 2px 0;
}
.w-bewegung__monat {
margin-top: 8px; text-align: center;
font-size: .72rem; color: var(--text-still);
font-variant-numeric: tabular-nums;
}
/* Die Legende ist Pflicht, nicht Zierde: ohne sie trägt allein die
Farbe die Bedeutung. */
.w-legende {
display: flex; flex-wrap: wrap; gap: 8px 20px;
margin-top: 14px; padding-top: 12px;
border-top: 1px solid var(--rand);
}
.w-legende__punkt { display: inline-flex; align-items: center; gap: 7px; }
.w-legende__fleck {
width: 11px; height: 11px; border-radius: 3px; flex: none;
background: color-mix(in srgb, #7fb08a 78%, transparent);
}
.w-legende__fleck[data-richtung="zurueck"] {
background: color-mix(in srgb, #d8a76a 78%, transparent);
}
.w-legende__text { font-size: .76rem; color: var(--text-still); }
/* ---------------------------------------------------------------------
DIAGRAMM 2 — DER STREIFEN
Dieselbe Bauform wie der Befinden-Verlauf. Wer die eine kennt, liest
die andere ohne Erklärung.
------------------------------------------------------------------- */
.w-streifen__gitter {
display: grid; gap: 4px;
padding: 14px 16px;
background: color-mix(in srgb, var(--tinte) 94%, #ffffff 6%);
border: 1px solid var(--rand);
border-radius: var(--radius);
overflow-x: auto;
}
.w-streifen__zeile {
display: grid;
grid-template-columns: minmax(14ch, 1fr) repeat(auto-fit, minmax(2.4rem, 2.4rem));
align-items: center; gap: 6px;
}
.w-streifen__zeile--kopf { margin-bottom: 4px; }
.w-streifen__punkt-name {
font-size: .79rem; line-height: 1.35; color: var(--text-leise);
padding-right: 8px;
}
.w-streifen__monat {
font-size: .71rem; color: var(--text-still); text-align: center;
font-variant-numeric: tabular-nums;
}
.w-streifen__zelle {
display: grid; place-items: center;
width: 2.2rem; height: 1.9rem;
font-size: .9rem; font-weight: 700;
border-radius: var(--radius);
color: var(--text-still);
background: rgba(0, 0, 0, .25);
border: 1px solid var(--rand);
}
.w-streifen__zelle[data-stufe="laeuft"] { color: #7fb08a; border-color: color-mix(in srgb, #668e6e 55%, transparent); }
.w-streifen__zelle[data-stufe="waechst"] { color: #8ec9ff; border-color: color-mix(in srgb, #8ec9ff 45%, transparent); }
.w-streifen__zelle[data-stufe="hakt"] { color: #d8a76a; border-color: color-mix(in srgb, #cc9451 55%, transparent); }
.w-streifen__zelle[data-stufe="leer"] { opacity: .32; }
/* Älter als 90 Tage: sichtbar, aber sichtbar alt. Nicht weg — man soll
sehen, DASS etwas alt ist, statt dass es verschwindet. */
.w-streifen__zelle[data-alt="ja"] { opacity: .45; border-style: dashed; }
/* ---------------------------------------------------------------------
DIE TEXTE
------------------------------------------------------------------- */
.w-texte { display: grid; gap: 12px; }
.w-text {
padding: 14px 16px;
background: color-mix(in srgb, var(--tinte) 94%, #ffffff 6%);
border: 1px solid var(--rand);
border-left: 3px solid var(--rand);
border-radius: var(--radius);
}
/* Nur zwei Arten bekommen eine Farbe, und beide sind ruhig. Alles
einzufärben hieße, dass keine Farbe mehr etwas bedeutet. */
.w-text[data-art="vor"] { border-left-color: color-mix(in srgb, #7fb08a 70%, transparent); }
.w-text[data-art="traegt"] { border-left-color: color-mix(in srgb, #7fb08a 70%, transparent); }
.w-text[data-art="liegt"] { border-left-color: color-mix(in srgb, #cc9451 70%, transparent); }
.w-text[data-art="alt"] { border-left-color: color-mix(in srgb, #cc9451 70%, transparent); }
.w-text__titel {
margin: 0 0 6px; font-size: .95rem; font-weight: 640; color: var(--text);
}
.w-text__satz {
margin: 0; max-width: 72ch;
font-size: .86rem; line-height: 1.65; color: var(--text-leise);
}
.w-text__liste { margin: 11px 0 0; padding: 0; list-style: none; display: grid; gap: 4px; }
.w-text__zeile {
display: flex; flex-wrap: wrap; align-items: baseline;
justify-content: space-between; gap: 6px 14px;
padding: 5px 0;
border-top: 1px solid color-mix(in srgb, var(--rand) 60%, transparent);
}
.w-text__punkt { font-size: .83rem; color: var(--text); flex: 1 1 18ch; }
.w-text__rechts { display: inline-flex; align-items: baseline; gap: 7px; flex: none; }
.w-text__stufe { font-size: .76rem; color: var(--text-still); }
.w-text__stufe[data-stufe="laeuft"] { color: #7fb08a; }
.w-text__stufe[data-stufe="waechst"] { color: #8ec9ff; }
.w-text__stufe[data-stufe="hakt"] { color: #d8a76a; }
.w-text__pfeil { font-size: .76rem; color: var(--text-still); }
.w-text__tage {
font-size: .72rem; color: var(--text-still); font-variant-numeric: tabular-nums;
}
/* ---------------------------------------------------------------------
Handy
Der Streifen darf schmaler werden, das Bewegungsdiagramm nicht: Unter
rund 2 rem je Spalte sind sechs Balken nicht mehr unterscheidbar. Er
rollt dann seitlich, statt unleserlich zu werden.
------------------------------------------------------------------- */
@media (max-width: 560px) {
.w-streifen__zeile {
grid-template-columns: minmax(10ch, 1fr) repeat(auto-fit, minmax(2rem, 2rem));
}
.w-streifen__zelle { width: 1.9rem; }
.w-bewegung__gitter { gap: 5px; }
.w-bewegung__spalte { grid-template-rows: 4.4rem auto 2.8rem auto; }
.w-bewegung__balken { width: min(100%, 1.9rem); }
.w-text__zeile { justify-content: flex-start; }
}
/* Wer weniger Bewegung möchte, bekommt weniger Bewegung. Hier gibt es
ohnehin keine Animation — der Übergang an der Karte ist alles, und
auch der fällt weg. */
@media (prefers-reduced-motion: reduce) {
.w-karte { transition: none; }
}
/* Erzwungene Farben (Windows-Kontrastmodus): Dort gilt die
Systemfarbe, und die Zeichen in den Zellen tragen die Auskunft
allein — genau dafür sind sie da. */
@media (forced-colors: active) {
.w-bewegung__balken { background: CanvasText; }
.w-legende__fleck { background: CanvasText; }
.w-streifen__zelle { border-color: CanvasText; }
}
+16
View File
@@ -263,6 +263,22 @@
fuellung: ['M3.5 5.5h17l-6.6 7.6h-3.8z'],
linien: ['M3.5 5.5h17l-6.6 7.6v6.4l-3.8-2.1v-4.3z'],
},
werdegang: {
/* EIN Mensch und sein Weg -- nicht zwei Koepfe wie bei
"teamlage", nicht eine Messkurve wie bei "zahlen". Der
Unterschied ist der ganze Punkt dieser Kachel: Hier wird
niemand mit jemandem verglichen.
Drei Stufen, die ansteigen, und ein gefuellter Punkt auf der
obersten -- das Jetzt. Gefuellt ist NUR der Punkt: Wuerden die
Stufen mitgefuellt, saehe es aus wie ein Balkendiagramm, und
genau das ist es ausdruecklich nicht. */
fuellung: ['M17.8 6.6a1.7 1.7 0 1 0 0 3.4 1.7 1.7 0 0 0 0-3.4z'],
linien: ['M3.4 19.6h4.2v-3.4H3.4z', 'M9.9 19.6h4.2v-6.6H9.9z',
'M16.4 19.6h4.2v-6.2h-4.2z',
'M17.8 6.6a1.7 1.7 0 1 0 0 3.4 1.7 1.7 0 0 0 0-3.4z',
'M5.5 16.2 12 13l5.8-2.7'],
},
wissen: {
/* Die linke Seite gefüllt: ein aufgeschlagenes Buch hat eine
Seite im Licht und eine im Schatten. */
+78 -6
View File
@@ -663,7 +663,37 @@
Die Zuordnung kommt aus assets/js/bereiche.js, also aus derselben
Liste wie die Kachel selbst. Wer dort eine Farbe ändert, ändert
Kachel UND Seite -- sie können gar nicht auseinanderlaufen. */
(function seiteEinfaerben() {
/* ==== GEFUNDEN AM 19.09.2026: DIE HAELFTE DER SEITEN BEKAM NICHTS =====
Der Umbau von heute Nacht heisst "Jede Seite traegt die Farbe ihrer
Kachel". Nachgemessen traf das auf 18 von 34 Seiten zu. Elf
Seiten mit echter Kachel gingen leer aus:
befinden, bewerben, bewerbungen, entwicklung, hilfe, rechte,
talente, teamlage, teilen, treff-moderation, treff-regeln,
unsere-seiten
Es sind ausgerechnet die von Team Dogi -- also die, die Filipe
taeglich benutzt.
DER GRUND: `B.zuSeite()` sucht nur in `Bereiche.GRUPPEN`, und dort
stehen ausschliesslich die Kacheln der AGENTUR. Die Kacheln von
Team Dogi und vom Treff liefert der SERVER (`ich.bereiche`,
`ich.bereiche_zusatz`) -- die kannte diese Stelle nicht.
Auffallen konnte das nicht: Eine Seite ohne Farbe sieht aus wie
eine Seite, die eben keine hat. Dieselbe Sorte Fehler wie bei
`chat.html` am 09.09. -- im Quelltext sieht die Zeile richtig aus,
und nur das Nachzaehlen findet sie.
JETZT ZWEIMAL, UND DAS IST ABSICHT:
1. sofort beim Laden aus GRUPPEN -- die Agenturseiten haben ihre
Farbe damit ohne ein einziges Flackern, wie bisher.
2. noch einmal, sobald der Server gesagt hat, wer man ist. Nur
wenn beim ersten Mal nichts gefunden wurde.
Zweimal zeichnen verhindert `kopf.dataset.gezeichnet`.
================================================================= */
function seiteEinfaerben(zusatz) {
const B = window.Bereiche;
/* Der Kalender hat einen eigenen Kopf (.k-kopf), weil dort der
Zeitraum die Überschrift ist. Er soll sein Zeichen trotzdem
@@ -671,8 +701,11 @@
einzige ohne. */
if (!B) return;
const bereich = B.zuSeite(location.pathname, location.search);
if (!bereich) return;
/* Erst die Kacheln, die der Server geschickt hat -- sie sind die
genauere Auskunft: Wer diese Seite oeffnen darf, hat sie auch als
Kachel bekommen. Danach die Agenturliste aus dem Browser. */
const bereich = ausListe(zusatz) || B.zuSeite(location.pathname, location.search);
if (!bereich) return false;
/* ==== ERST DIE SEITE, DANN DIE PLAKETTE ==========================
@@ -721,9 +754,12 @@
ohne ihn anzufassen. Dadurch funktioniert das auf allen dreizehn
Seiten, ohne dreizehn HTML-Dateien zu ändern. */
const kopf = document.querySelector('.kopf-zeile, .k-kopf');
if (!kopf) return;
/* `true` und nicht `false`: Ton und Buehne SIND gesetzt. Nur die
Plakette faellt aus, weil es keinen Kopf gibt -- das ist kein
Grund, es beim zweiten Anlauf noch einmal zu versuchen. */
if (!kopf || kopf.dataset.gezeichnet === 'ja') return true;
const erstes = kopf.firstElementChild;
if (!erstes) return;
if (!erstes) return true;
const plakette = document.createElement('span');
plakette.className = 'kopf-zeichen';
@@ -745,7 +781,43 @@
schatten.append(B.zeichenBauen(bereich.zeichen, 'kopf-wasserzeichen__svg'));
kopf.append(schatten);
kopf.dataset.gezeichnet = 'ja';
})();
return true;
}
/** Die Kachel zu dieser Seite in einer Liste vom Server suchen.
*
* UEBER DAS ZIEL UND NICHT UEBER DEN NAMEN. Namen aendern sich --
* am 19.09. hiess eine Kachel morgens noch "Entwicklung" und
* mittags "Eure Aufgaben". Das Ziel ist die Angabe, die bleibt. */
function ausListe(listen) {
if (!Array.isArray(listen) || !listen.length) return null;
const datei = String(location.pathname).split('/').pop() || 'start.html';
const gesucht = datei + (location.search || '');
let treffer = null;
for (const k of listen) {
if (!k || !k.ziel) continue;
if (k.ziel === gesucht) return k;
if (!treffer && String(k.ziel).split('?')[0] === datei) treffer = k;
}
return treffer;
}
const schonEingefaerbt = seiteEinfaerben(null);
/* Der zweite Anlauf. `werZeigen` ruft jede Seite auf, sobald der
Server geantwortet hat -- es ist die einzige Stelle, an der die
Kacheln von Team Dogi im Browser ankommen. */
if (!schonEingefaerbt) {
const vorherWer = window.werZeigen;
window.werZeigen = function (ich) {
try { if (typeof vorherWer === 'function') vorherWer.apply(this, arguments); }
finally {
seiteEinfaerben([
...(ich?.bereiche || []), ...(ich?.bereiche_zusatz || []),
]);
}
};
}
/* ---------- Der Schriftzug oben links -----------------------------------
+406
View File
@@ -0,0 +1,406 @@
/* =====================================================================
ENTWICKLUNG — die Auswertung je Person (19.09.2026)
Zwei Gesichter, und welches man sieht, entscheidet der Server:
Wer das Team führt, bekommt die Karten; wer dazugehört, bekommt
ausschließlich die Bewegung in der eigenen.
---------------------------------------------------------------------
DIE DIAGRAMME SIND SELBST GEZEICHNET
Kein Diagrammpaket. Zwei Formen, beide aus wenigen Zeilen:
BEWEGUNG Balken um eine Nulllinie, nach oben „nach vorn“, nach
unten „zurück“. Sechs Monate, mehr passt auf ein Handy
nicht und sagt auch nichts.
STREIFEN Ein Raster: Zeile = Punkt, Spalte = Monat, Zelle = die
Stufe, die damals galt. Dieselbe Bauform wie der
Befinden-Verlauf — wer die eine kennt, liest die andere.
Ein Paket dafür wären 60 kB über eine Leitung, auf der jemand mit dem
Handy im Stream steht. Und es brächte seine eigenen Farben mit, die
dann neben den Hausfarben stünden.
---------------------------------------------------------------------
DIE FARBE IST NIE DIE AUSKUNFT
Jede Zelle trägt das Zeichen ihrer Stufe als Text (✓ ↗ ! –), jeder
Balken seine Zahl. Wer Farben schlecht unterscheidet, liest genau
dasselbe. Das ist im Haus seit dem Befinden-Verlauf so.
Und es gibt hier kein Rot. „Da hakt es“ ist eine Beobachtung, kein
Alarm; ein Rückschritt heißt oft nur, dass jemand genauer hingesehen
hat als beim letzten Mal.
===================================================================== */
(() => {
const $ = (id) => document.getElementById(id);
const el = (t, k, text) => {
const n = document.createElement(t);
if (k) n.className = k;
if (text !== undefined) n.textContent = text;
return n;
};
const melde = (t) => { const f = $('fehler'); if (f) { f.textContent = t; f.hidden = !t; } };
async function hole(pfad) {
const a = await fetch(pfad, { credentials: 'same-origin' });
if (a.status === 401) { location.assign('/workspace/'); throw new Error('abgemeldet'); }
return a;
}
/* Die vier Stufen, wie der Server sie schickt — Zeichen inklusive.
Eine eigene Liste hier wäre die, die beim nächsten Nachschärfen
auseinanderläuft. */
let stufen = [];
const zeichenVon = (s) => stufen.find((x) => x.schluessel === s)?.zeichen || '·';
const namenVon = (s) => stufen.find((x) => x.schluessel === s)?.name || s;
const MONATSNAME = ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun',
'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'];
function monatKurz(m) {
const [j, mm] = String(m).split('-').map(Number);
if (!j || !mm) return m;
return `${MONATSNAME[mm - 1]}${mm === 1 ? ' ' + String(j).slice(2) : ''}`;
}
function datumKurz(iso) {
const t = Date.parse(iso || '');
if (!Number.isFinite(t)) return '';
return new Date(t).toLocaleDateString('de-DE',
{ day: '2-digit', month: '2-digit', year: '2-digit' });
}
/* =====================================================================
DIAGRAMM 1 — BEWEGUNG
ZWEI RICHTUNGEN UM EINE NULLLINIE. „Nach vorn“ und „zurück“ sind
dieselbe Größe mit verschiedenem Vorzeichen; zwei getrennte
Diagramme würden dazu verleiten, sie wie zwei Themen zu lesen.
EINE ACHSE, EIN MASSSTAB. Der höchste Balken bestimmt ihn, und er
gilt für oben und unten gleichermaßen — sonst sähe ein einzelner
Rückschritt so groß aus wie fünf Fortschritte.
KEINE ZAHL AN JEDEM BALKEN. Nur dort, wo etwas steht. Eine Null an
jedem leeren Monat wäre Lärm; die leere Spalte sagt dasselbe.
===================================================================== */
function bewegungBauen(bewegung, spalten) {
const wurzel = el('div', 'w-bewegung');
const hoechst = Math.max(1, ...bewegung.map(
(m) => Math.max(m.vor + m.geklaert + m.neu, m.zurueck + m.offen)));
const gitter = el('div', 'w-bewegung__gitter');
gitter.style.setProperty('--spalten', String(spalten.length));
for (const m of bewegung) {
const spalte = el('div', 'w-bewegung__spalte');
const oben = el('div', 'w-bewegung__halb w-bewegung__halb--oben');
const obenWert = m.vor + m.geklaert + m.neu;
if (obenWert) {
const b = el('div', 'w-bewegung__balken');
b.dataset.richtung = 'vor';
b.style.height = `${Math.round((obenWert / hoechst) * 100)}%`;
b.title = [
m.vor ? `${m.vor}× nach vorn` : null,
m.geklaert ? `${m.geklaert}× geklärt` : null,
m.neu ? `${m.neu}× zum ersten Mal` : null,
].filter(Boolean).join(', ');
oben.append(el('span', 'w-bewegung__zahl', String(obenWert)), b);
}
spalte.append(oben);
spalte.append(el('div', 'w-bewegung__null'));
const unten = el('div', 'w-bewegung__halb w-bewegung__halb--unten');
const untenWert = m.zurueck + m.offen;
if (untenWert) {
const b = el('div', 'w-bewegung__balken');
b.dataset.richtung = 'zurueck';
b.style.height = `${Math.round((untenWert / hoechst) * 100)}%`;
b.title = [
m.zurueck ? `${m.zurueck}× zurückgesetzt` : null,
m.offen ? `${m.offen}× auf „Kann ich nicht sagen“` : null,
].filter(Boolean).join(', ');
unten.append(b, el('span', 'w-bewegung__zahl', String(untenWert)));
}
spalte.append(unten);
spalte.append(el('div', 'w-bewegung__monat', monatKurz(m.monat)));
gitter.append(spalte);
}
wurzel.append(gitter);
/* DIE LEGENDE IST PFLICHT, nicht Zierde: Ohne sie trägt allein die
Farbe die Bedeutung, und das ist genau die Stelle, an der ein
Diagramm für manche Leute aufhört zu funktionieren. */
const legende = el('div', 'w-legende');
for (const [richtung, text] of [
['vor', 'nach vorn · geklärt · zum ersten Mal'],
['zurueck', 'zurückgesetzt · wieder offen'],
]) {
const p = el('span', 'w-legende__punkt');
const fleck = el('span', 'w-legende__fleck');
fleck.dataset.richtung = richtung;
p.append(fleck, el('span', 'w-legende__text', text));
legende.append(p);
}
wurzel.append(legende);
return wurzel;
}
/* =====================================================================
DIAGRAMM 2 — DER STREIFEN
===================================================================== */
function streifenBauen(streifen, spalten) {
const gitter = el('div', 'w-streifen__gitter');
const kopf = el('div', 'w-streifen__zeile w-streifen__zeile--kopf');
kopf.append(el('span', 'w-streifen__punkt-name', ''));
for (const m of spalten) kopf.append(el('span', 'w-streifen__monat', monatKurz(m)));
gitter.append(kopf);
for (const z of streifen) {
const zeile = el('div', 'w-streifen__zeile');
zeile.append(el('span', 'w-streifen__punkt-name', z.titel));
for (const c of z.zellen) {
const zelle = el('span', 'w-streifen__zelle',
c.stufe ? zeichenVon(c.stufe) : '·');
if (c.stufe) {
zelle.dataset.stufe = c.stufe;
if (c.alt) zelle.dataset.alt = 'ja';
zelle.title = `${monatKurz(c.monat)}: ${namenVon(c.stufe)}`
+ (c.seit ? ` (gesetzt am ${datumKurz(c.seit)})` : '')
+ (c.alt ? ' – älter als drei Monate' : '');
} else {
zelle.dataset.stufe = 'leer';
zelle.title = `${monatKurz(c.monat)}: noch keine Einschätzung`;
}
zeile.append(zelle);
}
gitter.append(zeile);
}
return gitter;
}
/* =====================================================================
DIE TEXTE
===================================================================== */
function texteBauen(texte) {
const wurzel = el('div', 'w-texte');
for (const t of texte) {
const kasten = el('section', 'w-text');
kasten.dataset.art = t.art;
kasten.append(el('h4', 'w-text__titel', t.titel));
kasten.append(el('p', 'w-text__satz', t.satz));
if (t.punkte?.length) {
const liste = el('ul', 'w-text__liste');
for (const p of t.punkte) {
const zeile = el('li', 'w-text__zeile');
zeile.append(el('span', 'w-text__punkt', p.titel));
const rechts = el('span', 'w-text__rechts');
if (p.von && p.zu) {
rechts.append(el('span', 'w-text__stufe', namenVon(p.von)));
rechts.append(el('span', 'w-text__pfeil', '→'));
}
if (p.zu) {
const s = el('span', 'w-text__stufe', namenVon(p.zu));
s.dataset.stufe = p.zu;
rechts.append(s);
}
if (p.erwartung) rechts.append(el('span', 'w-text__stufe', p.erwartung));
if (p.tage != null) rechts.append(el('span', 'w-text__tage', `seit ${p.tage} T.`));
else if (p.am) rechts.append(el('span', 'w-text__tage', datumKurz(p.am)));
zeile.append(rechts);
liste.append(zeile);
}
kasten.append(liste);
}
wurzel.append(kasten);
}
return wurzel;
}
/* =====================================================================
DIE KARTE EINER PERSON
===================================================================== */
async function karteZeigen(id) {
const a = await hole(`/workspace/api/werdegang/person/${id}`);
if (!a.ok) { melde('Diese Karte lässt sich gerade nicht öffnen.'); return; }
const d = await a.json();
stufen = d.stufen || [];
$('liste-block').hidden = true;
$('mich-block').hidden = true;
$('karte-block').hidden = false;
$('karte-name').textContent = `${d.person.name} · ${d.person.rolle_name}`;
$('karte-satz').textContent = d.satz;
/* ==== DER DRITTE AUSGANG ========================================
Nicht „in Ordnung“ und nicht „nicht in Ordnung“, sondern:
KONNTE NOCH NICHT NACHSEHEN — mit Grund. Ein Diagramm über
einen einzigen Zeitpunkt wäre kein Diagramm, sondern ein
Balken, und er sähe aus wie eine Aussage.
============================================================= */
const bBlock = $('bewegung-block'), sBlock = $('streifen-block');
if (!d.verlauf_moeglich) {
bBlock.hidden = false; sBlock.hidden = true;
$('bewegung').replaceChildren();
$('bewegung-satz').textContent = d.gesetzt === 0
? 'Zu dieser Person ist noch nichts eingetragen. Sobald die erste '
+ 'Einschätzung steht, beginnt hier der Verlauf.'
: `Es gibt bisher ${d.monate_mit_inhalt === 1 ? 'einen Monat' : d.monate_mit_inhalt + ' Monate'} `
+ `mit Einschätzungen. Ein Verlauf braucht mindestens ${d.verlauf_ab} – `
+ 'vorher stünde hier eine Linie, die nichts bedeutet.';
} else {
bBlock.hidden = false; sBlock.hidden = false;
$('bewegung-satz').textContent =
`Die letzten ${d.spalten.length} Monate. Nach oben: weitergekommen, `
+ 'geklärt oder zum ersten Mal beantwortet. Nach unten: zurückgesetzt '
+ 'oder wieder offen. Beides ist eine Auskunft, keines ist eine Note.';
$('bewegung').replaceChildren(bewegungBauen(d.bewegung, d.spalten));
$('streifen-satz').textContent =
'Was in einem Monat galt, gilt auch im nächsten, solange niemand etwas '
+ `ändert – deshalb sind die Zellen durchgezogen. Blasse Zellen sind älter `
+ `als ${d.verblasst_nach_tagen} Tage und zählen nicht mehr mit.`;
$('streifen').replaceChildren(streifenBauen(d.streifen, d.spalten));
}
const tBlock = $('texte-block');
if (d.texte?.length) {
tBlock.hidden = false;
$('texte').replaceChildren(texteBauen(d.texte));
} else {
tBlock.hidden = true;
}
/* Die Adresse merkt sich, welche Karte offen ist — damit „Zurück“
auf die Liste führt und nicht aus der Seite heraus. */
history.replaceState(null, '', `?p=${id}`);
window.scrollTo({ top: 0, behavior: 'instant' });
}
/* =====================================================================
DIE LISTE
KEINE SORTIERUNG NACH AKTIVITÄT. Eine Liste, die die mit der
meisten Bewegung nach oben stellt, ist eine Rangliste mit einem
anderen Namen. Der Server sortiert nach Namen, und dabei bleibt es.
===================================================================== */
function listeBauen(d) {
const wurzel = el('div', 'w-liste__gitter');
for (const p of d.liste) {
const karte = el('button', 'w-karte');
karte.type = 'button';
karte.append(el('span', 'w-karte__name', p.name));
karte.append(el('span', 'w-karte__rolle', p.rolle_name));
const zeilen = el('span', 'w-karte__zeilen');
zeilen.append(el('span', 'w-karte__zeile',
p.gesetzt === 0
? 'Noch nichts eingetragen'
: `${p.gesetzt} von ${p.gesamt} Punkten beantwortet`));
zeilen.append(el('span', 'w-karte__zeile',
p.bewegt === 0
? `In ${d.monate} Monaten keine Änderung`
: `${p.bewegt} Änderung${p.bewegt === 1 ? '' : 'en'} in ${d.monate} Monaten`
+ (p.vor ? ` · ${p.vor} nach vorn` : '')));
if (p.zuletzt_tage != null) {
const z = el('span', 'w-karte__zeile',
p.zuletzt_tage === 0 ? 'Zuletzt heute angesehen'
: `Zuletzt vor ${p.zuletzt_tage} Tag${p.zuletzt_tage === 1 ? '' : 'en'} angesehen`);
/* DIE EINZIGE FARBE IN DER LISTE, und sie sagt etwas über UNS:
Wer seit über drei Monaten nicht hingesehen hat, hat keine
Auskunft über diesen Menschen, sondern über sich. */
if (p.zuletzt_tage >= 90) z.dataset.alt = 'ja';
zeilen.append(z);
}
karte.append(zeilen);
karte.addEventListener('click', () => karteZeigen(p.id).catch(() => {}));
wurzel.append(karte);
}
return wurzel;
}
async function listeZeigen() {
const a = await hole('/workspace/api/werdegang/liste');
if (a.status === 404) return false; /* kein Leitungszugang */
if (!a.ok) { melde('Die Karten lassen sich gerade nicht laden.'); return true; }
const d = await a.json();
$('karte-block').hidden = true;
$('mich-block').hidden = true;
$('liste-block').hidden = false;
$('liste').removeAttribute('aria-busy');
$('liste-hinweis').textContent = d.hinweis;
if (!d.liste.length) {
$('liste').replaceChildren(el('p', 'leise',
'Es gibt noch niemanden mit einer Karte. Sobald jemand ins Team kommt, '
+ 'steht er hier.'));
} else {
$('liste').replaceChildren(listeBauen(d));
}
history.replaceState(null, '', location.pathname);
return true;
}
/* =====================================================================
DIE EIGENE SICHT
OHNE NAMEN, OHNE ANLASS, OHNE STREIFEN. Sie sieht, DASS sich etwas
bewegt hat und wann — nicht, wer was gesetzt hat und was
danebensteht. Der Anlass ist für ein Gespräch aufgeschrieben, nicht
zum Lesen ohne den Menschen dazu.
===================================================================== */
async function michZeigen() {
const a = await hole('/workspace/api/werdegang/mich');
if (!a.ok) { melde('Das lässt sich gerade nicht laden.'); return; }
const d = await a.json();
$('liste-block').hidden = true;
$('karte-block').hidden = true;
$('mich-block').hidden = false;
$('titel').textContent = 'Deine Entwicklung';
$('unterzeile').textContent =
'Was sich über die Zeit bewegt hat. Keine Note, keine Punktzahl.';
if (!d.hat_karte) {
$('mich-satz').textContent =
'Für deine Rolle wird keine Entwicklungskarte geführt. Das ist keine '
+ 'Lücke – es gibt schlicht nichts, was hier stehen müsste.';
$('mich-hinweis').textContent = '';
return;
}
stufen = d.stufen || stufen;
$('mich-satz').textContent = d.satz;
$('mich-hinweis').textContent = d.hinweis;
const etwas = d.bewegung?.some((m) => m.vor || m.zurueck || m.neu || m.geklaert);
$('mich-bewegung').replaceChildren(
etwas ? bewegungBauen(d.bewegung, d.spalten) : el('span'));
}
/* ---- Start ---------------------------------------------------------- */
$('zur-liste').addEventListener('click', () => { listeZeigen().catch(() => {}); });
(async () => {
try {
/* ERST DIE LISTE FRAGEN. Antwortet der Server mit 404, führt diese
Person das Team nicht — dann ist die eigene Sicht die richtige.
Die Rolle hier selbst zu prüfen hieße, dieselbe Entscheidung an
zwei Stellen zu treffen, und die zweite ist immer die, die
irgendwann etwas durchlässt. */
const leitung = await listeZeigen();
if (!leitung) { await michZeigen(); return; }
const p = new URLSearchParams(location.search).get('p');
if (p) await karteZeigen(Number(p));
} catch (f) {
if (String(f?.message || '') !== 'abgemeldet') {
melde('Die Seite lässt sich gerade nicht laden.');
}
}
})();
})();
+12 -12
View File
@@ -12,19 +12,19 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -291,10 +291,10 @@
</form>
</dialog>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/glocke.js?v=202609190208" defer></script>
<script src="assets/js/aufgaben.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/glocke.js?v=202609191018" defer></script>
<script src="assets/js/aufgaben.js?v=202609191018" defer></script>
</body>
</html>
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/automation.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/automation.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -168,10 +168,10 @@
</main>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/glocke.js?v=202609190208" defer></script>
<script src="assets/js/automation.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/glocke.js?v=202609191018" defer></script>
<script src="assets/js/automation.js?v=202609191018" defer></script>
</body>
</html>
+12 -12
View File
@@ -7,14 +7,14 @@
<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=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -105,9 +105,9 @@
</main>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/befinden.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/befinden.js?v=202609191018" defer></script>
</body>
</html>
+18 -18
View File
@@ -12,24 +12,24 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609191018" />
<!-- Wegen des Blocks „Willst du dazugehoeren?" auf dem Brett
„Mitmachen". Eine eigene Kopie der Regel hier waere die zweite
Stelle, an der dieselbe Sache anders aussieht. -->
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -251,14 +251,14 @@
</main>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/glocke.js?v=202609190208" defer></script>
<script src="assets/js/checkliste.js?v=202609190208" defer></script>
<script src="assets/js/ampel.js?v=202609190208" defer></script>
<script src="assets/js/schulung.js?v=202609190208" defer></script>
<script src="assets/js/vorlagen.js?v=202609190208" defer></script>
<script src="assets/js/bereich.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/glocke.js?v=202609191018" defer></script>
<script src="assets/js/checkliste.js?v=202609191018" defer></script>
<script src="assets/js/ampel.js?v=202609191018" defer></script>
<script src="assets/js/schulung.js?v=202609191018" defer></script>
<script src="assets/js/vorlagen.js?v=202609191018" defer></script>
<script src="assets/js/bereich.js?v=202609191018" defer></script>
</body>
</html>
+15 -15
View File
@@ -7,17 +7,17 @@
<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=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein - siehe treff-regeln.html. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -108,10 +108,10 @@
</main>
<!-- Der Fuß gehört zu jeder Seite - siehe pruef-css-klassen. -->
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/auskunft.js?v=202609190208" defer></script>
<script src="assets/js/bewerben.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/auskunft.js?v=202609191018" defer></script>
<script src="assets/js/bewerben.js?v=202609191018" defer></script>
</body>
</html>
+14 -14
View File
@@ -7,16 +7,16 @@
<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=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein - siehe treff-regeln.html. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -80,10 +80,10 @@
</main>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/auskunft.js?v=202609190208" defer></script>
<script src="assets/js/bewerbungen.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/auskunft.js?v=202609191018" defer></script>
<script src="assets/js/bewerbungen.js?v=202609191018" defer></script>
</body>
</html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/calls.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/calls.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -89,11 +89,11 @@
</main>
<script src="assets/js/ki.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/glocke.js?v=202609190208" defer></script>
<script src="assets/js/calls.js?v=202609190208" defer></script>
<script src="assets/js/ki.js?v=202609191018" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/glocke.js?v=202609191018" defer></script>
<script src="assets/js/calls.js?v=202609191018" defer></script>
</body>
</html>
+14 -14
View File
@@ -12,26 +12,26 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<!-- aufgaben.css bringt die Dialog- und Formularstile mit (.dialog,
.neu__zeile, .dialog__knoepfe). Sie stehen dort seit dem
Aufgabenbrett; sie hierher zu kopieren hiesse, sie an zwei Stellen
zu pflegen. Dieselbe Lösung nutzen kalender.html und content.html.
Gemeldet von pruef-css-klassen.mjs, bevor jemand einen ungestylten
Dialog zu sehen bekam. -->
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/chat.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/chat.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -341,15 +341,15 @@
</form>
</dialog>
<script src="assets/js/bereiche.js?v=202609190208"></script>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208"></script>
<script src="assets/js/glocke.js?v=202609190208"></script>
<script src="assets/js/bereiche.js?v=202609191018"></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018"></script>
<script src="assets/js/glocke.js?v=202609191018"></script>
<!-- ANRUF VOR CHAT (18.09.2026): anruf.js stellt `window.anrufStarten`
bereit, und chat.js baut die Knoepfe nur, wenn es das gibt.
Andersherum waere die Reihenfolge eine stille Abhaengigkeit --
die Knoepfe fehlten, und niemand wuesste warum. -->
<script src="assets/js/anruf.js?v=202609190208"></script>
<script src="assets/js/chat.js?v=202609190208"></script>
<script src="assets/js/anruf.js?v=202609191018"></script>
<script src="assets/js/chat.js?v=202609191018"></script>
</body>
</html>
+15 -15
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/content.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/content.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -179,12 +179,12 @@
</main>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/glocke.js?v=202609190208" defer></script>
<script src="assets/js/checkliste.js?v=202609190208" defer></script>
<script src="assets/js/vorlagen.js?v=202609190208" defer></script>
<script src="assets/js/content.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/glocke.js?v=202609191018" defer></script>
<script src="assets/js/checkliste.js?v=202609191018" defer></script>
<script src="assets/js/vorlagen.js?v=202609191018" defer></script>
<script src="assets/js/content.js?v=202609191018" defer></script>
</body>
</html>
+5 -5
View File
@@ -36,15 +36,15 @@
<link rel="preload" as="image" href="assets/img/buehne/crew-gate-hoch-580.webp"
type="image/webp" media="(max-width: 899px)" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/crew-32.png?v=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/crew-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/crew-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/crew-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<!-- Diese Wand steht NUR auf der Adresse von Team Dogi (ueberall sonst
404, siehe crewWeiche). Sie darf das Haus deshalb direkt nennen
statt ueber den Platzhalter haus.css zu gehen -- und sie MUSS es:
Eine Zugangswand, die sich erst nach dem Umbiegen faerbt, waere
die eine Seite, auf der man das Umfaerben sieht. -->
<link rel="stylesheet" href="assets/css/crew-haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/crew-haus.css?v=202609191018" />
</head>
<body class="gate gate--crew">
@@ -339,6 +339,6 @@
</main>
</div>
<script src="assets/js/gate.js?v=202609190208" defer></script>
<script src="assets/js/gate.js?v=202609191018" defer></script>
</body>
</html>
+4 -4
View File
@@ -22,25 +22,25 @@
},
"icons": [
{
"src": "/assets/img/app-symbole/crew-192.png?v=202609190208",
"src": "/assets/img/app-symbole/crew-192.png?v=202609191018",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/img/app-symbole/crew-512.png?v=202609190208",
"src": "/assets/img/app-symbole/crew-512.png?v=202609191018",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609190208",
"src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609191018",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609190208",
"src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609191018",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/dateien.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/dateien.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -108,10 +108,10 @@
</main>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/glocke.js?v=202609190208" defer></script>
<script src="assets/js/dateien.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/glocke.js?v=202609191018" defer></script>
<script src="assets/js/dateien.js?v=202609191018" defer></script>
</body>
</html>
+12 -12
View File
@@ -7,14 +7,14 @@
<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=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -106,9 +106,9 @@
</main>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/entwicklung.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/entwicklung.js?v=202609191018" defer></script>
</body>
</html>
+11 -11
View File
@@ -7,13 +7,13 @@
<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=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/hilfe.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/hilfe.css?v=202609191018" />
</head>
<body class="start">
@@ -141,9 +141,9 @@
</main>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/hilfe.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/hilfe.js?v=202609191018" defer></script>
</body>
</html>
+4 -4
View File
@@ -30,9 +30,9 @@
<link rel="preload" as="image" href="assets/img/buehne/gate-hoch-580.webp"
type="image/webp" media="(max-width: 899px)" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
</head>
<body class="gate">
@@ -401,6 +401,6 @@
</main>
</div>
<script src="assets/js/gate.js?v=202609190208" defer></script>
<script src="assets/js/gate.js?v=202609191018" defer></script>
</body>
</html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/kalender.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/kalender.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -377,11 +377,11 @@
</main>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/glocke.js?v=202609190208" defer></script>
<script src="assets/js/kalender.js?v=202609190208" defer></script>
<script src="assets/js/kalender-abo.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/glocke.js?v=202609191018" defer></script>
<script src="assets/js/kalender.js?v=202609191018" defer></script>
<script src="assets/js/kalender-abo.js?v=202609191018" defer></script>
</body>
</html>
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/leistung.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/leistung.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -444,10 +444,10 @@
</form>
</dialog>
<script src="assets/js/wahl.js?v=202609190208"></script>
<script src="assets/js/bereiche.js?v=202609190208"></script>
<script src="assets/js/kopf.js?v=202609190208"></script>
<script src="assets/js/glocke.js?v=202609190208"></script>
<script src="assets/js/leistung.js?v=202609190208"></script>
<script src="assets/js/wahl.js?v=202609191018"></script>
<script src="assets/js/bereiche.js?v=202609191018"></script>
<script src="assets/js/kopf.js?v=202609191018"></script>
<script src="assets/js/glocke.js?v=202609191018"></script>
<script src="assets/js/leistung.js?v=202609191018"></script>
</body>
</html>
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/personen.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/personen.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -423,10 +423,10 @@
</main>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/glocke.js?v=202609190208" defer></script>
<script src="assets/js/personen.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/glocke.js?v=202609191018" defer></script>
<script src="assets/js/personen.js?v=202609191018" defer></script>
</body>
</html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -214,11 +214,11 @@
</main>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/glocke.js?v=202609190208" defer></script>
<script src="assets/js/steckbrief.js?v=202609190208" defer></script>
<script src="assets/js/profil.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/glocke.js?v=202609191018" defer></script>
<script src="assets/js/steckbrief.js?v=202609191018" defer></script>
<script src="assets/js/profil.js?v=202609191018" defer></script>
</body>
</html>
+12 -12
View File
@@ -7,14 +7,14 @@
<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=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -134,9 +134,9 @@
Beim ersten Anlauf fehlte die Zeile auf genau diesen beiden neuen
Seiten. Gefunden hat das nicht das Auge, sondern pruef-css-klassen,
die seit dem 07.09. jede Seite danach absucht. -->
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/rechte.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/rechte.js?v=202609191018" defer></script>
</body>
</html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/report.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/report.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -106,11 +106,11 @@
</main>
<script src="assets/js/ki.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/glocke.js?v=202609190208" defer></script>
<script src="assets/js/report.js?v=202609190208" defer></script>
<script src="assets/js/ki.js?v=202609191018" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/glocke.js?v=202609191018" defer></script>
<script src="assets/js/report.js?v=202609191018" defer></script>
</body>
</html>
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/scouting.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/scouting.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -173,10 +173,10 @@
</div>
</div>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/glocke.js?v=202609190208" defer></script>
<script src="assets/js/scouting.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/glocke.js?v=202609191018" defer></script>
<script src="assets/js/scouting.js?v=202609191018" defer></script>
</body>
</html>
+12 -12
View File
@@ -12,21 +12,21 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<!-- Nur die Startseite: Begrüßungskachel, Uhr, Glockenmulde. Siehe
den Hinweis in start.css -- dort stand es bis zum 07.09.2026. -->
<link rel="stylesheet" href="assets/css/heim.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/heim.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -678,10 +678,10 @@
</p>
</main>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/glocke.js?v=202609190208" defer></script>
<script src="assets/js/start.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/glocke.js?v=202609191018" defer></script>
<script src="assets/js/start.js?v=202609191018" defer></script>
</body>
</html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/startcheck.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/startcheck.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -85,11 +85,11 @@
</main>
<script src="assets/js/ki.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/glocke.js?v=202609190208" defer></script>
<script src="assets/js/startcheck.js?v=202609190208" defer></script>
<script src="assets/js/ki.js?v=202609191018" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/glocke.js?v=202609191018" defer></script>
<script src="assets/js/startcheck.js?v=202609191018" defer></script>
</body>
</html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -150,11 +150,11 @@
</main>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/auskunft.js?v=202609190208" defer></script>
<script src="assets/js/glocke.js?v=202609190208" defer></script>
<script src="assets/js/steckbrief.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/auskunft.js?v=202609191018" defer></script>
<script src="assets/js/glocke.js?v=202609191018" defer></script>
<script src="assets/js/steckbrief.js?v=202609191018" defer></script>
</body>
</html>
+13 -13
View File
@@ -7,18 +7,18 @@
<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=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609191018" />
<!-- Wegen des Kastens „Anfragen“ ueber der Liste. Eine eigene Kopie
der Regel hier waere die zweite Stelle, an der dieselbe Sache
anders aussieht. -->
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -111,9 +111,9 @@
</main>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/talente.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/talente.js?v=202609191018" defer></script>
</body>
</html>
+13 -13
View File
@@ -7,20 +7,20 @@
<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=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/team.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/team.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -110,10 +110,10 @@
</main>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/glocke.js?v=202609190208" defer></script>
<script src="assets/js/team.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/glocke.js?v=202609191018" defer></script>
<script src="assets/js/team.js?v=202609191018" defer></script>
</body>
</html>
+12 -12
View File
@@ -7,20 +7,20 @@
<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=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/team.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/team.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<!-- `data-buehne` steht hier fest und nicht im Skript: Die Kachel zu
@@ -84,9 +84,9 @@
Kaputt ist dabei nichts -- er funktioniert. Genau deshalb faellt
es niemandem auf, und deshalb prueft pruef-css-klassen die
ABHAENGIGKEIT statt der Optik. Hier hat sie angeschlagen. -->
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/teamlage.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/teamlage.js?v=202609191018" defer></script>
</body>
</html>
+11 -11
View File
@@ -7,13 +7,13 @@
<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=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -112,9 +112,9 @@
</main>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/teilen.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/teilen.js?v=202609191018" defer></script>
</body>
</html>
+12 -12
View File
@@ -7,14 +7,14 @@
<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=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -126,9 +126,9 @@
</main>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/treff-moderation.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/treff-moderation.js?v=202609191018" defer></script>
</body>
</html>
+13 -13
View File
@@ -7,18 +7,18 @@
<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=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein. Auf der Adresse des
Treffs biegt der Server genau diesen Dateinamen auf crew-haus.css
um, und die Seite kommt schon beim ERSTEN Abruf in Lila und
Babyblau an -- ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -221,10 +221,10 @@
Beim ersten Anlauf fehlte die Zeile auf genau diesen beiden neuen
Seiten. Gefunden hat das nicht das Auge, sondern pruef-css-klassen,
die seit dem 07.09. jede Seite danach absucht. -->
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/auskunft.js?v=202609190208" defer></script>
<script src="assets/js/treff-regeln.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/auskunft.js?v=202609191018" defer></script>
<script src="assets/js/treff-regeln.js?v=202609191018" defer></script>
</body>
</html>
+13 -13
View File
@@ -12,22 +12,22 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<!-- Formular- und Dialogstile: seit dem 07.09.2026 steht hier der
Dialog "Creator anlegen". -->
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/uebersicht.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/uebersicht.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -149,11 +149,11 @@
</main>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/glocke.js?v=202609190208" defer></script>
<script src="assets/js/uebersicht.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/glocke.js?v=202609191018" defer></script>
<script src="assets/js/uebersicht.js?v=202609191018" defer></script>
</body>
</html>
+11 -11
View File
@@ -7,13 +7,13 @@
<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=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/unsere-seiten.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/unsere-seiten.css?v=202609191018" />
</head>
<body class="start">
@@ -217,9 +217,9 @@
</main>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/unsere-seiten.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/unsere-seiten.js?v=202609191018" defer></script>
</body>
</html>
+139
View File
@@ -0,0 +1,139 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>Entwicklung · Spicy &amp; 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=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/werdegang.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</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&nbsp;Dogi</a> · Entwicklung</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">
<!-- ==================================================================
ENTWICKLUNG — die Auswertung je Person (19.09.2026)
Filipe: „dan perfektionierst du eine neue wo diagramme texte und
so zu jedem modi gemacht wird und diese kategorie kannst du
entwicklung nenen."
DIESE SEITE HAT ZWEI GESICHTER, und welches man sieht, entscheidet
der Server:
Leitung -> die Karten aller, die eine haben
Ein Modi -> ausschliesslich die Bewegung in der EIGENEN Karte,
ohne Namen, ohne Anlass, ohne wer-hat-was
WAS HIER NICHT STEHT UND NIE STEHEN WIRD: eine Punktzahl, ein
Prozentwert, ein Durchschnitt, eine Rangliste, ein Vergleich
zwischen zwei Menschen. Eine Zahl ueber einen Menschen wird
gelesen, verglichen und weitererzaehlt — gebraucht wird ein
Gespraechsanlass.
============================================================== -->
<section class="kopf-zeile">
<div>
<p class="marke">Entwicklung &amp; Nachwuchs</p>
<h1 class="titel" id="titel">Entwicklung</h1>
<p class="unterzeile" id="unterzeile">
Wie sich jemand über die Zeit bewegt. Keine Note, kein Vergleich –
jede Karte nur mit sich selbst.
</p>
</div>
</section>
<p class="fehler" id="fehler" role="alert" aria-live="polite"></p>
<!-- ---- Die Liste (nur Leitung) ---------------------------------- -->
<section id="liste-block" hidden>
<p class="w-hinweis" id="liste-hinweis"></p>
<div id="liste" class="w-liste" aria-busy="true">
<p class="leise">wird geladen …</p>
</div>
</section>
<!-- ---- Eine Karte (nur Leitung) --------------------------------- -->
<section id="karte-block" hidden>
<button type="button" class="knopf knopf--still knopf--klein" id="zur-liste">
← Alle Karten
</button>
<h2 class="w-name" id="karte-name">…</h2>
<p class="w-satz" id="karte-satz">…</p>
<!-- Diagramm 1: Bewegung. Zwei Richtungen um eine Nulllinie —
vorwaerts und rueckwaerts sind dieselbe Groesse mit
verschiedenem Vorzeichen, nicht zwei Themen. -->
<section class="w-teil" id="bewegung-block">
<h3 class="e-abschnitt">Was sich bewegt hat</h3>
<p class="w-teil__satz" id="bewegung-satz"></p>
<div id="bewegung"></div>
</section>
<!-- Diagramm 2: der Streifen. Eine Zeile je Punkt, eine Spalte je
Monat. Das Zeichen sagt die Stufe, die Farbe verstaerkt nur. -->
<section class="w-teil" id="streifen-block">
<h3 class="e-abschnitt">Punkt für Punkt, Monat für Monat</h3>
<p class="w-teil__satz" id="streifen-satz"></p>
<div id="streifen" class="w-streifen"></div>
</section>
<!-- Die Texte. Reihenfolge ist Absicht: erst was sich bewegt hat,
dann was traegt, erst danach was liegt. -->
<section class="w-teil" id="texte-block">
<h3 class="e-abschnitt">Was dazu zu sagen ist</h3>
<div id="texte"></div>
</section>
<p class="w-fuss">
Auf dieser Seite gibt es keine Punktzahl und keinen Vergleich zwischen
Personen. Was hier steht, ist ein Gesprächsanlass – kein Zeugnis.
Einschätzungen setzt und ändert man im Katalog:
<a href="entwicklung.html">Eure Aufgaben</a>.
</p>
</section>
<!-- ---- Die eigene Sicht (Modi) ---------------------------------- -->
<section id="mich-block" hidden>
<h2 class="e-abschnitt">Deine Entwicklung</h2>
<p class="w-satz" id="mich-satz">…</p>
<div id="mich-bewegung"></div>
<p class="w-fuss" id="mich-hinweis"></p>
</section>
</main>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/werdegang.js?v=202609191018" defer></script>
</body>
</html>
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190208" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190208" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/wissen.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190208" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191018" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191018" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/wissen.css?v=202609191018" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191018" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190208" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191018" />
</head>
<body class="start">
@@ -190,10 +190,10 @@
</main>
<script src="assets/js/wahl.js?v=202609190208" defer></script>
<script src="assets/js/bereiche.js?v=202609190208" defer></script>
<script src="assets/js/kopf.js?v=202609190208" defer></script>
<script src="assets/js/glocke.js?v=202609190208" defer></script>
<script src="assets/js/wissen.js?v=202609190208" defer></script>
<script src="assets/js/wahl.js?v=202609191018" defer></script>
<script src="assets/js/bereiche.js?v=202609191018" defer></script>
<script src="assets/js/kopf.js?v=202609191018" defer></script>
<script src="assets/js/glocke.js?v=202609191018" defer></script>
<script src="assets/js/wissen.js?v=202609191018" defer></script>
</body>
</html>