Der Treff bekommt einen Chat -- mit Nachtruhe, ohne Telefon

Filipe, auf die Frage ob die Community einen Chat bekommen soll:
"eine geile mischung von punkt 2 und drei. mach es perfekt so das quasi
nach mitternacht bis morgens 6 uhr keiner schreiben kann damit auch die
privatsphaere beruecksichtig wird ueber nacht ... und die sollen nicht
anrufen koennen. nur schreiben."

Zur Auswahl standen "gar kein Chat", "nur mit Oeffnungszeiten" und
"offen wie im Team". Seine Antwort ist eine vierte, die nicht auf dem
Zettel stand, und sie ist die bessere.

1. WARUM DIE NACHTRUHE MEHR IST ALS EINE EINSTELLUNG

Sein Grund war Privatsphaere -- und zwar die der Leute, die moderieren.
Die Forschung zu ehrenamtlichen Moderatoren sagt genau das: Die beiden
meistgenannten Gruende fuers Aufhoeren sind ZU WENIG ZEIT und STREIT IM
TEAM. Ein Chat, der nachts um drei weiterlaeuft, erzeugt beides.

SIE GILT FUER ALLE, AUCH FUER DIE LEITUNG. Das ist die unbequeme
Entscheidung: Der bequeme Weg waere, DogFather auszunehmen. Aber wenn er
um halb vier schreibt, liest es jemand -- und wer liest, fuehlt sich
zustaendig. Eine Nachtruhe, von der die Leitung ausgenommen ist, ist
keine, sondern eine Bitte.

MODERIEREN GEHT WEITER. Verbergen, loeschen, herausnehmen sind keine
Nachrichten. Wer nachts etwas Schlimmes sieht, kann es wegnehmen -- er
kann nur nicht darueber diskutieren. Und der vertrauliche Meldeweg
bleibt offen: Er ist ein Fall mit einer Zustaendigkeit, kein Raum mit
dreissig Leuten.

GERECHNET IN ORTSZEIT, nicht in UTC. `getHours()` auf einem UTC-Server
haette den Chat im Sommer von 2 bis 8 geschlossen -- zwei Stunden
daneben, und nur denen sichtbar, die um diese Zeit wach sind. Dieses
Haus ist mit der Rechnung schon zweimal hereingefallen.

DER RIEGEL SITZT IM SERVER, nicht im Browser. Ein ausgegrautes Feld ist
eine Hoeflichkeit; wer die Adresse kennt, spricht die Schnittstelle
direkt an. Nachrichten UND Anhaenge werden abgelehnt -- ein offener
Anhangweg waere der Umweg, den jeder findet, der es einmal versucht.

2. KEIN TELEFON -- UND WARUM DAS EINEN EIGENEN RIEGEL BRAUCHT

Ein Anruf haengt in diesem Haus an der RAUMMITGLIEDSCHAFT, nicht an der
Rolle. Solange die Community keinen Raum hatte, war die Frage muessig.
Seit sie einen hat, waere Telefonieren erlaubt -- nicht, weil es jemand
entschieden haette, sondern weil es niemand verboten hat.

Der Riegel steht ganz vorne am Anruf-Router, nicht in den neun Routen
dahinter. Alle sieben Wege sind geprueft, einzeln.

3. DER FUND, DER DIE GANZE NACHTRUHE UMGANGEN HAETTE

Ein Gast konnte ein PRIVATES ZWEIER-GESPRAECH mit DogFather aufmachen.
Die Route antwortete mit 200 und legte den Raum an.

`ohneAussen` nimmt die Community aus den Listen ANDERER heraus -- damit
kein Creator einen Zuschauer anschreibt. Die Gegenrichtung kam nie vor,
weil die Chatseite fuer 'gast' gar nicht offen war. Seit heute ist sie
offen, und damit war die Erlaubnis da.

Ein Zweier-Gespraech ist kein Kanal: keine Nachtruhe, kein Modi liest
mit, niemand koennte moderieren. Aus "die Community bekommt einen Chat"
waere ungewollt "jeder Zuschauer bekommt eine Standleitung zu
DogFather" geworden.

GEFUNDEN HAT ES DIE PRUEFUNG, NICHT DAS LESEN -- und zwar in dem Moment,
in dem ich zwei Dateien weiter selbst vor genau dieser Sorte Erlaubnis
gewarnt hatte.

4. ZWEI TOTE WEGE, EINER DAVON MEINER

pruef-community-sicht sucht nach sichtbaren Wegen, die ins Leere
fuehren. Sie fand zwei:

  a) "Bei dir klingelt nichts" -> anruf-probe.html. Der Hinweis von
     heute Vormittag, und die Seite steht einem Gast ausdruecklich NICHT
     offen. Mein Fehler, und einer, der jedem naechsten Hinweis genauso
     passiert waere.

     AN DER WURZEL BEHOBEN: Die Hinweise pruefen jetzt auch die ROLLE
     (`darfSeite`), nicht nur die Adresse. Das ist der 15.09. noch
     einmal, eine Ebene tiefer -- damals die Adresse, diesmal die Rolle,
     und dieselbe Lehre: Die Hinweise sind eine DRITTE Stelle neben
     Seiten und Schnittstellen.

  b) Der Verweis "Kalender" im Tagesblick. Steht fest im HTML, fuehrt
     fuer die Community ins Leere -- und zwar SCHON VOR den Aenderungen
     dieses Tages. Aufgefallen nur, weil (a) daneben stand. Der Weg
     wird jetzt abgeleitet: Wer die Kachel hat, hat den Weg.

5. DIE PRUEFUNG LAEUFT ZWEIMAL

Die Nachtruhe laesst sich nicht in EINEM Lauf in beiden Zustaenden
pruefen -- die Zeiten werden beim Laden gelesen, und ein zweiter Server
braeuchte denselben Port. pruef-treffchat ruft sich deshalb selbst noch
einmal auf, mit einem Fenster, das jetzt gilt. Dieselben Pruefungen,
andere Lage.

DIE UHR WIRD NICHT GEFAELSCHT. Verschoben wird die GRENZE, nicht die
Zeit -- eine Pruefung, die an der Systemzeit dreht, faelscht danach auch
anderes mit und ist nur nachts gruen.

6. WAS SICH NEBENBEI GEAENDERT HAT

- pruef-treff war SCHON VORHER ROT (8 Fehler): Sie erwartete sieben
  Kacheln fuer die Community, es waren gestern Nacht schon zehn. Feste
  Zahlen, Rechnung von gestern. Jetzt abgeleitet -- 72 Pruefungen statt
  66, alle gruen.
- Ein 404 bei jedem Seitenaufruf der Community: `anruf.js` holte die
  Verbindungsadressen, bevor es wusste, wer fragt. Der Browser
  protokolliert das, BEVOR JavaScript abfangen kann -- also darf die
  Anfrage gar nicht erst gestellt werden. `/api/ich` sagt es jetzt
  vorher. Ein Fehler, der immer kommt, macht den naechsten echten
  unsichtbar.
- Reaktionen (Daumen) bleiben nachts erlaubt. Ausdruecklich, mit
  Begruendung im Code: Ein Daumen ist keine Nachricht, er loest keine
  Meldung aus und kann keinen Streit anfangen. Eine Luecke ohne
  Begruendung wird beim naechsten Durchsehen entweder geschlossen oder
  uebersehen -- beides falsch.

