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:
+127
-14
@@ -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;
|
||||
|
||||
@@ -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
@@ -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."
|
||||
|
||||
|
||||
@@ -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; };
|
||||
|
||||
/* =====================================================================
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 });
|
||||
};
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user