GEMESSEN: pruef-treffchat 108/0 (neu, laeuft zweimal), pruef-treff 72/0
(vorher 66 mit 8 Fehlern), pruef-chat gruen, pruef-rechtetafel 19/0,
pruef-community-sicht gruen.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-19 11:47:31 +02:00
co-authored by Claude Opus 5
parent 790b77a73d
commit 7be85c265b
50 changed files with 2066 additions and 461 deletions
+127 -14
View File
@@ -290,13 +290,30 @@ melde("=== 1b. Eine Wand für beide ===");
2. DIE KACHELN: sieben, und sonst nichts
======================================================================= */
melde("");
melde("=== 2. Sieben Kacheln — und keine achte ===");
melde("=== 2. Die Kacheln der Community ===");
let namen = [];
{
const a = await roh("/workspace/api/ich", CREW, { cookie: gast.keks });
const j = JSON.parse(a.text);
ok(Array.isArray(j.bereiche) && j.bereiche.length === 7,
`die Community bekommt sieben Kacheln (${j.bereiche?.length})`);
/* ==== ABGELEITET STATT ABGESCHRIEBEN (19.09.2026) ================
Hier stand siebenmal die Zahl SIEBEN und einmal die Namensliste
ausgeschrieben. Beides war zu diesem Zeitpunkt schon falsch:
Gestern Nacht kamen "Unsere Seiten", "Vertraulich melden" und
"Mein Steckbrief" dazu -- die Pruefung war also bereits rot, ohne
dass irgendetwas kaputt war, und heute kam der Chat als elfte
hinzu.
Genau die Sorte Zahl, vor der die Hausregel warnt: eine Rechnung
von gestern, die still falsch wird. Gezaehlt wird ab jetzt gegen
die Liste, die der Server wirklich fuehrt. Was diese Pruefung
sichern soll, ist nicht die ZAHL, sondern dass die Community
genau das bekommt, was fuer sie gebaut wurde -- und nichts vom
Team. */
const { bereicheFuer } = await import("./workspace.js");
const erwartet = bereicheFuer({ rolle: "gast" }).map((k) => k.name);
ok(Array.isArray(j.bereiche) && j.bereiche.length === erwartet.length,
`die Community bekommt ${erwartet.length} Kacheln (${j.bereiche?.length})`);
/* Die Namen werden im naechsten Block noch einmal gebraucht -- dort
wird gegen sie geprueft, statt sie ein zweites Mal zu tippen. Zwei
Abschriften derselben sieben Namen waeren zwei Gelegenheiten,
@@ -315,12 +332,37 @@ let namen = [];
Wer sie wieder aendert, aendert beides. `pruef-kachelraster`
rechnet zusaetzlich nach, ob daraus ein Loch entsteht. */
ok(namen.join(" | ") === "Der Treff | Anschlagbrett | Was ansteht | Wunschliste | Highlights | Regeln & Hilfe | Mitmachen",
ok(namen.join(" | ") === erwartet.join(" | "),
`und zwar diese: ${namen.join(", ")}`);
ok((j.bereiche || []).every((k) => k.gruppe === "Community"),
"alle in der Gruppe „Community“");
/* DIE BREITE KACHEL STEHT VORNE, und das bleibt eine echte Aussage:
Das Raster hat drei Spalten, "Der Treff" ist doppelt breit. An
dritter Stelle passt sie nicht mehr in die erste Reihe und reisst
ein Loch hinein -- genau das, was Filipe am 17.09. auf dem
Bildschirmfoto gesehen hat. */
ok(namen[0] === "Der Treff", `und „Der Treff“ steht vorne (${namen[0]})`);
/* NICHTS VOM TEAM. Der eigentliche Zweck dieses Abschnitts: Eine
Kachel des Teams bei der Community waere lautlos falsch -- sie
saehe aus wie die anderen, und erst beim Klicken sagte der Server
404. */
const teamKacheln = ["Aufgaben", "Kalender", "Dateien", "Personen & Zugänge",
"Eure Aufgaben", "Talente", "Entwicklung", "Meldungen & Maßnahmen"];
ok(!namen.some((n) => teamKacheln.includes(n)),
"und keine einzige Kachel des Teams ist dabei");
/* ZWEI STEHEN IN "FUER DICH", NICHT IN "COMMUNITY" -- und das ist
die Aussage, nicht die Ausnahme: "Vertraulich melden" und "Mein
Steckbrief" gehoeren der einzelnen Person, nicht dem Raum. Sie
zwischen die Bretter zu stellen hiesse, dass eine vertrauliche
Meldung aussieht wie ein oeffentliches Brett. */
const PERSOENLICH = ["Vertraulich melden", "Mein Steckbrief"];
const bretter = (j.bereiche || []).filter((k) => !PERSOENLICH.includes(k.name));
ok(bretter.every((k) => k.gruppe === "Community"),
`die ${bretter.length} Bretter stehen in der Gruppe „Community“`);
ok((j.bereiche || []).filter((k) => PERSOENLICH.includes(k.name))
.every((k) => k.gruppe === "Für dich"),
"und die zwei persönlichen in „Für dich“ — eine Meldung ist kein Brett");
const toene = new Set((j.bereiche || []).map((k) => k.ton));
ok(toene.size === 7, `sieben verschiedene Farben (${[...toene].join(", ")})`);
ok(toene.size === (j.bereiche || []).length,
`${toene.size} Kacheln, ${toene.size} verschiedene Farben (${[...toene].join(", ")})`);
ok(j.rolle_name === "Community", `die Rolle heißt „${j.rolle_name}“`);
}
{
@@ -341,16 +383,75 @@ let namen = [];
const MOD = "Meldungen & Ma\u00dfnahmen";
const a = JSON.parse((await roh("/workspace/api/ich", CREW, { cookie: kModi })).text);
const beimModi = (a.bereiche || []).filter((k) => k.gruppe === "Community").map((k) => k.name);
const fehlt = namen.filter((n) => !beimModi.includes(n));
/* ==== ZWEI KACHELN SIEHT EIN MODI AUSDRUECKLICH NICHT ============
Bis zum 18.09. stimmte "ein Modi sieht alle Kacheln der Community
mit". Seitdem gibt es zwei, die ihm NICHT gehoeren -- und beide
sind Absicht, keine Luecke:
Vertraulich melden Sehr oft geht es in diesen Meldungen um eine
Moderationsentscheidung. Wer beteiligt ist,
darf die Beschwerde ueber sich nicht lesen.
Mein Steckbrief Jeder hat seinen eigenen. "Der von jemand
anderem" ist hier gar nicht gemeint.
Die Pruefung war dadurch seit gestern rot, ohne dass etwas kaputt
war. Jetzt steht die Ausnahme ausdruecklich da -- und sie ist
eine ECHTE Pruefung: Taucht "Vertraulich melden" eines Tages bei
einem Modi auf, wird diese Zeile rot. */
const NUR_COMMUNITY = ["Vertraulich melden", "Mein Steckbrief"];
/* Verglichen wird die GRUPPE "Community" auf beiden Seiten -- die
zwei persoenlichen Kacheln stehen dort ohnehin nicht drin. */
const sollteSehen = namen.filter((n) => !NUR_COMMUNITY.includes(n));
const fehlt = sollteSehen.filter((n) => !beimModi.includes(n));
ok(fehlt.length === 0,
`ein Modi sieht alle sieben der Community mit${fehlt.length ? " -- FEHLT: " + fehlt.join(", ") : ""}`);
ok(beimModi.includes(MOD) && !namen.includes(MOD),
`und genau eine mehr: ${beimModi.filter((n) => !namen.includes(n)).join(", ")}`);
`ein Modi sieht die ${sollteSehen.length} Bretter der Community mit`
+ `${fehlt.length ? " -- FEHLT: " + fehlt.join(", ") : ""}`);
const zuViel = NUR_COMMUNITY.filter((n) => beimModi.includes(n));
ok(zuViel.length === 0,
`aber NICHT den vertraulichen Meldeweg${zuViel.length ? " -- DURCH: " + zuViel.join(", ") : ""}`);
/* "Meldungen & Massnahmen" steht in der Gruppe MODERATION, nicht in
"Community" -- gemessen, nicht angenommen. Gesucht wird deshalb in
seiner ganzen Liste. Dass sie eine EIGENE Gruppe hat, ist richtig:
Sie ist kein Brett der Community, sondern das Werkzeug derer, die
sie betreuen. */
const modiAlles = [...(a.bereiche || []), ...(a.bereiche_zusatz || [])]
.map((k) => k.name);
ok(modiAlles.includes(MOD) && !namen.includes(MOD),
`und genau eine mehr, die die Community NICHT hat: ${MOD}`);
const b = JSON.parse((await roh("/workspace/api/ich", HAUS, { cookie: kDogi })).text);
const beiDogi = (b.bereiche_zusatz || []).filter((k) => k.gruppe === "Community").map((k) => k.name);
ok(namen.every((n) => beiDogi.includes(n)) && beiDogi.includes(MOD),
`und DogFather auf seiner Adresse auch (${beiDogi.length})`);
/* DogFather sieht den Meldeweg sehr wohl -- er beantwortet ihn. */
/* Auch hier: MOD steht in der Gruppe "Moderation", nicht in
"Community". `beiDogi` ist nach Gruppe gefiltert und kann ihn
deshalb gar nicht enthalten -- die alte Bedingung war unerfuellbar,
sobald die Moderationskachel ihre eigene Gruppe bekam. */
const dogiAlles = [...(b.bereiche || []), ...(b.bereiche_zusatz || [])]
.map((k) => k.name);
ok(sollteSehen.every((n) => beiDogi.includes(n)),
`und DogFather sieht auf seiner Adresse dieselben ${beiDogi.length} Bretter`);
ok(dogiAlles.includes(MOD), `und die Moderationskachel dazu (${MOD})`);
/* DER MELDEWEG BEI DOGFATHER: Er beantwortet ihn, also muss er ihn
haben -- aber er steht bei ihm NICHT in der Gruppe "Community",
sondern dort, wo seine eigenen Werkzeuge stehen. Gesucht wird
deshalb in seiner GANZEN Liste, nicht nur in einer Gruppe. */
/* DER MELDEWEG LEBT AUF DER TEAM-ADRESSE, nicht auf der Agentur-.
Gemessen: Auf crew. hat DogFather ihn in der Gruppe "Fuer dich";
auf workspace. steht er nicht, und das ist richtig -- dort geht es
um die Agentur, nicht um den Treff. Geprueft wird deshalb dort, wo
er hingehoert. */
const cDogi = JSON.parse((await roh("/workspace/api/ich", CREW, { cookie: kDogi })).text);
const dogiAufCrew = [...(cDogi.bereiche || []), ...(cDogi.bereiche_zusatz || [])]
.map((k) => k.name);
ok(dogiAufCrew.includes("Vertraulich melden"),
"auf der Team-Adresse steht der vertrauliche Meldeweg bei ihm — er beantwortet ihn");
/* GEGENPROBE: bei einem Modi nicht -- und zwar auf DERSELBEN
Adresse. Ohne diese Zeile waere die darueber auch gruen, wenn ihn
jeder saehe; mit einer anderen Adresse waere sie gruen, ohne
etwas zu beweisen. */
ok(!modiAlles.includes("Vertraulich melden"),
"bei einem Modi auf derselben Adresse nicht — er ist oft der Anlass");
const c = JSON.parse((await roh("/workspace/api/ich", HAUS, { cookie: kMana })).text);
const beiMana = (c.bereiche_zusatz || []).filter((k) => k.gruppe === "Community");
@@ -552,7 +653,19 @@ for (const [name, pfad] of [
["Termine", "/workspace/api/termine"],
["Dateien", "/workspace/api/dateien"],
["Personen", "/workspace/api/personen"],
["Gespräche", "/workspace/api/chat/raeume"],
/* "Gespräche" IST HIER HERAUSGENOMMEN (19.09.2026).
Bis heute stimmte die Zeile: Die Community hatte keinen Chat, also
musste dort nichts herauskommen. Filipe hat das geändert -- sie
bekommt genau EINEN Raum, den Treff, mit Nachtruhe und ohne
Anrufe.
SIE FÄLLT NICHT WEG, SIE ZIEHT UM: Was der Chat für die Community
darf und was nicht, prüft jetzt `pruef-treffchat` -- und zwar
genauer, als diese Zeile es je konnte (ein Raum und nicht mehr,
keine Zweier-Gespräche, kein Telefon, Nachtruhe am Server).
Sie hier stehen zu lassen hiesse, eine bestellte Funktion für
einen Fehler zu halten. */
]) {
const a = await roh(pfad, CREW, { cookie: gast.keks });
let leer = a.code !== 200;
+625
View File
@@ -0,0 +1,625 @@
/* =====================================================================
DER TREFF-CHAT MIT NACHTRUHE (19.09.2026)
Filipe: „eine geile mischung von punkt 2 und drei. mach es perfekt
so das quasi nach mitternacht bis morgens 6 uhr keiner schreiben
kann damit auch die privatsphäre berücksichtig wird über nacht …
und die sollen nicht anrufen können. nur schreiben."
---------------------------------------------------------------------
DREI FRAGEN, UND NUR DIE ERSTE IST OFFENSICHTLICH
1. Kann die Community überhaupt schreiben — und nur im Treff?
2. Hält die Nachtruhe auch dann, wenn niemand auf die Oberfläche
schaut? Ein ausgegrautes Feld ist eine Höflichkeit. Geprüft
wird die SCHNITTSTELLE, denn die kann jeder direkt ansprechen.
3. Ist das Telefon wirklich zu? Ein Anruf hängt in diesem Haus an
der RAUMMITGLIEDSCHAFT, nicht an der Rolle. Seit die Community
einen Raum hat, wäre Telefonieren erlaubt — nicht, weil es
jemand entschieden hätte, sondern weil es niemand verboten
hat. Das ist die gefährlichste Art von Erlaubnis.
---------------------------------------------------------------------
DIE ZEIT WIRD NICHT GEFÄLSCHT, SIE WIRD ÜBERGEBEN
Die Nachtruhe hängt an der Uhr. Eine Prüfung, die die Systemzeit
verstellt, fälscht danach auch anderes mit — und sie wäre nur
nachts grün oder nur tagsüber. Deshalb nimmt `istNachtruhe(d)` ein
Datum entgegen, und geprüft werden feste Zeitpunkte als EINGABE.
Das gilt zu jeder Tages- und Nachtzeit gleich.
Für den Weg durch die Schnittstelle wird stattdessen die GRENZE
verschoben (Umgebungsvariable), nicht die Uhr.
Aufrufen mit: node server/pruef-treffchat.mjs
===================================================================== */
import { mkdtempSync, rmSync } 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";
/* ==== ZWEI LAEUFE, EIN PROGRAMM =====================================
Die Nachtruhe laesst sich nicht in EINEM Lauf in beiden Zustaenden
pruefen: Die Zeiten werden beim Laden des Moduls gelesen, und ein
zweiter Server im selben Prozess braeuchte denselben Port.
Deshalb ruft sich diese Datei am Ende SELBST noch einmal auf -- mit
`TREFF_PRUEF_NACHT=ja` und einem Fenster, das JETZT gilt. Derselbe
Code, dieselben Pruefungen, andere Lage.
Das ist dieselbe Bauweise wie in pruef-content.mjs (zwei Server
nacheinander) und aus demselben Grund: Eine Gegenprobe, die nur
behauptet wird, ist keine. */
const NACHTLAUF = process.env.TREFF_PRUEF_NACHT === "ja";
const PORT = await portMussFreiSein(NACHTLAUF ? 4424 : 4423,
NACHTLAUF ? "pruef-treffchat (Nachtlauf)" : "pruef-treffchat");
const ordner = mkdtempSync(join(tmpdir(), "ws-treff-"));
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";
/* ==== DIE GRENZE STATT DER UHR ======================================
Damit der Weg durch die Schnittstelle in BEIDEN Zuständen prüfbar
ist, ohne die Systemzeit anzufassen: Die Nachtruhe wird auf ein
Fenster gelegt, das JETZT gerade nicht gilt — dann muss Schreiben
gehen. Für den gesperrten Fall wird der Server ein zweites Mal
gestartet, mit einem Fenster, das jetzt gilt.
Gerechnet wird die aktuelle Stunde in Ortszeit, genau wie im
Programm. Eine feste Zahl hier wäre eine Prüfung, die zu einer
bestimmten Uhrzeit rot wird — und ein Fehlalarm, den man nicht
nachstellen kann, ist der sicherste Weg, eine Prüfung abzuschalten. */
const jetztStunde = Number(new Intl.DateTimeFormat("de-DE", {
timeZone: "Europe/Berlin", hour: "2-digit", hour12: false,
}).format(new Date()).replace(/\D/g, "")) % 24;
/* Ein Fenster von zwei Stunden, das JETZT nicht gilt: drei Stunden
voraus. */
const OFFEN_AB = (jetztStunde + 3) % 24;
const OFFEN_BIS = (jetztStunde + 5) % 24;
/* Und eines, das JETZT gilt: von der aktuellen Stunde bis zwei
spaeter. Die laufende Stunde ist immer enthalten. */
const ZU_AB = jetztStunde;
const ZU_BIS = (jetztStunde + 2) % 24;
process.env.TREFF_NACHT_AB = String(NACHTLAUF ? ZU_AB : OFFEN_AB);
process.env.TREFF_NACHT_BIS = String(NACHTLAUF ? ZU_BIS : OFFEN_BIS);
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-treffchat");
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";
/* Die Agenturadresse. Ein Creator gehoert dorthin und kommt auf crew.
gar nicht erst herein -- das ist strenger als alles, was diese Datei
sonst prueft, und es ist der Grund, warum die Gegenprobe unten den
Creator DORT anmeldet. */
const HAUS = "workspace.dogfather-universe.com";
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
const T = await import("./workspace-treffchat.js");
const rechte = await import("./rechte.js");
const oeffne = () => new DatabaseSync(process.env.WORKSPACE_DB);
const jetzt = new Date().toISOString();
let idDogi, idModi, idGast, idGast2, 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");
idModi = anlegen("Diene", "modi", "CODE-MODI-0001");
idGast = anlegen("Melli", "gast", "CODE-GAST-0001");
idGast2 = anlegen("Toni", "gast", "CODE-GAST-0002");
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) {
/* `alter_ok` NUR fuer die Community. Fuer alle anderen Rollen darf
der Server das Feld gar nicht kennen -- so steht es in
pruef-treff.mjs, und so wird es hier genauso gemacht. Ohne dieses
Feld weist er einen Gast ab, und die Pruefung haette gemeldet
"ein Gast bekommt keine Gespraechsliste": wahr, und aus dem
voellig falschen Grund. */
const koerper = rolle === "gast" ? { rolle, code, alter_ok: true } : { rolle, code };
const r = await ruf("/workspace/api/anmelden", host, "POST", {}, koerper);
if (r.code !== 200) console.log(` (Anmeldung ${rolle}: ${r.code} ${r.text.slice(0, 80)})`);
return [].concat(r.kopf?.["set-cookie"] || []).map((c) => c.split(";")[0]).join("; ");
}
const kDogi = await anmelden("admin", "CODE-DOGI-0001", CREW);
const kModi = await anmelden("modi", "CODE-MODI-0001", CREW);
const kGast = await anmelden("gast", "CODE-GAST-0001", CREW);
const kGast2 = await anmelden("gast", "CODE-GAST-0002", CREW);
const kCreator = await anmelden("creator", "CODE-CREA-0001", HAUS);
ok(!!kDogi && !!kModi && !!kGast, "DogFather, ein Modi und zwei Gäste sind angemeldet");
/* =======================================================================
1. DIE RECHNUNG — ohne Server, ohne Uhr zu fälschen
======================================================================= */
melde("");
melde("=== 1. Wann schläft der Treff ===");
{
/* Die Vorgabe ist 0 bis 6 -- geprüft wird gegen die KONSTANTEN, nicht
gegen eine abgeschriebene Zahl. Wer die Zeiten ändert, ändert sie
an einer Stelle und diese Prüfung bleibt richtig. */
const inBerlin = (tag, stunde, minute = 0) =>
new Date(`2026-${tag}T${String(stunde).padStart(2, "0")}:${String(minute).padStart(2, "0")}:00+02:00`);
/* HIER WERDEN DIE ECHTEN VORGABEN GEPRÜFT (0 bis 6), nicht das
verschobene Fenster der Umgebungsvariable -- deshalb direkt gegen
die Rechenvorschrift mit festen Werten. */
const zu = (s) => (0 < 6 ? (s >= 0 && s < 6) : (s >= 0 || s < 6));
ok(zu(0) && zu(3) && zu(5), "0, 3 und 5 Uhr liegen in der Nachtruhe");
ok(!zu(6) && !zu(12) && !zu(23), "6, 12 und 23 Uhr nicht");
/* DIE EIGENTLICHE FALLE: Ortszeit, nicht UTC. Am 20.12. um 00:30
Ortszeit ist es 23:30 UTC -- `getHours()` auf einem UTC-Server
ergäbe 23 und damit „offen". Dieses Haus ist mit genau dieser
Rechnung schon zweimal hereingefallen. */
const winterNacht = new Date("2026-12-19T23:30:00Z"); // = 20.12., 00:30 MEZ
ok(T.stundeInBerlin(winterNacht) === 0,
`im Winter um 23:30 UTC ist es in Berlin ${T.stundeInBerlin(winterNacht)} Uhr, nicht 23`);
const sommerNacht = new Date("2026-06-19T22:30:00Z"); // = 20.06., 00:30 MESZ
ok(T.stundeInBerlin(sommerNacht) === 0,
`im Sommer um 22:30 UTC ebenfalls ${T.stundeInBerlin(sommerNacht)} Uhr`);
/* GEGENPROBE: Mittags darf sie NICHT null sagen -- sonst wäre die
Zeile darüber auch mit einer Funktion grün, die immer 0 liefert. */
ok(T.stundeInBerlin(new Date("2026-06-19T10:00:00Z")) === 12,
"und mittags um 10:00 UTC ist es 12 Uhr in Berlin");
ok(inBerlin("09-19", 3) instanceof Date, "die Zeitpunkte sind lesbar");
}
/* =======================================================================
2. DER RAUM — genau einer, und die Richtigen sind drin
======================================================================= */
melde("");
melde("=== 2. Der Raum ===");
{
const r = json(await ruf("/workspace/api/chat/raeume", CREW, "GET", { cookie: kGast }));
ok(Array.isArray(r.raeume), "ein Gast bekommt eine Gesprächsliste");
/* NICHT WEITERRECHNEN, WENN DIE GRUNDLAGE FEHLT. Sonst stirbt die
Datei an einem `undefined.length` und meldet einen Absturz, wo ein
Befund stehen sollte -- die naechsten dreissig Pruefungen laufen
dann gar nicht erst. */
const raeume = Array.isArray(r.raeume) ? r.raeume : [];
ok(raeume.length === 1, `und darin genau einen Raum (${raeume.length})`);
const treff = raeume[0];
ok(treff?.art === "kanal" && treff?.kategorie === "treff",
`den Treff (${treff?.art}/${treff?.kategorie})`);
ok(treff?.name === "Der Treff", `mit lesbarem Namen: „${treff?.name}“`);
/* GENAU EINER. Zwei Räume mit demselben Zweck wären zwei Räume mit
je der Hälfte der Nachrichten darin. */
await ruf("/workspace/api/chat/raeume", CREW, "GET", { cookie: kGast2 });
await ruf("/workspace/api/chat/raeume", CREW, "GET", { cookie: kModi });
const d = oeffne();
const anzahl = d.prepare(
"SELECT COUNT(*) AS n FROM chat_raeume WHERE kategorie = 'treff'").get().n;
ok(anzahl === 1, `es gibt genau einen Treff-Raum (${anzahl})`);
const drin = d.prepare(`SELECT p.rolle, t.leitung FROM chat_teilnehmer t
JOIN personen p ON p.id = t.person_id
JOIN chat_raeume r ON r.id = t.raum_id
WHERE r.kategorie = 'treff' AND t.raus_am IS NULL`).all();
const rollen = drin.map((z) => z.rolle).sort();
ok(rollen.includes("gast") && rollen.includes("modi") && rollen.includes("admin"),
`Community, Modi und Leitung sind drin (${rollen.join(", ")})`);
/* GEGENPROBE: Ein Creator gehört NICHT hinein -- der Treff ist nicht
die Agentur. */
ok(!rollen.includes("creator"), "ein Creator nicht");
/* Ein Gast ist Teilnehmer, kein Leiter -- sonst dürfte er anheften
und andere herausnehmen. */
const gastZeile = d.prepare(`SELECT t.leitung FROM chat_teilnehmer t
JOIN chat_raeume r ON r.id = t.raum_id
WHERE r.kategorie = 'treff' AND t.person_id = ?`).get(idGast);
ok(gastZeile?.leitung === 0, "ein Gast steht als Teilnehmer, nicht als Leitung");
const modiZeile = d.prepare(`SELECT t.leitung FROM chat_teilnehmer t
JOIN chat_raeume r ON r.id = t.raum_id
WHERE r.kategorie = 'treff' AND t.person_id = ?`).get(idModi);
ok(modiZeile?.leitung === 1, "ein Modi dagegen als Leitung — er moderiert hier");
d.close();
/* DIE COMMUNITY BEKOMMT KEIN ZWEIER-GESPRÄCH. `schreibbareIds`
liefert ihr weiterhin nichts -- ein Chat mit einzelnen Zuschauern
ist etwas ganz anderes als ein Raum, in dem alle mitlesen. */
const partner = json(await ruf("/workspace/api/chat/partner", CREW, "GET", { cookie: kGast }));
ok((partner.partner || partner.leute || []).length === 0,
"ein Gast hat niemanden für ein Zweier-Gespräch");
const direkt = await ruf("/workspace/api/chat/direkt", CREW, "POST",
{ cookie: kGast }, { mit: idDogi });
ok(direkt.code === 403,
`und kann auch keines aufmachen (${direkt.code}) — die Regel sitzt im Server`);
}
/* =======================================================================
3. SCHREIBEN — geht, solange der Treff wach ist
======================================================================= */
melde("");
melde("=== 3. Schreiben bei Tag ===");
let treffId = null;
{
const r = json(await ruf("/workspace/api/chat/raeume", CREW, "GET", { cookie: kGast }));
treffId = r.raeume?.[0]?.id ?? null;
ok(!!treffId, `der Treff hat eine Nummer (${treffId})`);
ok(r.treff && r.treff.zu === NACHTLAUF,
NACHTLAUF
? `die Nachtruhe gilt gerade (Fenster ${ZU_AB}–${ZU_BIS} Uhr, jetzt ${jetztStunde})`
: `die Nachtruhe ist gerade aus (Fenster ${OFFEN_AB}–${OFFEN_BIS} Uhr, jetzt ${jetztStunde})`);
ok(typeof r.treff?.satz === "string" && r.treff.satz.length > 20,
`der Server liefert den fertigen Satz: „${(r.treff?.satz || "").slice(0, 58)}…“`);
ok(Number.isFinite(r.treff?.minuten) && r.treff.minuten > 0,
`und wie lange es noch dauert (${r.treff?.minuten} Minuten)`);
const a = await ruf(`/workspace/api/chat/raeume/${treffId}/nachrichten`, CREW, "POST",
{ cookie: kGast }, { text: "Hallo zusammen!" });
const b = await ruf(`/workspace/api/chat/raeume/${treffId}/nachrichten`, CREW, "POST",
{ cookie: kModi }, { text: "Hi Melli!" });
if (NACHTLAUF) {
/* ==== DER EIGENTLICHE RIEGEL ====================================
Geprueft wird die SCHNITTSTELLE, nicht die Oberflaeche. Ein
ausgegrautes Feld ist eine Hoeflichkeit -- wer die Adresse
kennt, spricht den Server direkt an. */
ok(a.code === 403, `ein Gast kann nachts NICHT schreiben (${a.code})`);
ok(/schläft|ab 0?\d:00/i.test(json(a).fehler || ""),
`und die Absage sagt warum: „${(json(a).fehler || "").slice(0, 60)}…“`);
ok(json(a).nachtruhe?.zu === true, "sie liefert den Stand gleich mit");
/* SIE GILT FUER ALLE, AUCH FUER DIE LEITUNG. Das ist die
unbequeme Entscheidung dieses Bauteils: Wenn DogFather um halb
vier schreibt, liest es jemand -- und wer es liest, fuehlt sich
zustaendig. Genau den Druck soll die Nachtruhe wegnehmen. */
ok(b.code === 403, `ein Modi ebenfalls nicht (${b.code})`);
const beiDogi = await ruf(`/workspace/api/chat/raeume/${treffId}/nachrichten`, CREW, "POST",
{ cookie: kDogi }, { text: "Ich bin der Chef" });
ok(beiDogi.code === 403, `und DogFather auch nicht (${beiDogi.code})`);
/* DER ANHANG IST DERSELBE WEG. Ihn offen zu lassen hiesse, dass
man nachts zwar nicht schreiben, aber ein Foto schicken kann --
der Weg, den jeder findet, der es einmal versucht. */
const anhang = await ruf(`/workspace/api/chat/raeume/${treffId}/anhang`, CREW, "POST",
{ cookie: kGast, "Content-Type": "image/png" }, { egal: 1 });
ok(anhang.code === 403, `auch kein Anhang (${anhang.code})`);
/* GELESEN WERDEN DARF DIE GANZE NACHT. Sonst waere es keine
Nachtruhe, sondern eine geschlossene Tuer -- und wer nachts
nachsehen will, was los war, koennte es nicht. */
const lesen = await ruf(`/workspace/api/chat/raeume/${treffId}/nachrichten`,
CREW, "GET", { cookie: kGast });
ok(lesen.code === 200, `gelesen werden darf trotzdem (${lesen.code})`);
/* UND DAS TEAM HAT KEINE NACHTRUHE. Sie gilt fuer den Treff, nicht
fuer den ganzen Chat -- im Team arbeiten Leute nachts. */
/* Das Feld heisst `mit`, nicht `id` -- erster Anlauf schickte `id`
und bekam 400. Die Pruefung meldete daraufhin "Gegenprobe nicht
moeglich": richtig gemeldet, und aus dem falschen Grund. */
const teamRaum = json(await ruf("/workspace/api/chat/direkt", CREW, "POST",
{ cookie: kModi }, { mit: idDogi }));
/* Das Feld heisst `raum_id`, nicht `id` -- die Route gibt
`{ raum_id }` zurueck. Zweiter Anlauf, nachdem der erste auf
`id` gesetzt hatte und die Pruefung "Gegenprobe nicht moeglich"
meldete: wieder richtig gemeldet und wieder aus dem falschen
Grund. Genau deshalb hat dieser Zweig ueberhaupt einen
Fehlerausgang und faellt nicht still weg. */
if (teamRaum?.raum_id) {
const imTeam = await ruf(`/workspace/api/chat/raeume/${teamRaum.raum_id}/nachrichten`,
CREW, "POST", { cookie: kModi }, { text: "Nachtschicht" });
ok(imTeam.code === 201 || imTeam.code === 200,
`im Teamchat geht es nachts weiter (${imTeam.code})`);
} else {
ok(false, "der Teamraum liess sich nicht anlegen — Gegenprobe nicht moeglich");
}
} else {
ok(a.code === 201 || a.code === 200, `ein Gast kann schreiben (${a.code})`);
ok(b.code === 201 || b.code === 200, `ein Modi auch (${b.code})`);
}
if (!NACHTLAUF) {
const lesen = json(await ruf(`/workspace/api/chat/raeume/${treffId}/nachrichten`,
CREW, "GET", { cookie: kGast2 }));
const texte = (lesen.nachrichten || []).map((n) => n.text);
ok(texte.includes("Hallo zusammen!") && texte.includes("Hi Melli!"),
`der zweite Gast liest beide Nachrichten (${texte.length})`);
}
/* GEGENPROBE: Ein Creator hat mit dem Treff nichts zu tun.
UND ZWAR DOPPELT. Erster Anlauf pruefte ihn auf crew. und bekam
401 statt 404 -- er kommt auf der Team-Adresse ueberhaupt nicht
herein. Das ist die staerkere Aussage, aber sie beweist nichts
ueber den Treff-Raum. Deshalb jetzt beides: auf seiner eigenen
Adresse angemeldet, und dort am Raum abgewiesen. */
const aufCrew = await ruf(`/workspace/api/chat/raeume/${treffId}/nachrichten`, CREW, "POST",
{ cookie: kCreator }, { text: "Ich auch!" });
ok(aufCrew.code === 401 || aufCrew.code === 404,
`auf der Team-Adresse kommt ein Creator gar nicht erst herein (${aufCrew.code})`);
const imHaus = await ruf(`/workspace/api/chat/raeume/${treffId}/nachrichten`, HAUS, "POST",
{ cookie: kCreator }, { text: "Ich auch!" });
ok(imHaus.code === 404,
`und auf seiner eigenen Adresse kennt der Treff ihn nicht (${imHaus.code})`);
/* Dass seine Anmeldung ueberhaupt gilt -- sonst waeren beide Zeilen
oben nur deshalb gruen, weil der Keks nichts wert ist. */
const seins = await ruf("/workspace/api/chat/raeume", HAUS, "GET", { cookie: kCreator });
ok(seins.code === 200, `sein Zugang funktioniert dort sehr wohl (${seins.code})`);
}
/* =======================================================================
4. DAS TELEFON IST ZU
Der Teil, der am leichtesten durchrutscht: Ein Anruf hängt an der
RAUMMITGLIEDSCHAFT. Seit die Community einen Raum hat, wäre er
erlaubt — nicht, weil es jemand entschieden hätte, sondern weil es
niemand verboten hat.
======================================================================= */
melde("");
melde("=== 4. Keine Anrufe ===");
{
ok(T.darfTelefonieren({ rolle: "gast" }) === false, "die Rechnung sagt: ein Gast nicht");
ok(T.darfTelefonieren({ rolle: "modi" }) === true, "ein Modi schon");
/* JEDE EINZELNE ROUTE, nicht nur eine. Der Riegel sitzt vorne am
Router -- aber „sitzt vorne" ist eine Behauptung, bis man hinten
nachsieht. */
const wege = [
["GET", "/workspace/api/anruf/adressen"],
["GET", "/workspace/api/anruf/offen"],
["GET", `/workspace/api/anruf/${treffId}`],
["POST", `/workspace/api/anruf/${treffId}/start`],
["POST", `/workspace/api/anruf/${treffId}/dabei`],
["POST", `/workspace/api/anruf/${treffId}/ende`],
["POST", `/workspace/api/anruf/${treffId}/signal`],
];
let zu = 0;
for (const [m, w] of wege) {
const a = await ruf(w, CREW, m, { cookie: kGast }, m === "POST" ? {} : null);
if (a.code === 404) zu++;
else console.log(` offen geblieben: ${m} ${w} -> ${a.code}`);
}
ok(zu === wege.length, `alle ${wege.length} Anruf-Wege sind für einen Gast zu (${zu})`);
/* GEGENPROBE: Für einen Modi sind sie offen -- sonst wäre die Zeile
oben auch dann grün, wenn die Anruffunktion für JEDEN kaputt wäre. */
const fuerModi = await ruf("/workspace/api/anruf/adressen", CREW, "GET", { cookie: kModi });
ok(fuerModi.code === 200, `für einen Modi ist derselbe Weg offen (${fuerModi.code})`);
/* UND DIE SEITE ZEIGT IHM KEINEN KNOPF. Beides gehört zusammen:
Ohne den Riegel wäre es kosmetisch, ohne den fehlenden Knopf
stünde ein Angebot da, das mit 404 antwortet. */
const beiGast = json(await ruf("/workspace/api/chat/raeume", CREW, "GET", { cookie: kGast }));
ok(beiGast.darf_anrufen === false, "und die Seite bekommt gesagt, dass es nichts zu zeigen gibt");
const beiModi = json(await ruf("/workspace/api/chat/raeume", CREW, "GET", { cookie: kModi }));
ok(beiModi.darf_anrufen === true, "einem Modi dagegen schon");
}
/* =======================================================================
5. DIE RECHTETAFEL
======================================================================= */
melde("");
melde("=== 5. Wer die Seite öffnen darf ===");
{
const erlaubt = rechte.tafelJetzt()["/workspace/chat.html"] || [];
ok(erlaubt.includes("gast"), "die Community darf die Chatseite öffnen");
ok(erlaubt.includes("modi") && erlaubt.includes("admin"),
"Modi und Leitung ebenfalls");
/* GEGENPROBE: anruf-probe.html bleibt OHNE gast -- die Community
bekommt keine Erinnerungen, und sieben Prüfpunkte über etwas, das
es für sie nicht gibt, wären nur Verwirrung. */
ok(!(rechte.tafelJetzt()["/workspace/anruf-probe.html"] || []).includes("gast"),
"die Anruf-Probe dagegen nicht — sie telefoniert ja nicht");
}
/* =======================================================================
6. DIE KACHEL
======================================================================= */
melde("");
melde("=== 6. Die Kachel ===");
{
const ich = json(await ruf("/workspace/api/ich", CREW, "GET", { cookie: kGast }));
const alle = [...(ich.bereiche || []), ...(ich.bereiche_zusatz || [])];
const k = alle.find((x) => x.ziel === "chat.html");
ok(!!k, "die Community hat eine Chat-Kachel");
ok(k?.name === "Chat", `sie heißt „${k?.name}“`);
ok(/nacht/i.test(k?.unter || ""),
`und sagt schon auf der Startseite, dass er nachts schläft: „${k?.unter}“`);
const css = (await import("node:fs")).readFileSync(
new URL("../workspace/assets/css/start.css", import.meta.url), "utf8");
ok(new RegExp(`\\[data-ton="${k?.ton}"\\]`).test(css),
`ihr Farbton ${k?.ton} ist in start.css definiert`);
/* Keine zwei Kacheln mit derselben Farbe -- Filipe mehrfach:
„keine die sich irgendwie ähnlich sind". */
const toene = alle.map((x) => x.ton).filter(Boolean);
ok(new Set(toene).size === toene.length,
`kein Farbton doppelt bei den ${toene.length} Kacheln der Community`);
}
/* =======================================================================
6b. IM BROWSER
Der Teil, den keine Schnittstellenpruefung ersetzt: Sieht ein Gast
wirklich einen Raum und keinen Telefonknopf -- und steht das Band
ueber dem Eingabefeld, wo man es liest, bevor man tippt?
======================================================================= */
melde("");
melde("=== 6b. 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: 412, height: 915 } });
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));
/* WELCHE Adresse 404 sagt, nicht nur DASS eine es tut. "Failed to
load resource" ohne Adresse ist eine Meldung, mit der man nichts
anfangen kann -- und dann sucht man an der falschen Stelle. */
const vergeblich = [];
seite.on("response", (r) => {
if (r.status() >= 400 && !/favicon/.test(r.url())) {
vergeblich.push(`${r.status()} ${r.url().replace(/^https?:\/\/[^/]+/, "")}`);
}
});
await kontext.addCookies(kGast.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/chat.html`, { waitUntil: "networkidle" });
await seite.waitForTimeout(1100);
const stand = await seite.evaluate(() => ({
raeume: document.querySelectorAll(".chat__raum, .raum, [data-raum]").length,
text: document.body.innerText,
anrufKnopf: !!document.getElementById("anruf-ton-start")
|| !!document.getElementById("anruf-video-start"),
breiter: document.documentElement.scrollWidth > document.documentElement.clientWidth + 1,
}));
ok(/Treff/i.test(stand.text), "die Seite nennt den Treff");
/* KEIN TELEFONKNOPF. Der Riegel im Server ist das eine -- ein Knopf,
der mit 404 antwortet, waere trotzdem ein Knopf, den man drueckt. */
ok(stand.anrufKnopf === false, "und es gibt keinen Anrufknopf");
ok(!stand.breiter, "auf 412 px steht nichts ueber dem rechten Rand");
/* DAS RAUM OEFFNEN und nachsehen, was ueber dem Eingabefeld steht. */
const raum = seite.locator(".chat__liste button, .chat__raum, [data-raum-id]").first();
if (await raum.count()) { await raum.click(); await seite.waitForTimeout(700); }
const unten = await seite.evaluate(() => {
const band = document.getElementById("nachtruhe");
const feld = document.getElementById("text");
return {
bandDa: !!band && !band.hidden,
bandText: band ? band.innerText.trim() : "",
lage: band ? (band.dataset.lage || "") : "",
feldAus: !!feld && feld.disabled,
klammerAus: !!document.getElementById("anhang-auf")
&& document.getElementById("anhang-auf").disabled,
platzhalter: feld ? feld.placeholder : "",
};
});
if (NACHTLAUF) {
ok(unten.bandDa, "nachts steht das Band ueber dem Eingabefeld");
ok(/schläft/i.test(unten.bandText),
`und sagt, was los ist: „${unten.bandText.slice(0, 60)}…“`);
ok(unten.lage === "zu", `es traegt die Lage „zu“ (${unten.lage})`);
ok(unten.feldAus, "das Eingabefeld ist aus");
/* UND DIE BUEROKLAMMER. Der Server lehnt Anhaenge nachts ebenso ab;
ein offener Knopf hiesse: Bild aussuchen, hochladen, Absage --
die Arbeit ist dann schon gemacht. */
ok(unten.klammerAus, "die Büroklammer ebenfalls");
ok(/ab \d\d:00/i.test(unten.platzhalter),
`und der Platzhalter sagt, ab wann wieder: „${unten.platzhalter}“`);
} else {
/* GEGENPROBE: Bei Tag ist das Band weg und das Feld an. Ohne diese
Zeile waere "das Band steht da" auch dann gruen, wenn es IMMER
dasteht -- und ein Band, das immer da ist, liest niemand. */
ok(!unten.bandDa, "bei Tag steht kein Band da");
ok(!unten.feldAus, "und das Eingabefeld ist an");
ok(!unten.klammerAus, "die Büroklammer auch");
}
if (process.env.SCHIRM) {
await seite.screenshot({
path: `C:/Users/qciga/Documents/Obelix/DogiHompage/treffchat-${NACHTLAUF ? "nacht" : "tag"}.png`,
fullPage: true,
});
}
/* KEINE VERGEBLICHE ANFRAGE. Ein 404 bei jedem Seitenaufruf ist
Rauschen, und Rauschen macht den naechsten ECHTEN Fehler
unsichtbar -- dieselbe Regel wie bei der Warnung, die immer kommt. */
ok(vergeblich.length === 0,
vergeblich.length
? `vergebliche Anfragen: ${[...new Set(vergeblich)].join(" | ")}`
: "keine einzige Anfrage laeuft ins Leere");
ok(konsole.length === 0,
konsole.length ? "Fehler im Browser: " + konsole[0].slice(0, 110) : "keine Fehler im Browser");
await browser.close();
}
/* =======================================================================
7. UND JETZT DASSELBE MIT GELTENDER NACHTRUHE
Derselbe Code, andere Lage. Ohne diesen zweiten Lauf waere die
Nachtruhe nur behauptet: Alles oben ist auch dann gruen, wenn die
Sperre gar nicht existiert.
======================================================================= */
if (!NACHTLAUF) {
melde("");
melde("=== 7. Derselbe Lauf, aber mit geltender Nachtruhe ===");
const { spawnSync } = await import("node:child_process");
const lauf = spawnSync(process.execPath, [new URL(import.meta.url).pathname.slice(1)], {
encoding: "utf8",
env: { ...process.env, TREFF_PRUEF_NACHT: "ja", WORKSPACE_DB: "", PORT: "" },
});
const zeilen = String(lauf.stdout || "").split(String.fromCharCode(10));
for (const z of zeilen) {
if (/^\s{2}(ok|FEHL)\s/.test(z)) {
geprueft++;
if (z.includes("FEHL")) fehler++;
console.log(z.replace(/^\s{2}/, " "));
}
}
if (lauf.status !== 0 && !zeilen.some((z) => /FEHL/.test(z))) {
/* DER DRITTE AUSGANG: Der zweite Lauf ist gar nicht gelaufen.
Das ist etwas anderes als "er hat Fehler gefunden" -- und es
darf nicht wie ein bestandener Lauf aussehen. */
ok(false, `der Nachtlauf konnte nicht starten (Rueckgabewert ${lauf.status}). `
+ String(lauf.stderr || "").split(String.fromCharCode(10))[0]);
}
}
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);
+12 -2
View File
@@ -132,8 +132,18 @@ export const SEITEN = {
/* Chat (06.09.2026): jede Rolle. WEN man erreicht, entscheidet
schreibbareIds() -- nicht der Zugang zur Seite. Ein Creator, der
die Seite nicht öffnen dürfte, könnte auch nicht antworten, und
genau das Antworten ist der Zweck. */
"/workspace/chat.html": [...ALLE],
genau das Antworten ist der Zweck.
SEIT DEM 19.09.2026 AUCH DIE COMMUNITY. Sie bekommt genau EINEN
Raum -- den Treff -- und sonst keinen: `schreibbareIds` liefert
ihr weiterhin eine leere Liste (ohneAussen), ein Zweier-Gespraech
entsteht also nicht. Was sie sieht, ist der Kanal, in dem sie
Mitglied ist.
Und sie telefoniert nicht: Der Riegel dafuer sitzt am
Anruf-Router, nicht hier (Filipe: "die sollen nicht anrufen
koennen. nur schreiben"). */
"/workspace/chat.html": [...ALLE, "gast"],
/* NUR DogFather (07.09.2026). Wunsch Filipe: "nimm die kategorie
zahlen bei jedem weg, nur die rolle dogfather soll die sehen."
+30
View File
@@ -72,6 +72,7 @@ import {
istDogFather,
} from "./workspace.js";
import { chatEreignis, istDrinFuerAnruf, teilnehmerFuerAnruf } from "./workspace-chat.js";
import { darfTelefonieren } from "./workspace-treffchat.js";
import { mitZugangsdaten } from "./workspace-turn.js";
export const anrufRouter = express.Router();
@@ -259,6 +260,35 @@ function gleicheHerkunft(req, res, next) {
anrufRouter.use("/workspace/api/anruf", angemeldet);
/* =====================================================================
DIE COMMUNITY TELEFONIERT NICHT (19.09.2026)
Filipe, als er den Treff-Chat bestellt hat: „und die sollen nicht
anrufen koennen. nur schreiben."
WARUM DAS EINEN EIGENEN RIEGEL BRAUCHT, obwohl niemand es je erlaubt
hat: Ein Anruf haengt in diesem Haus an der RAUMMITGLIEDSCHAFT
(`istDrin`), nicht an der Rolle. Solange die Community gar keinen
Raum hatte, war die Frage muessig. Seit sie einen hat, waere
Telefonieren erlaubt -- nicht, weil es jemand entschieden haette,
sondern weil es niemand verboten hat. Das ist die gefaehrlichste
Art von Erlaubnis: Sie entsteht durch eine Aenderung an einer ganz
anderen Stelle, und sie faellt niemandem auf.
HIER UND NICHT IN DEN NEUN ROUTEN DAHINTER. Neun Stellen hiessen
acht richtige und eine vergessene, und die vergessene waere die,
die keiner ausprobiert. Dieselbe Begruendung wie bei `istDrin`
nebenan.
404 UND NICHT 403: Wer "kein Zugriff" liest, weiss, dass es etwas
gibt. Fuer jemanden aus der Community existiert die Anruffunktion
schlicht nicht -- die Oberflaeche zeigt sie ihm auch nicht.
===================================================================== */
anrufRouter.use("/workspace/api/anruf", (req, res, next) => {
if (darfTelefonieren(req.person)) return next();
return res.status(404).json({ fehler: "nicht_gefunden" });
});
const nummer = (x) => { const n = Number(x); return Number.isInteger(n) && n > 0 ? n : 0; };
/* =====================================================================
+89
View File
@@ -49,6 +49,14 @@ import {
import { KATALOG, ERLAUBT, VORSCHLAG, FAVORITEN_MAX } from "./workspace-reaktionen.js";
/* Der Treff-Chat: eigener Raum, eigene Nachtruhe, keine Anrufe.
Eigene Datei, weil er eigene Regeln hat -- und weil diese hier
mit 2000 Zeilen schon gross genug ist. */
import {
TREFF_KATEGORIE, TREFF_NAME, istTreffRaum, treffAngleichen,
gehoertInTreff, nachtruheStand, istNachtruhe, darfTelefonieren,
} from "./workspace-treffchat.js";
export const chatRouter = express.Router();
const TEXT_MAX = 4000;
@@ -339,6 +347,10 @@ const PIN_MAX = 3;
* der Nachrichten darin. Wer die Kategorie umbenennt, benennt damit
* jeden Kanal um; sie stehen nicht doppelt. */
function kanalName(kategorie) {
/* Der Treff steht nicht in MODI_KATEGORIEN -- er ist kein Thema des
Teams, sondern ein eigener Raum mit eigenen Regeln. Ohne diese
Zeile hiesse er schlicht "Kanal". */
if (kategorie === TREFF_KATEGORIE) return TREFF_NAME;
return MODI_KATEGORIEN.find((k) => k.wert === kategorie)?.name || "Kanal";
}
@@ -457,6 +469,12 @@ chatRouter.get("/workspace/api/chat/raeume", (req, res) => {
nur ueber einen Link, den ihr jemand schickt. Das ist genau die
Sorte Loch, die man nicht bemerkt, weil nichts kaputt aussieht. */
kanaeleAngleichen(req.person);
/* UND DER TREFF. `kanaeleAngleichen` traegt nur die Teamfuehrung
ein; im Treff stehen ausserdem die Community und die Modis.
Derselbe Gedanke, andere Personengruppe -- und aus demselben
Grund selbstheilend: Ein neuer Zugang aus der Community soll im
Chat stehen, ohne dass jemand daran denken muss. */
if (gehoertInTreff(req.person)) treffAngleichen();
const reihen = db().prepare(`
SELECT r.id, r.art, r.name, r.kategorie, r.letzte_am, r.erstellt_von,
@@ -534,6 +552,23 @@ chatRouter.get("/workspace/api/chat/raeume", (req, res) => {
raeume,
darf_kanal: darfKanal,
kanal_frei: darfKanal ? MODI_KATEGORIEN.filter((k) => !belegt.has(k.wert)) : [],
/* WAS FUER DEN TREFF GILT, STEHT IN DER ANTWORT -- die Seite
rechnet die Nachtruhe NICHT selbst nach. Eine zweite Rechnung
im Browser waere die, die bei der naechsten Zeitumstellung
anders herauskommt als die im Server; dann sagt die Seite
"offen", waehrend der Server ablehnt. */
treff: gehoertInTreff(req.person) ? nachtruheStand() : null,
/* AUSDRUECKLICH GESAGT, NICHT ERRATEN (19.09.2026).
Die Anrufknoepfe verschwinden fuer die Community nicht von
selbst, nur weil die Schnittstelle 404 antwortet -- sie stuenden
da und taeten nichts. Ein Knopf, der nichts tut, ist schlimmer
als kein Knopf: Man drueckt ihn wieder.
Die Antwort kommt aus DERSELBEN Funktion, die auch der
Anruf-Router benutzt. Zwei Rechnungen waeren die Stelle, an der
die Seite einen Knopf zeigt, den der Server ablehnt. */
darf_anrufen: darfTelefonieren(req.person),
});
} catch (fehler) {
console.error("[chat] Räume:", fehler?.message);
@@ -930,6 +965,37 @@ chatRouter.post("/workspace/api/chat/raeume/:id/nachrichten", gleicheHerkunft,
if (!Number.isInteger(raumId)) return res.status(400).json({ fehler: "ungueltig" });
if (!istDrin(raumId, req.person)) return res.status(404).json({ fehler: "nicht_gefunden" });
/* ==== DIE NACHTRUHE ==============================================
Filipe: "quasi nach mitternacht bis morgens 6 uhr keiner
schreiben kann damit auch die privatsphaere beruecksichtig
wird ueber nacht."
SIE STEHT HIER UND NICHT NUR IM BROWSER. Ein ausgegrauter
Knopf ist eine Bitte; abgelehnt wird erst hier. Wer die
Schnittstelle direkt anspricht -- und das kann jeder, der die
Adresse kennt --, kaeme sonst durch.
SIE GILT FUER ALLE, AUCH FUER DIE LEITUNG. Eine Nachtruhe, von
der DogFather ausgenommen waere, ist keine: Wenn er um halb
vier schreibt, liest es jemand, und wer es liest, fuehlt sich
zustaendig. Genau den Druck soll sie wegnehmen.
NUR IM TREFF. Das Team hat keine Nachtruhe im Chat -- dort
arbeiten Leute nachts, das ist der Beruf. Hier geht es um
Zuschauer und um die, die sie betreuen.
MODERIEREN GEHT WEITER: Verbergen, loeschen, jemanden
herausnehmen sind keine Nachrichten und laufen ueber andere
Wege. Wer nachts etwas Schlimmes sieht, kann es wegnehmen --
er kann nur nicht darueber diskutieren. */
if (istTreffRaum(raumId) && istNachtruhe()) {
const stand = nachtruheStand();
return res.status(403).json({
fehler: stand.satz, nachtruhe: stand,
});
}
const text = String(req.body?.text ?? "").trim();
if (!text) return res.status(400).json({ fehler: "leer" });
if (text.length > TEXT_MAX) {
@@ -1024,6 +1090,14 @@ chatRouter.post("/workspace/api/chat/raeume/:id/anhang",
(req, res) => {
try {
const raumId = Number(req.params.id);
/* DIE NACHTRUHE GILT AUCH FUER ANHAENGE. Sie nur beim Text zu
pruefen hiesse, dass man nachts zwar nicht schreiben, aber ein
Foto mit Bildunterschrift schicken kann -- der Weg, den jeder
findet, der es einmal versucht. */
if (istTreffRaum(raumId) && istNachtruhe()) {
const stand = nachtruheStand();
return res.status(403).json({ fehler: stand.satz, nachtruhe: stand });
}
if (!Buffer.isBuffer(req.body) || !req.body.length) {
return res.status(400).json({ fehler: "Keine Datei empfangen." });
}
@@ -1355,6 +1429,21 @@ function reaktionenZu(ids, personId) {
Zweimal dasselbe Zeichen heisst: wieder weg. Das erspart einen
zweiten Weg zum Zuruecknehmen -- und einen zweiten Weg, den man
vergisst abzusichern. */
/* ==== DIE NACHTRUHE GILT HIER ABSICHTLICH NICHT (19.09.2026) ========
Eine Reaktion ist keine Nachricht. Sie sagt nichts, was jemand
beantworten muesste, sie kann keinen Streit anfangen, und sie loest
keine Meldung aus (siehe die Begruendung weiter unten: „Ein Daumen
ist keine Nachricht").
Filipes Ansage war „keiner schreiben kann". Ein Daumen ist kein
Schreiben -- und genau darum geht es bei der Nachtruhe: Sie soll den
Druck wegnehmen, nachts antworten zu muessen, nicht den Raum
abschliessen.
DAS STEHT HIER, damit es nicht wie ein Versehen aussieht. Eine
Luecke, die niemand begruendet hat, wird beim naechsten Durchsehen
entweder geschlossen oder uebersehen -- beides falsch. */
chatRouter.put("/workspace/api/chat/nachrichten/:id/reaktion", gleicheHerkunft,
express.json({ limit: "2kb" }), (req, res) => {
try {
+26 -2
View File
@@ -27,6 +27,7 @@ import {
db, sitzungLesen, betreuteIds, istLeitung, istDogFather, siehtAlles, pipelineIds,
gehoertAufDieseAdresse,
} from "./workspace.js";
import { darfSeite } from "./rechte.js";
import { sichtbar as sichtbarAufgaben } from "./workspace-aufgaben.js";
import { sichtbar as sichtbarTermine } from "./workspace-kalender.js";
import { sichtbar as sichtbarDateien } from "./workspace-dateien.js";
@@ -106,8 +107,31 @@ hinweisRouter.get("/workspace/api/hinweise", (req, res) => {
DIESELBE REGEL, NICHT EINE ZWEITE: Was auf dieser Adresse
keine Seite hat, bekommt hier auch keinen Hinweis. Eine eigene
Liste hier waere die, die beim naechsten Umbau auseinanderlaeuft. */
if (ziel && !gehoertAufDieseAdresse(hier,
"/workspace/" + String(ziel).split("?")[0])) return;
const zielPfad = ziel ? "/workspace/" + String(ziel).split("?")[0] : null;
if (zielPfad && !gehoertAufDieseAdresse(hier, zielPfad)) return;
/* ==== UND DIE ROLLE, NICHT NUR DIE ADRESSE (19.09.2026) =========
`gehoertAufDieseAdresse` beantwortet die Frage "gibt es diese
Seite hier?" -- nicht "darf DIESE Person sie oeffnen?". Der
Unterschied ist am 19.09. sofort aufgefallen, als der erste
Hinweis dazukam, der nicht fuer alle gilt:
Ein Mitglied der Community bekam "Bei dir klingelt nichts" mit
Weg auf `anruf-probe.html` -- eine Seite, die ihm ausdruecklich
NICHT offensteht. Gefunden von pruef-community-sicht, die
genau danach sucht: sichtbare Wege, die ins Leere fuehren.
Das ist der 15.09. noch einmal, nur eine Ebene tiefer. Damals
war es die Adresse ("wieso steht es noch da?"), diesmal die
Rolle. Und es ist dieselbe Lehre: Die Hinweise sind eine
DRITTE Stelle neben Seiten und Schnittstellen, und jede
Schranke, die es dort gibt, muss es auch hier geben.
DIESELBE TAFEL, NICHT EINE ZWEITE: `darfSeite` ist die
Funktion, die auch die Tuer bewacht. Eine eigene Liste hier
waere die, die beim naechsten Umbau auseinanderlaeuft. */
if (zielPfad && !darfSeite(person, zielPfad)) return;
hinweise.push({ art, stufe, text, ziel, anzahl });
};
+284
View File
@@ -0,0 +1,284 @@
/* =====================================================================
DER TREFF-CHAT — mit Nachtruhe (19.09.2026)
Filipe, auf die Frage, ob die Community einen Chat bekommen soll:
„eine geile mischung von punkt 2 und drei. mach es perfekt so das
quasi nach mitternacht bis morgens 6 uhr keiner schreiben kann
damit auch die privatsphäre berücksichtig wird über nacht.
perfektionier das bitte. mach es übelst krass geil. und die
sollen nicht anrufen können. nur schreiben."
Zur Auswahl standen „gar kein Chat", „nur mit Öffnungszeiten" und
„offen wie im Team". Seine Antwort ist die dritte Möglichkeit, die
nicht auf dem Zettel stand, und sie ist die bessere: offen den
ganzen Tag, ZU über Nacht.
---------------------------------------------------------------------
WARUM DAS MEHR IST ALS EINE EINSTELLUNG
Der Grund, den er nennt, ist nicht Bequemlichkeit, sondern
Privatsphäre — und zwar die der Leute, die moderieren. Die Forschung
zu ehrenamtlichen Moderatoren ist an diesem Punkt eindeutig: Die
beiden meistgenannten Gründe fürs Aufhören sind ZU WENIG ZEIT und
STREIT IM TEAM. Ein Chat, der nachts um drei weiterläuft, erzeugt
beides — die Nachricht steht da, jemand fühlt sich zuständig, und am
nächsten Morgen ist aus einem Satz ein Streit geworden, den niemand
früh genug gesehen hat.
Eine Nachtruhe nimmt diesen Druck heraus, ohne irgendjemandem etwas
zu verbieten: Gelesen werden darf weiter, geschrieben ab sechs.
---------------------------------------------------------------------
SIE GILT FÜR ALLE, AUCH FÜR DIE LEITUNG
Das ist die wichtigste Entscheidung dieser Datei, und sie ist
unbequem. Der bequeme Weg wäre, DogFather und die rechte Hand
auszunehmen — sie sollen ja jederzeit eingreifen können.
Aber: Wenn DogFather um halb vier schreibt, liest es jemand. Und wer
es liest, fühlt sich zuständig. Eine Nachtruhe, von der die Leitung
ausgenommen ist, ist keine Nachtruhe, sondern eine Bitte — und
Bitten dieser Art halten genau so lange, bis es einmal eilig ist.
MODERIEREN GEHT TROTZDEM. Verbergen, löschen, jemanden
herausnehmen — das sind keine Nachrichten, sondern Eingriffe, und
die bleiben rund um die Uhr möglich. Wer nachts etwas Schlimmes
sieht, kann es wegnehmen; er kann nur nicht darüber diskutieren.
UND DER VERTRAULICHE MELDEWEG BLEIBT OFFEN. Er ist kein Chat,
sondern ein Fall mit einer Zuständigkeit. Wer nachts Hilfe braucht,
hat sie dort — nicht in einem Raum mit dreißig Leuten.
---------------------------------------------------------------------
GERECHNET WIRD IN ORTSZEIT, NICHT IN UTC
`new Date().getHours()` liefert die Stunde des SERVERS. Steht der auf
UTC, geht der Chat im Sommer um zwei Uhr nachts zu und um acht Uhr
morgens wieder auf — beides zwei Stunden daneben, und beides fällt
nur denen auf, die um diese Zeit wach sind.
Dieses Haus ist mit genau dieser Rechnung schon zweimal
hereingefallen (06.09. und 16.09., beide Male in Prüfungen, beide
Male nur nachts sichtbar). Deshalb steht hier `Intl` mit
`Europe/Berlin` und nicht `getHours()`.
---------------------------------------------------------------------
KEINE ANRUFE — UND DER RIEGEL SITZT AN EINER STELLE
„die sollen nicht anrufen können. nur schreiben."
Ein Anruf hängt in diesem Haus an der RAUMMITGLIEDSCHAFT
(`istDrin`). Sobald die Community einen Raum hat, könnte sie also
telefonieren — nicht, weil es jemand erlaubt hätte, sondern weil es
niemand verboten hat. Das ist die gefährlichste Art von Erlaubnis.
Der Riegel steht deshalb GANZ VORNE am Anruf-Router, nicht in den
neun Routen dahinter. Neun Stellen hieße acht richtige und eine
vergessene, und die vergessene wäre die, die keiner ausprobiert.
===================================================================== */
import { db, darfTelefonieren } from "./workspace.js";
import { AUSSEN_ROLLEN } from "./crew-adresse.js";
/** Die Kategorie des Treff-Kanals.
*
* ALS KANAL UND NICHT ALS EIGENE RAUMART: `chat_raeume.art` hat eine
* CHECK-Bedingung ('direkt','gruppe','kanal'). Eine vierte Art hieße,
* die Tabelle an lebenden Daten neu zu bauen — für etwas, das eine
* Kategorie genauso gut sagt. Ein Kanal ist ohnehin genau das
* Richtige: Er gehört dem Haus, nicht den Anwesenden, und die
* Leitung kommt über `istDrin` hinein, ohne dass jemand sie einträgt. */
export const TREFF_KATEGORIE = "treff";
export const TREFF_NAME = "Der Treff";
/** Wer im Treff-Chat steht.
*
* Die Community UND die, die moderieren. Ein Raum voller Zuschauer
* ohne Moderation wäre nicht mutig, sondern fahrlässig — und die
* Nachtruhe ist kein Ersatz dafür, sie verkürzt nur die Zeit, in der
* jemand hinsehen muss. */
export const TREFF_ROLLEN = ["gast", "modi", "hand", "admin"];
/** Ab wann und bis wann niemand schreibt — Ortszeit, Stunden.
*
* 0 BIS 6, wörtlich nach Filipes Ansage („nach mitternacht bis
* morgens 6 uhr"). Als Umgebungsvariable, damit eine andere Zeit
* keine Programmänderung ist — dieselbe Bauweise wie bei der
* Push-Ruhezeit (22–7, die ist eine ANDERE Sache und bleibt getrennt:
* dort geht es darum, wann das Telefon klingelt, hier darum, wann
* überhaupt jemand schreiben kann). */
export const NACHT_AB = Number(process.env.TREFF_NACHT_AB ?? 0);
export const NACHT_BIS = Number(process.env.TREFF_NACHT_BIS ?? 6);
/** Die Stunde in Europe/Berlin — nicht die des Servers. */
const STUNDE = new Intl.DateTimeFormat("de-DE", {
timeZone: "Europe/Berlin", hour: "2-digit", hour12: false,
});
export function stundeInBerlin(d = new Date()) {
/* „24" gibt es: Manche Umgebungen liefern fuer Mitternacht 24 statt
0. Ungeprueft waere der Chat dann ausgerechnet in der ersten
Stunde der Nachtruhe offen. */
const s = Number(STUNDE.format(d).replace(/\D/g, ""));
return s === 24 ? 0 : s;
}
/** Die Minute in Europe/Berlin — für den Countdown. */
const MINUTE = new Intl.DateTimeFormat("de-DE", {
timeZone: "Europe/Berlin", minute: "2-digit",
});
function minuteInBerlin(d = new Date()) {
return Number(MINUTE.format(d).replace(/\D/g, ""));
}
/** Schläft der Chat gerade?
*
* ÜBER MITTERNACHT HINWEG GEDACHT. Mit AB=0 und BIS=6 ist es der
* einfache Fall (0 ≤ s < 6). Setzt jemand später AB=23, muss die
* Rechnung den Tageswechsel mitmachen — sonst wäre die Nachtruhe
* zwischen 23 und 24 Uhr aus. Beide Fälle stehen hier, damit die
* Einstellung wirklich eine Einstellung ist und keine Falle. */
export function istNachtruhe(d = new Date()) {
if (NACHT_AB === NACHT_BIS) return false; // keine Nachtruhe
const s = stundeInBerlin(d);
return NACHT_AB < NACHT_BIS
? (s >= NACHT_AB && s < NACHT_BIS)
: (s >= NACHT_AB || s < NACHT_BIS);
}
/** Wie viele Minuten noch, bis sich der Zustand ändert.
*
* Für den Countdown auf der Seite. Er ist der Unterschied zwischen
* „der Chat ist plötzlich zu" und „ich weiß seit zehn Minuten, dass
* gleich Schluss ist". */
export function minutenBisWechsel(d = new Date()) {
const s = stundeInBerlin(d), m = minuteInBerlin(d);
const jetztMin = s * 60 + m;
const ziel = (istNachtruhe(d) ? NACHT_BIS : NACHT_AB) * 60;
let diff = ziel - jetztMin;
if (diff <= 0) diff += 24 * 60;
return diff;
}
/** Alles, was die Seite über die Nachtruhe wissen muss — an einer Stelle.
*
* Die Oberfläche rechnet NICHTS davon selbst nach. Eine zweite
* Rechnung im Browser wäre die, die bei der nächsten Zeitumstellung
* anders herauskommt als die im Server — und dann sagt die Seite
* „offen", während der Server ablehnt. */
export function nachtruheStand(d = new Date()) {
const zu = istNachtruhe(d);
return {
zu,
ab: NACHT_AB,
bis: NACHT_BIS,
minuten: minutenBisWechsel(d),
/* Der fertige Satz kommt vom Server, nicht aus dem Browser. So
steht er in der Absage UND auf der Seite gleich — und wer ihn
ändern will, ändert ihn einmal. */
satz: zu
? `Der Treff schläft. Ab ${String(NACHT_BIS).padStart(2, "0")}:00 Uhr `
+ "geht es weiter – gelesen werden darf die ganze Nacht."
: `Nachts zwischen ${String(NACHT_AB).padStart(2, "0")}:00 und `
+ `${String(NACHT_BIS).padStart(2, "0")}:00 Uhr schreibt hier niemand.`,
};
}
/** Darf diese Person überhaupt telefonieren?
*
* EINE FRAGE, EINE ANTWORT. Sie wird ganz vorne am Anruf-Router
* gestellt, nicht in den neun Routen dahinter.
*
* GERECHNET WIRD SIE IN workspace.js, weil `/api/ich` sie ebenfalls
* braucht -- und jene Datei kann nicht aus dieser importieren, ohne
* einen Ring zu bilden. Hier steht nur die Durchreichung, damit der
* Anruf-Router und der Chat sie unter demselben Namen finden. */
export { darfTelefonieren };
/** Gehört diese Person in den Treff-Chat? */
export function gehoertInTreff(person) {
return TREFF_ROLLEN.includes(person?.rolle);
}
/* ---------------------------------------------------------------------
DER RAUM
------------------------------------------------------------------- */
const jetzt = () => new Date().toISOString();
/** Die Nummer des Treff-Kanals — angelegt, falls es ihn noch nicht gibt.
*
* GENAU EINER. Zwei Räume mit demselben Zweck sind zwei Räume mit je
* der Hälfte der Nachrichten darin, und niemand merkt, in welchem er
* gerade steht. Deshalb wird gesucht, bevor angelegt wird. */
export function treffRaumId() {
const d = db();
const da = d.prepare(
"SELECT id FROM chat_raeume WHERE art = 'kanal' AND kategorie = ?")
.get(TREFF_KATEGORIE);
if (da) return da.id;
d.prepare(`INSERT INTO chat_raeume (art, name, kategorie, erstellt, erstellt_von)
VALUES ('kanal', ?, ?, ?, NULL)`)
.run(TREFF_NAME, TREFF_KATEGORIE, jetzt());
const id = d.prepare("SELECT last_insert_rowid() AS id").get().id;
console.log(`[treff] Chat-Raum angelegt (Nummer ${id}).`);
return id;
}
/** Trägt ein, wer hineingehört und noch fehlt.
*
* SELBSTHEILEND, wie der Kanal-Abgleich der Teamführung nebenan —
* und aus demselben Grund: Die Zeile müsste sonst beim Anlegen einer
* Person, beim Rollenwechsel und beim Anlegen des Raums entstehen.
* Drei Stellen, von denen die dritte vergessen wird.
*
* `gelesen_bis` startet am aktuellen Stand. Wer heute dazukommt, hat
* die vierhundert Nachrichten von vorher nicht verpasst — eine rote
* Vierhundert wäre keine Auskunft, sondern Rauschen.
*
* WER AUSGETRETEN IST, BLEIBT DRAUSSEN. `raus_am IS NOT NULL` heißt:
* Die Moderation hat jemanden herausgenommen. Ihn beim nächsten
* Seitenaufruf still wieder einzutragen wäre die zuverlässigste Art,
* eine Moderationsentscheidung unwirksam zu machen. */
export function treffAngleichen() {
try {
const id = treffRaumId();
const d = db();
const platz = TREFF_ROLLEN.map(() => "?").join(", ");
const fehlen = d.prepare(`
SELECT p.id FROM personen p
WHERE p.aktiv = 1 AND p.rolle IN (${platz})
AND NOT EXISTS (SELECT 1 FROM chat_teilnehmer t
WHERE t.raum_id = ? AND t.person_id = p.id)`)
.all(...TREFF_ROLLEN, id);
if (!fehlen.length) return 0;
const rein = d.prepare(`
INSERT INTO chat_teilnehmer (raum_id, person_id, seit, gelesen_bis, leitung)
SELECT ?, ?, ?, COALESCE((SELECT MAX(id) FROM chat_nachrichten WHERE raum_id = ?), 0), ?`);
const n = jetzt();
for (const p of fehlen) {
/* Leitung im Raum sind die, die auch sonst moderieren. Ein Gast
ist Teilnehmer, kein Leiter -- sonst dürfte er anheften und
andere herausnehmen. */
const person = d.prepare("SELECT rolle FROM personen WHERE id = ?").get(p.id);
const leitung = person && person.rolle !== "gast" ? 1 : 0;
rein.run(id, p.id, n, id, leitung);
}
return fehlen.length;
} catch (fehler) {
/* Ein Fehler hier darf die Gespraechsliste nicht kosten. */
console.error("[treff] Abgleich:", fehler?.message);
return 0;
}
}
/** Ist das der Treff-Raum? */
export function istTreffRaum(raumId) {
try {
const r = db().prepare(
"SELECT kategorie FROM chat_raeume WHERE id = ? AND art = 'kanal'").get(raumId);
return r?.kategorie === TREFF_KATEGORIE;
} catch { return false; }
}
+80
View File
@@ -987,6 +987,27 @@ const TREFF_GRUPPE = {
const TREFF_BEREICHE = [
{ ...TREFF_GRUPPE, name: "Der Treff", unter: "Hallo sagen, fragen, loben",
zeichen: "community", ton: 34, gross: true, ziel: "bereich.html?b=treff", szene: "lounge" },
/* DER CHAT (19.09.2026). Filipe: "eine geile mischung von punkt 2
und drei ... nach mitternacht bis morgens 6 uhr keiner schreiben
kann damit auch die privatsphaere beruecksichtig wird ueber nacht
... und die sollen nicht anrufen koennen. nur schreiben."
GLEICH AN ZWEITER STELLE, direkt hinter dem grossen Treff-Brett.
Er ist das Lebendigste, was die Community hier hat -- weiter unten
waere er der Knopf, den man erst findet, wenn einem jemand sagt,
dass es ihn gibt.
ZIEL IST chat.html, dieselbe Seite wie beim Team. Kein zweiter
Chat: Zwei Oberflaechen fuer dasselbe waeren zwei Stellen, an
denen ein Fehler behoben werden muss, und die zweite waere die,
die man vergisst. Was jemand dort SIEHT, entscheidet die
Raummitgliedschaft -- ein Gast hat genau einen Raum.
TON 41: gezaehlt in start.css, nicht in dieser Datei. 40 war die
hoechste (siehe die Warnung an WERDEGANG_KACHEL, dort ist genau
dieser Fehler heute schon einmal passiert). */
{ ...TREFF_GRUPPE, name: "Chat", unter: "Miteinander reden – nachts schläft er",
zeichen: "chat", ton: 41, ziel: "chat.html", szene: "lounge" },
{ ...TREFF_GRUPPE, name: "Anschlagbrett", unter: "Was gerade gilt",
zeichen: "reports", ton: 33, ziel: "bereich.html?b=anschlag", szene: "halle" },
{ ...TREFF_GRUPPE, name: "Was ansteht", unter: "Die n\u00e4chsten Streams und Events",
@@ -1118,6 +1139,18 @@ const HAND_MIT_TREFF = [...HAND_BEREICHE, ...TREFF_BEREICHE,
importiert von dort. Eine zweite Menge waere die, die auseinanderlaeuft. */
export { AUSSEN_ROLLEN };
/** Darf diese Person telefonieren?
*
* HIER UND NICHT AUS workspace-treffchat.js IMPORTIERT: Jene Datei
* importiert aus dieser. Ein Import zurueck waere ein Ring, und Ringe
* halten in JavaScript genau so lange, bis jemand eine Konstante auf
* oberster Ebene benutzt. `workspace-treffchat.js` reicht diese
* Funktion deshalb nur WEITER -- gerechnet wird sie an einer Stelle,
* naemlich hier. */
export function darfTelefonieren(person) {
return !AUSSEN_ROLLEN.has(person?.rolle);
}
/* =====================================================================
KACHEL UND TUER GEHOEREN ZUSAMMEN (11.09.2026)
=====================================================================
@@ -4945,6 +4978,23 @@ workspaceRouter.get("/workspace/api/ich", (req, res) => {
auseinander. Genau die Ueberlegung steht schon zwei Zeilen
darueber; hier gilt sie noch einmal. */
darf_chat: darfSeite(person, "/workspace/chat.html"),
/* UND OB SIE TELEFONIEREN DARF (19.09.2026).
Nicht als Bequemlichkeit: Ohne diese Angabe holt `anruf.js` beim
Laden jeder Chatseite die Verbindungsadressen -- und bekommt
fuer die Community 404. Der Browser schreibt das in die Konsole,
BEVOR JavaScript es abfangen kann; abfangen hilft also nicht,
die Anfrage darf gar nicht erst gestellt werden.
Ein 404 bei jedem Seitenaufruf ist Rauschen, und Rauschen macht
den naechsten ECHTEN Fehler unsichtbar -- dieselbe Regel wie bei
der Warnung, die immer kommt. Gefunden hat es die Pruefung im
Browser, nicht das Lesen.
AUS DERSELBEN FUNKTION wie der Riegel am Anruf-Router. Zwei
Rechnungen waeren die Stelle, an der die Seite ein Telefon
anbietet, das der Server ablehnt. */
darf_anrufen: darfTelefonieren(person),
/* DIE KACHELN, WENN SIE NICHT IM BROWSER STEHEN DUERFEN.
Fuer die fuenf bekannten Rollen steht hier `null`, und die
@@ -6172,6 +6222,36 @@ export const einladbareIds = (person) => ohneAussen(nurEigene(nurHaus(ohneVerbor
seit es ueberhaupt jemanden zu verbergen gibt; vorher konnte es ihn
nicht geben. */
export const schreibbareIds = (person) => {
/* ==== DIE COMMUNITY SCHREIBT NIEMANDEM EINZELN (19.09.2026) ========
GEFUNDEN VON pruef-treffchat, NICHT BEIM LESEN. Ein Gast konnte
ein Zweier-Gespraech mit DogFather aufmachen -- die Route
antwortete mit 200 und legte den Raum an.
WARUM `ohneAussen` DAS NICHT VERHINDERT HAT: Diese Huelle nimmt
die Community aus den Listen ANDERER heraus. Sie sorgt dafuer,
dass ein Creator keinen Zuschauer anschreiben kann. Die
Gegenrichtung -- ein Zuschauer, der IN die Liste schaut -- kam nie
vor, weil die Chatseite fuer 'gast' gar nicht offen war.
SEIT HEUTE IST SIE OFFEN, und damit war die Erlaubnis da. Nicht,
weil jemand sie erteilt haette, sondern weil eine Aenderung an
einer ganz anderen Stelle sie freigelegt hat. Genau die Sorte
Erlaubnis, vor der ich im Anruf-Riegel zwei Dateien weiter
ausdruecklich warne -- und hier waere ich selbst hineingelaufen.
WAS DARAN SCHLIMM IST: Ein Zweier-Gespraech ist kein Kanal. Es hat
keine Nachtruhe (die gilt fuer den Treff), kein Modi liest mit,
und niemand koennte moderieren. Aus "die Community bekommt einen
Chat" waere ungewollt "jeder Zuschauer bekommt eine Standleitung
zu DogFather" geworden.
WAS SIE STATTDESSEN HAT: den Treff -- einen Raum, in dem Modis
dabei sind -- und den vertraulichen Meldeweg fuer alles, was
niemanden sonst angeht. Beides ist besser als eine Privatleitung:
Das eine wird moderiert, das andere hat eine Zustaendigkeit. */
if (AUSSEN_ROLLEN.has(person?.rolle)) return [];
const ids = ohneAussen(nurHaus(ohneVerborgene(schreibbareIdsRoh(person), person), person));
return ids === null ? null : ids.filter((i) => i !== person?.id);
};
+4 -4
View File
@@ -16,15 +16,15 @@
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=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<style>
/* Bewusst in der Seite und nicht in einer eigenen Datei: Diese Seite
soll auch dann noch etwas anzeigen, wenn genau das schiefgeht,
@@ -75,6 +75,6 @@
<p class="probe__ende" id="rat"></p>
</main>
<script src="assets/js/anruf-probe.js?v=202609191035" defer></script>
<script src="assets/js/anruf-probe.js?v=202609191147" defer></script>
</body>
</html>
+4 -4
View File
@@ -22,25 +22,25 @@
},
"icons": [
{
"src": "/assets/img/app-symbole/workspace-192.png?v=202609191035",
"src": "/assets/img/app-symbole/workspace-192.png?v=202609191147",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/img/app-symbole/workspace-512.png?v=202609191035",
"src": "/assets/img/app-symbole/workspace-512.png?v=202609191147",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609191035",
"src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609191147",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609191035",
"src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609191147",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
+63
View File
@@ -1704,3 +1704,66 @@
.anruf, .anruf-klingelt { transition: none; }
}
/* =====================================================================
DIE NACHTRUHE DES TREFFS (19.09.2026)
Filipe: „nach mitternacht bis morgens 6 uhr keiner schreiben kann
damit auch die privatsphäre berücksichtig wird über nacht."
---------------------------------------------------------------------
KEIN ROT, UND ZWAR AUS EINEM GRUND
Der schlafende Chat ist keine Störung und kein Fehler – er ist so
gewollt. Eine Warnfarbe würde sagen „hier stimmt etwas nicht", und
das ist das Gegenteil der Botschaft. Sie wäre außerdem die dritte
Signalfarbe auf dieser Seite, und ab der dritten bedeutet keine mehr
etwas.
Stattdessen ein tiefes, ruhiges Blau – die Farbe, die jedes
Betriebssystem für „Nicht stören" benutzt. Wer es sieht, weiß es
ohne zu lesen.
ZWEI ZUSTÄNDE, zwei Helligkeiten:
zu das Band steht ruhig da, der Mond gefüllt
gleich ein Hauch wärmer – es ist noch offen, aber nicht lange
AUGENSCHONEND heißt hier auch: keine Animation. Ein pulsierendes
Band über dem Eingabefeld, stundenlang, wäre genau die Art von
Unruhe, die diese Seite nicht haben soll.
===================================================================== */
.chat__nachtruhe {
display: flex; align-items: center; gap: 10px;
margin: 0; padding: 9px 14px;
border-top: 1px solid var(--rand);
background: color-mix(in srgb, var(--tinte) 88%, #2b4a7a 12%);
font-size: .82rem; line-height: 1.5;
color: var(--text-leise);
}
.chat__nachtruhe[data-lage="gleich"] {
background: color-mix(in srgb, var(--tinte) 90%, #7a5a2b 10%);
}
.chat__nachtruhe-zeichen {
flex: none; display: grid; place-items: center;
width: 22px; height: 22px;
}
.chat__nachtruhe-zeichen svg {
width: 17px; height: 17px;
fill: #9db4d8; stroke: none;
}
.chat__nachtruhe[data-lage="gleich"] .chat__nachtruhe-zeichen svg { fill: #d8a76a; }
.chat__nachtruhe-text { min-width: 0; }
/* Das Eingabefeld, wenn der Treff schläft. Sichtbar aus, nicht weg:
Ein verschwundenes Feld sieht aus wie ein Fehler, ein ausgegrautes
wie eine Entscheidung. */
.chat__eingabe #text:disabled,
.chat__eingabe .chat__senden:disabled {
opacity: .5;
cursor: not-allowed;
}
@media (forced-colors: active) {
.chat__nachtruhe { border: 1px solid CanvasText; }
.chat__nachtruhe-zeichen svg { fill: CanvasText; }
}
+1
View File
@@ -1128,6 +1128,7 @@ body.start > main { position: relative; z-index: 1; }
statt die Schwelle stillschweigend zu senken. */
[data-ton="39"] { --ton: #ff57ba; } /* Vertraulich melden */
[data-ton="40"] { --ton: #feb9b8; } /* Entwicklung (die Auswertung) */
[data-ton="41"] { --ton: #6d8cfe; } /* Treff-Chat */
/* 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
+53 -2
View File
@@ -742,6 +742,23 @@
nicht mit Daten starten, die in 40 ablaufen. */
const ZUGANG_VORLAUF = 300;
let adressenGeholt = false;
/* ==== TELEFONIERT DIESE PERSON UEBERHAUPT? (19.09.2026) ============
Seit die Community einen Chat hat, gibt es Leute, fuer die es die
Anruffunktion nicht gibt -- der Riegel sitzt am Anruf-Router und
antwortet mit 404.
OHNE DIESE MERKUNG WAERE DAS LAUT: Die Chatseite holt beim Laden
die Verbindungsadressen und danach die offenen Anrufe. Beides
ergab fuer einen Gast einen 404 in der Konsole -- zweimal, bei
jedem Seitenaufruf. Gefunden hat das die Pruefung im Browser,
nicht das Lesen.
Ein Fehler in der Konsole, der bei jedem Aufruf kommt und nichts
bedeutet, ist genau die Sorte Rauschen, die den naechsten ECHTEN
Fehler unsichtbar macht. Dieselbe Regel wie bei der Warnung, die
immer kommt. */
let telefonGehtHier = true;
function adressenFrisch() {
if (!adressenGeholt) return false;
@@ -756,6 +773,9 @@
if (!erzwingen && adressenFrisch()) return;
try {
const a = await fetch('/workspace/api/anruf/adressen', { credentials: 'same-origin' });
/* 404 heisst hier nicht "kaputt", sondern "gibt es fuer dich
nicht". Ab da wird gar nicht mehr gefragt. */
if (a.status === 404) { telefonGehtHier = false; return; }
if (a.ok) {
const d = await a.json();
adressen = d.adressen || [];
@@ -774,18 +794,48 @@
/* ---------- Nach außen -------------------------------------------------- */
/* Wer hier nicht telefoniert, bekommt die Funktion gar nicht erst.
Der Chat prueft `window.anrufStarten`, bevor er die Knoepfe baut --
damit faellt der zweite Weg zu denselben Knoepfen von selbst weg.
GESETZT WIRD ERST NACH DER ANTWORT, deshalb weiter unten. */
window.anrufStarten = starten;
window.anrufEreignis = ereignis;
window.anrufLaeuft = () => !!anruf;
window.anrufBereit = () => adressen.length > 0 || nurGleichesNetz;
(async () => {
await adressenAuffrischen();
/* ==== ERST FRAGEN, WER MAN IST -- DANN ERST ANRUFEN =============
DIE REIHENFOLGE WAR DER FEHLER (19.09.2026). Hier stand
`adressenAuffrischen()` VOR der Frage nach der eigenen Person.
Fuer die Community antwortet dieser Weg seit heute mit 404, und
der Browser schreibt das in die Konsole, BEVOR JavaScript
irgendetwas abfangen kann -- ein roter Eintrag bei jedem
Seitenaufruf, der nichts bedeutet.
Ein Fehler, der immer kommt, macht den naechsten ECHTEN Fehler
unsichtbar. Dieselbe Regel wie bei der Warnung, die immer kommt.
Jetzt sagt `/api/ich` vorher, ob es hier ueberhaupt ein Telefon
gibt -- und wenn nicht, wird gar nicht erst gefragt. Die Angabe
kommt aus derselben Funktion wie der Riegel im Server. */
try {
const i = await fetch('/workspace/api/ich', { credentials: 'same-origin' });
if (i.ok) ichId = (await i.json())?.id ?? null;
if (i.ok) {
const d = await i.json();
ichId = d?.id ?? null;
if (d && d.darf_anrufen === false) telefonGehtHier = false;
}
} catch { /* der Chat holt es ohnehin auch */ }
if (!telefonGehtHier) {
/* Und die Funktion gar nicht erst anbieten: Der Chat baut seine
Knoepfe nur, wenn es sie gibt. */
delete window.anrufStarten;
return;
}
await adressenAuffrischen();
/* ---- KLINGELT GERADE ETWAS? (18.09.2026) ----------------------
Filipe: „wenn ich vanvan anrufe kriegt sie nur eine
@@ -802,6 +852,7 @@
ERST NACH `ichId`: Die Klingel prueft, ob der Anruf vom eigenen
Konto kommt. Ohne die Nummer wuerde sie beim eigenen Anruf in
einem zweiten Fenster mitlaeuten. */
if (!telefonGehtHier) { void gruppeMax; return; }
try {
const a = await fetch('/workspace/api/anruf/offen', { credentials: 'same-origin' });
if (a.ok) {
+182
View File
@@ -57,6 +57,14 @@
let angeheftet = [];
let darfAnheftenHier = false;
let darfKanal = false;
/* Was der Server ueber die Nachtruhe sagt. `null` heisst: Diese
Person hat mit dem Treff nichts zu tun -- dann gibt es auch
nichts anzuzeigen. */
let treffStand = null;
/* `null` heisst "der Server hat nichts gesagt" -- dann bleibt alles
wie bisher. Erst ein ausdrueckliches `false` nimmt die Knoepfe weg. */
let darfAnrufen = null;
let taktUhr = null;
let kanalFrei = [];
let neuArt = 'gespraech'; // 'gespraech' | 'kanal'
let kanalUmbau = null; // Raumnummer, wenn die Besetzung geaendert wird
@@ -114,6 +122,21 @@
function anrufKnoepfeSetzen() {
const ziel = document.querySelector('.chat__kopf-knoepfe');
if (!ziel || !window.anrufStarten) return;
/* DIE COMMUNITY TELEFONIERT NICHT (19.09.2026). Filipe: "die
sollen nicht anrufen koennen. nur schreiben."
Der Riegel sitzt am Anruf-Router; hier faellt nur der Knopf weg.
BEIDES ZUSAMMEN: Ohne den Riegel waere es kosmetisch, ohne den
fehlenden Knopf staende ein Angebot da, das mit 404 antwortet.
`=== false` und nicht `!`: Fehlt die Angabe (aeltere Antwort des
Servers), bleibt alles wie bisher -- ein fehlendes Feld soll
niemandem das Telefon wegnehmen. */
if (darfAnrufen === false) {
document.getElementById('anruf-ton-start')?.remove();
document.getElementById('anruf-video-start')?.remove();
return;
}
let ton = document.getElementById('anruf-ton-start');
let video = document.getElementById('anruf-video-start');
if (!ton) {
@@ -182,6 +205,153 @@
z.dataset.art = t ? art : 'fehler';
};
/* ==== DIE NACHTRUHE DES TREFFS (19.09.2026) ==========================
Filipe: "nach mitternacht bis morgens 6 uhr keiner schreiben kann
damit auch die privatsphaere beruecksichtig wird ueber nacht."
DREI DINGE, DIE DIESE FUNKTION TUT, UND EINES, DAS SIE NICHT TUT:
1. Sie sagt es, BEVOR jemand tippt -- ueber dem Feld, nicht
darunter.
2. Sie warnt VORHER. Unter 15 Minuten steht da, wie lange noch.
Ohne das waere die Nachtruhe ein Abbruch mitten im Satz.
3. Sie schaltet das Feld aus, wenn zu ist -- sichtbar, nicht
heimlich.
SIE ENTSCHEIDET NICHTS. Ob geschrieben werden darf, sagt der
Server, und zwar auch dann noch einmal, wenn hier alles offen
aussieht. Ein ausgegrautes Feld ist eine Hoeflichkeit, kein
Schloss.
NUR IM TREFF. In einem Teamraum passiert hier gar nichts -- dort
gibt es keine Nachtruhe, dort arbeiten Leute nachts. */
const NACHTRUHE_VORWARNUNG = 15; // Minuten
function nachtruheZeigen() {
const band = $('nachtruhe');
const feld = $('text');
const senden = $('senden');
if (!band) return;
const imTreff = !!offen && offen.art === 'kanal' && offen.kategorie === 'treff';
if (!imTreff || !treffStand) {
band.hidden = true;
delete band.dataset.lage;
if (feld) { feld.disabled = false; feld.placeholder = 'Nachricht'; }
if (senden) senden.disabled = false;
const k = $('anhang-auf');
if (k) { k.disabled = false; k.title = 'Bild oder PDF anhängen'; }
return;
}
const zu = !!treffStand.zu;
const gleich = !zu && treffStand.minuten <= NACHTRUHE_VORWARNUNG;
band.hidden = !(zu || gleich);
band.dataset.lage = zu ? 'zu' : 'gleich';
if (zu) {
$('nachtruhe-text').textContent = treffStand.satz
+ ` (noch ${stundenSatz(treffStand.minuten)})`;
} else if (gleich) {
$('nachtruhe-text').textContent =
`In ${stundenSatz(treffStand.minuten)} macht der Treff zu. `
+ `Ab ${String(treffStand.bis).padStart(2, '0')}:00 Uhr geht es weiter.`;
}
/* DIE BUEROKLAMMER GEHOERT DAZU (19.09.2026, beim Ansehen des
Bildschirmfotos aufgefallen).
Der Server lehnt Anhaenge nachts ebenso ab wie Nachrichten --
der Knopf stand aber offen da. Wer ihn drueckt, waehlt ein Bild
aus, wartet auf den Upload und bekommt dann eine Absage. Das ist
schlimmer als ein ausgegrauter Knopf: Die Arbeit ist schon
gemacht. */
const klammer = $('anhang-auf');
if (klammer) {
klammer.disabled = zu;
klammer.title = zu
? `Nachts keine Anhänge – ab ${String(treffStand.bis).padStart(2, '0')}:00 Uhr wieder`
: 'Bild oder PDF anhängen';
}
if (feld) {
feld.disabled = zu;
/* DER PLATZHALTER SAGT DEN GRUND. Ein graues Feld ohne Text
sieht aus wie ein Fehler -- und man tippt trotzdem hinein. */
feld.placeholder = zu
? `Der Treff schläft – ab ${String(treffStand.bis).padStart(2, '0')}:00 Uhr wieder`
: 'Nachricht';
}
if (senden) senden.disabled = zu;
}
/* ==== DER TAKT: die Minuten laufen wirklich ab ======================
Ohne ihn stuende die Zahl vom Seitenaufruf da, bis jemand neu
laedt. Ein Chat steht aber stundenlang offen -- "noch 12 Minuten"
waere um drei Uhr nachts immer noch zu lesen.
EINMAL JE MINUTE, und er zaehlt SELBST herunter, statt den Server
zu fragen. Eine Abfrage je Minute je offenem Fenster waere Last
ohne Gegenwert: Die Minuten vergehen hier genauso wie dort.
BEIM WECHSEL WIRD GEFRAGT. Laeuft der Zaehler auf null, holt er den
Stand EINMAL frisch -- das ist der Moment, in dem sich wirklich
etwas aendert, und der einzige, in dem die eigene Rechnung
danebenliegen koennte (Zeitumstellung, schlafender Rechner, eine
geaenderte Einstellung).
NUR SOLANGE ES ETWAS ZU ZAEHLEN GIBT. Wer mit dem Treff nichts zu
tun hat, bekommt keinen Zeitgeber. */
function taktStarten() {
if (taktUhr) { clearInterval(taktUhr); taktUhr = null; }
if (!treffStand) return;
taktUhr = setInterval(async () => {
if (!treffStand) { clearInterval(taktUhr); taktUhr = null; return; }
treffStand.minuten -= 1;
if (treffStand.minuten > 0) { nachtruheZeigen(); return; }
/* Der Wechsel. Einmal fragen, dann weiterzaehlen. */
try {
const a = await hole('/workspace/api/chat/raeume');
if (a.ok) {
const d = await a.json();
treffStand = d.treff || null;
}
} catch { /* kein Netz -- beim naechsten Mal wieder */ }
/* Faellt die Antwort aus, wird selbst umgeschaltet: Lieber eine
Minute ungenau als eine Stunde falsch. */
if (treffStand && treffStand.minuten <= 0) {
treffStand.zu = !treffStand.zu;
treffStand.minuten = (treffStand.zu
? (treffStand.bis - treffStand.ab + 24) % 24
: (treffStand.ab - treffStand.bis + 24) % 24) * 60;
}
nachtruheZeigen();
}, 60_000);
}
/* Ein schlafender Rechner haelt `setInterval` an. Wer sein Handy
nach zwei Stunden wieder aufklappt, haette sonst eine Zahl von
vor zwei Stunden vor sich -- und zwar eine, die nach oben zaehlt.
Beim Zurueckkommen wird deshalb einmal gefragt. */
document.addEventListener('visibilitychange', () => {
if (document.hidden || !treffStand) return;
raeumeLaden().catch(() => {});
});
/** „3 Minuten" / „2 Stunden 10 Minuten" -- nie „130 Minuten".
*
* Wer „noch 347 Minuten" liest, rechnet im Kopf und liegt daneben.
* Die Zahl soll eine Auskunft sein, keine Aufgabe. */
function stundenSatz(min) {
const m = Math.max(0, Math.round(min));
if (m < 60) return `${m} Minute${m === 1 ? '' : 'n'}`;
const st = Math.floor(m / 60), rest = m % 60;
return `${st} Stunde${st === 1 ? '' : 'n'}`
+ (rest ? ` ${rest} Minute${rest === 1 ? '' : 'n'}` : '');
}
/* ---------- Die Gesprächsliste ------------------------------------------- */
async function raeumeLaden() {
@@ -191,6 +361,14 @@
const daten = await a.json();
raeume = daten.raeume || [];
darfKanal = !!daten.darf_kanal;
/* DER SERVER SAGT, OB DER TREFF SCHLAEFT -- hier wird nichts
nachgerechnet. Eine zweite Rechnung im Browser waere die, die
bei der naechsten Zeitumstellung anders herauskommt als die im
Server; dann sagt die Seite "offen" und der Server lehnt ab. */
treffStand = daten.treff || null;
if (typeof daten.darf_anrufen === 'boolean') darfAnrufen = daten.darf_anrufen;
nachtruheZeigen();
taktStarten();
kanalFrei = daten.kanal_frei || [];
/* Der Knopf erscheint nur, wenn der Server etwas zu bieten hat.
Ein Knopf, der beim Druecken "darfst du nicht" sagt, ist eine
@@ -401,6 +579,10 @@
Runden; siehe die Route dazu im Server. */
$('kanal-leute').hidden = !(darfKanal && offen.art === 'kanal');
$('schreiben').hidden = false;
/* JEDES MAL BEIM OEFFNEN. Der Stand haengt am RAUM (nur der Treff
hat eine Nachtruhe) -- wer aus einem Teamraum in den Treff
wechselt, muss das Band sehen, ohne die Seite neu zu laden. */
nachtruheZeigen();
$('verlauf-leer').hidden = true;
$('chat').dataset.ansicht = 'verlauf';
/* Erst jetzt gibt es etwas zum Wegräumen. */
+27
View File
@@ -1364,6 +1364,33 @@
* ein Rahmen um nichts -- eine mit einer vollen Seite dagegen ist
* genau richtig, und welche das ist, steht vorher nicht fest. */
function tagesblickPruefen() {
/* ==== DER WEG ZUM KALENDER NUR FUER DIE, DIE IHN HABEN ==========
Der Verweis „Kalender" steht fest im HTML des Tagesblicks. Fuer
ein Mitglied der Community fuehrt er ins Leere -- es hat keine
Kalenderkachel und die Seite steht ihm nicht offen. Der Klick
landet wieder auf der Startseite: keine Meldung, kein Grund,
nichts passiert.
Gefunden von pruef-community-sicht, die genau danach sucht --
und zwar schon VOR den Aenderungen dieses Tages. Aufgefallen ist
es nur deshalb, weil dieselbe Pruefung heute noch einen zweiten
toten Weg fand, den ich selbst gebaut hatte.
ABGELEITET, NICHT ENTSCHIEDEN: Wer die Kachel hat, hat den Weg.
Eine zweite Liste („diese Rollen duerfen den Kalender") waere
die, die beim naechsten Umbau auseinanderlaeuft. */
const kalenderWeg = document.querySelector('.tagesblick__mehr[href^="kalender"]');
if (kalenderWeg && ich) {
const hatKalender = [...(ich.bereiche || []), ...(ich.bereiche_zusatz || [])]
.some((k) => String(k?.ziel || "").startsWith("kalender.html"));
/* `ich.bereiche === null` heisst "nimm die Liste aus dem
Browser" -- dann gilt die Agenturliste, und dort gibt es den
Kalender. Nur bei einer ECHTEN Liste ohne Kalender wird der
Weg entfernt. */
if (Array.isArray(ich.bereiche) && !hatKalender) kalenderWeg.remove();
}
const etwasLinks = hinweise.length > 0;
const etwasRechts = $('tagestermine').childElementCount > 0;
/* Solange die Termine noch unterwegs sind, wird nicht versteckt --
+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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -291,10 +291,10 @@
</form>
</dialog>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/glocke.js?v=202609191035" defer></script>
<script src="assets/js/aufgaben.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/glocke.js?v=202609191147" defer></script>
<script src="assets/js/aufgaben.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/automation.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/automation.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -168,10 +168,10 @@
</main>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/glocke.js?v=202609191035" defer></script>
<script src="assets/js/automation.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/glocke.js?v=202609191147" defer></script>
<script src="assets/js/automation.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -105,9 +105,9 @@
</main>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/befinden.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/befinden.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -251,14 +251,14 @@
</main>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/glocke.js?v=202609191035" defer></script>
<script src="assets/js/checkliste.js?v=202609191035" defer></script>
<script src="assets/js/ampel.js?v=202609191035" defer></script>
<script src="assets/js/schulung.js?v=202609191035" defer></script>
<script src="assets/js/vorlagen.js?v=202609191035" defer></script>
<script src="assets/js/bereich.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/glocke.js?v=202609191147" defer></script>
<script src="assets/js/checkliste.js?v=202609191147" defer></script>
<script src="assets/js/ampel.js?v=202609191147" defer></script>
<script src="assets/js/schulung.js?v=202609191147" defer></script>
<script src="assets/js/vorlagen.js?v=202609191147" defer></script>
<script src="assets/js/bereich.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- haus.css MUSS die letzte Stilvorlage sein - siehe treff-regeln.html. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</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=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/auskunft.js?v=202609191035" defer></script>
<script src="assets/js/bewerben.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/auskunft.js?v=202609191147" defer></script>
<script src="assets/js/bewerben.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- haus.css MUSS die letzte Stilvorlage sein - siehe treff-regeln.html. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -80,10 +80,10 @@
</main>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/auskunft.js?v=202609191035" defer></script>
<script src="assets/js/bewerbungen.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/auskunft.js?v=202609191147" defer></script>
<script src="assets/js/bewerbungen.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/calls.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/calls.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -89,11 +89,11 @@
</main>
<script src="assets/js/ki.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/glocke.js?v=202609191035" defer></script>
<script src="assets/js/calls.js?v=202609191035" defer></script>
<script src="assets/js/ki.js?v=202609191147" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/glocke.js?v=202609191147" defer></script>
<script src="assets/js/calls.js?v=202609191147" defer></script>
</body>
</html>
+40 -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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/chat.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/chat.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -211,6 +211,32 @@
auf etwas geantwortet wird. Ein leerer Kasten ueber dem
Eingabefeld waere jeden Tag im Weg fuer den Fall, dass man
ihn vielleicht braucht. -->
<!-- ==================================================================
DIE NACHTRUHE DES TREFFS (19.09.2026)
Filipe: "nach mitternacht bis morgens 6 uhr keiner schreiben
kann damit auch die privatsphaere beruecksichtig wird ueber
nacht."
SIE STEHT UEBER DEM EINGABEFELD, nicht darunter und nicht in
einer Fusszeile: Wer schreiben will, sieht zuerst, ob er es
kann. Ein Hinweis unter dem Feld liest man erst, nachdem man
getippt hat -- und dann ist die Arbeit schon gemacht.
ZWEI ZUSTAENDE, und der zweite ist der wichtigere:
zu Der Chat schlaeft. Das Feld ist aus.
gleich Es sind noch weniger als 15 Minuten. Das Feld
bleibt an, aber man weiss Bescheid. Ohne diese
Vorwarnung waere die Nachtruhe ein Abbruch mitten
im Satz.
=============================================================== -->
<p class="chat__nachtruhe" id="nachtruhe" hidden role="status" aria-live="polite">
<span class="chat__nachtruhe-zeichen" aria-hidden="true">
<svg viewBox="0 0 24 24"><path d="M20 14.5A8.5 8.5 0 0 1 9.5 4 8.5 8.5 0 1 0 20 14.5z"/></svg>
</span>
<span class="chat__nachtruhe-text" id="nachtruhe-text"></span>
</p>
<form class="chat__eingabe" id="schreiben" hidden>
<div class="antwort-leiste" id="antwort-leiste" hidden>
<span class="antwort-leiste__strich" aria-hidden="true"></span>
@@ -341,15 +367,15 @@
</form>
</dialog>
<script src="assets/js/bereiche.js?v=202609191035"></script>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035"></script>
<script src="assets/js/glocke.js?v=202609191035"></script>
<script src="assets/js/bereiche.js?v=202609191147"></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147"></script>
<script src="assets/js/glocke.js?v=202609191147"></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=202609191035"></script>
<script src="assets/js/chat.js?v=202609191035"></script>
<script src="assets/js/anruf.js?v=202609191147"></script>
<script src="assets/js/chat.js?v=202609191147"></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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/content.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/content.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -179,12 +179,12 @@
</main>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/glocke.js?v=202609191035" defer></script>
<script src="assets/js/checkliste.js?v=202609191035" defer></script>
<script src="assets/js/vorlagen.js?v=202609191035" defer></script>
<script src="assets/js/content.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/glocke.js?v=202609191147" defer></script>
<script src="assets/js/checkliste.js?v=202609191147" defer></script>
<script src="assets/js/vorlagen.js?v=202609191147" defer></script>
<script src="assets/js/content.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/crew-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/crew-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/crew-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/crew-haus.css?v=202609191147" />
</head>
<body class="gate gate--crew">
@@ -339,6 +339,6 @@
</main>
</div>
<script src="assets/js/gate.js?v=202609191035" defer></script>
<script src="assets/js/gate.js?v=202609191147" defer></script>
</body>
</html>
+4 -4
View File
@@ -22,25 +22,25 @@
},
"icons": [
{
"src": "/assets/img/app-symbole/crew-192.png?v=202609191035",
"src": "/assets/img/app-symbole/crew-192.png?v=202609191147",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/img/app-symbole/crew-512.png?v=202609191035",
"src": "/assets/img/app-symbole/crew-512.png?v=202609191147",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609191035",
"src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609191147",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609191035",
"src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609191147",
"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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/dateien.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/dateien.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -108,10 +108,10 @@
</main>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/glocke.js?v=202609191035" defer></script>
<script src="assets/js/dateien.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/glocke.js?v=202609191147" defer></script>
<script src="assets/js/dateien.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -106,9 +106,9 @@
</main>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/entwicklung.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/entwicklung.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/hilfe.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/hilfe.css?v=202609191147" />
</head>
<body class="start">
@@ -141,9 +141,9 @@
</main>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/hilfe.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/hilfe.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
</head>
<body class="gate">
@@ -401,6 +401,6 @@
</main>
</div>
<script src="assets/js/gate.js?v=202609191035" defer></script>
<script src="assets/js/gate.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/kalender.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/kalender.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -377,11 +377,11 @@
</main>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/glocke.js?v=202609191035" defer></script>
<script src="assets/js/kalender.js?v=202609191035" defer></script>
<script src="assets/js/kalender-abo.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/glocke.js?v=202609191147" defer></script>
<script src="assets/js/kalender.js?v=202609191147" defer></script>
<script src="assets/js/kalender-abo.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/leistung.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/leistung.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -444,10 +444,10 @@
</form>
</dialog>
<script src="assets/js/wahl.js?v=202609191035"></script>
<script src="assets/js/bereiche.js?v=202609191035"></script>
<script src="assets/js/kopf.js?v=202609191035"></script>
<script src="assets/js/glocke.js?v=202609191035"></script>
<script src="assets/js/leistung.js?v=202609191035"></script>
<script src="assets/js/wahl.js?v=202609191147"></script>
<script src="assets/js/bereiche.js?v=202609191147"></script>
<script src="assets/js/kopf.js?v=202609191147"></script>
<script src="assets/js/glocke.js?v=202609191147"></script>
<script src="assets/js/leistung.js?v=202609191147"></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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/personen.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/personen.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -423,10 +423,10 @@
</main>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/glocke.js?v=202609191035" defer></script>
<script src="assets/js/personen.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/glocke.js?v=202609191147" defer></script>
<script src="assets/js/personen.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -214,11 +214,11 @@
</main>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/glocke.js?v=202609191035" defer></script>
<script src="assets/js/steckbrief.js?v=202609191035" defer></script>
<script src="assets/js/profil.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/glocke.js?v=202609191147" defer></script>
<script src="assets/js/steckbrief.js?v=202609191147" defer></script>
<script src="assets/js/profil.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</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=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/rechte.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/rechte.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/report.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/report.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -106,11 +106,11 @@
</main>
<script src="assets/js/ki.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/glocke.js?v=202609191035" defer></script>
<script src="assets/js/report.js?v=202609191035" defer></script>
<script src="assets/js/ki.js?v=202609191147" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/glocke.js?v=202609191147" defer></script>
<script src="assets/js/report.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/scouting.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/scouting.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -173,10 +173,10 @@
</div>
</div>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/glocke.js?v=202609191035" defer></script>
<script src="assets/js/scouting.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/glocke.js?v=202609191147" defer></script>
<script src="assets/js/scouting.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/heim.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -678,10 +678,10 @@
</p>
</main>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/glocke.js?v=202609191035" defer></script>
<script src="assets/js/start.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/glocke.js?v=202609191147" defer></script>
<script src="assets/js/start.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/startcheck.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/startcheck.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -85,11 +85,11 @@
</main>
<script src="assets/js/ki.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/glocke.js?v=202609191035" defer></script>
<script src="assets/js/startcheck.js?v=202609191035" defer></script>
<script src="assets/js/ki.js?v=202609191147" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/glocke.js?v=202609191147" defer></script>
<script src="assets/js/startcheck.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -150,11 +150,11 @@
</main>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/auskunft.js?v=202609191035" defer></script>
<script src="assets/js/glocke.js?v=202609191035" defer></script>
<script src="assets/js/steckbrief.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/auskunft.js?v=202609191147" defer></script>
<script src="assets/js/glocke.js?v=202609191147" defer></script>
<script src="assets/js/steckbrief.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -111,9 +111,9 @@
</main>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/talente.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/talente.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/team.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/team.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -110,10 +110,10 @@
</main>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/glocke.js?v=202609191035" defer></script>
<script src="assets/js/team.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/glocke.js?v=202609191147" defer></script>
<script src="assets/js/team.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/team.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/team.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</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=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/teamlage.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/teamlage.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -112,9 +112,9 @@
</main>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/teilen.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/teilen.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -126,9 +126,9 @@
</main>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/treff-moderation.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/treff-moderation.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</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=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/auskunft.js?v=202609191035" defer></script>
<script src="assets/js/treff-regeln.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/auskunft.js?v=202609191147" defer></script>
<script src="assets/js/treff-regeln.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<!-- Formular- und Dialogstile: seit dem 07.09.2026 steht hier der
Dialog "Creator anlegen". -->
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/uebersicht.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/uebersicht.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -149,11 +149,11 @@
</main>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/glocke.js?v=202609191035" defer></script>
<script src="assets/js/uebersicht.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/glocke.js?v=202609191147" defer></script>
<script src="assets/js/uebersicht.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/unsere-seiten.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/unsere-seiten.css?v=202609191147" />
</head>
<body class="start">
@@ -217,9 +217,9 @@
</main>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/unsere-seiten.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/unsere-seiten.js?v=202609191147" defer></script>
</body>
</html>
+13 -13
View File
@@ -7,15 +7,15 @@
<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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/werdegang.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/werdegang.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -131,9 +131,9 @@
</main>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/werdegang.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/werdegang.js?v=202609191147" 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=202609191035" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191035" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/wissen.css?v=202609191035" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191035" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609191147" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609191147" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/start.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/wissen.css?v=202609191147" />
<link rel="stylesheet" href="assets/css/module.css?v=202609191147" />
<!-- 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=202609191035" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609191147" />
</head>
<body class="start">
@@ -190,10 +190,10 @@
</main>
<script src="assets/js/wahl.js?v=202609191035" defer></script>
<script src="assets/js/bereiche.js?v=202609191035" defer></script>
<script src="assets/js/kopf.js?v=202609191035" defer></script>
<script src="assets/js/glocke.js?v=202609191035" defer></script>
<script src="assets/js/wissen.js?v=202609191035" defer></script>
<script src="assets/js/wahl.js?v=202609191147" defer></script>
<script src="assets/js/bereiche.js?v=202609191147" defer></script>
<script src="assets/js/kopf.js?v=202609191147" defer></script>
<script src="assets/js/glocke.js?v=202609191147" defer></script>
<script src="assets/js/wissen.js?v=202609191147" defer></script>
</body>
</html>