Vermittlungsserver: coturn mit Zugangsdaten, die verfallen

Damit Anrufe auch in Netzen zustande kommen, die keine direkte
Verbindung zulassen (15-25 % der Faelle). coturn ist installiert,
steht aber still, bis die Konfiguration liegt -- ein coturn mit
Werkseinstellung ist ein offenes Relais.

KEIN FESTES PASSWORT. Es laege dauerhaft im Browser jedes
Team-Mitglieds und liesse sich nie entziehen. Der Server rechnet
stattdessen bei jeder Abfrage Zugangsdaten, die nach zwoelf Stunden
verfallen (server/workspace-turn.js, coturns `use-auth-secret`). Das
Geheimnis liegt in einer Datei, nicht in der Datenbank: einstellung-
Setzen() schreibt Werte ins Protokoll, und coturn braucht denselben
Wert ohnehin in /etc.

Die Oberflaeche frischt die Daten vor jedem Anruf auf. Der Chat ist
eine App, die tagelang offen bleibt -- wer nur beim Laden holt,
telefoniert am zweiten Tag ohne Vermittlung, und es faellt nicht auf:
Es scheitern nur die, die sie gebraucht haetten.

DIE WICHTIGSTE ZEILE DER KONFIGURATION ist die Sperrliste. Gemessen:
dreizehn Dienste lauschen auf diesem Server nur oertlich, darunter
Caddys Verwaltung auf 127.0.0.1:2019 -- wer sie erreicht, kann jede
Website umleiten. Ohne Sperrliste waere der Vermittlungsserver die
Tuer dorthin, und die Anfrage saehe fuer Caddy aus wie von localhost.

Geprueft: pruef-anruf.mjs 51 -> 73 Pruefungen. Gegenprobe (Geheimnis
als Passwort ausliefern + fremde Zugangsdaten ueberschreiben) macht
genau 5 rot, darunter "das Geheimnis steht NIRGENDS in der Antwort".

tools/turn-probelauf.sh beweist am echten coturn, was ein fester
Vergleichswert nicht kann: dass coturn unsere Rechnung akzeptiert.
Beide Seiten koennten sonst konsequent falsch rechnen und jede
Pruefung waere gruen. Seine eigene Gegenprobe war zweimal zu Recht
rot -- der erste Aufbau mass wegen `-y` gar nicht das Ziel, das er zu
messen behauptete, sondern coturns eingebauten Loopback-Schutz.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-18 15:22:14 +02:00
co-authored by Claude Opus 5
parent 5bbf1f2019
commit 775c4b4207
40 changed files with 1348 additions and 413 deletions
+127
View File
@@ -0,0 +1,127 @@
# =====================================================================
# coturn - Vermittlungsserver fuer die Anrufe im Creator Workspace
# Angelegt 18.09.2026. Gehoert nach /etc/turnserver.conf
#
# WARUM DIESE DATEI IM REPO LIEGT
# Sie steht auf dem Server unter /etc und waere bei einem Neuaufbau weg,
# ohne dass es jemandem auffiele - der Anruf wuerde dann einfach bei
# jedem Fuenften nicht mehr klappen. Genau das ist bei RunOne mit den
# systemd-Dateien passiert; seitdem liegen solche Dateien versioniert.
#
# DAS GEHEIMNIS STEHT NICHT HIER DRIN.
# Es wird vom Einrichtungsbefehl erzeugt und unten angehaengt. Eine
# Vorlage mit Platzhalter waere eine Vorlage, in der irgendwann der
# Platzhalter stehen bleibt - im Haus schon zweimal passiert.
# =====================================================================
# --- Wo gelauscht wird -------------------------------------------------
# Ausdruecklich nur auf der oeffentlichen Adresse. Ohne diese Zeile
# lauscht coturn auf ALLEN Netzgeraeten - auch auf den vier
# Docker-Bruecken (172.17-172.20). Ein Vermittlungsserver, der im
# Docker-Netz erreichbar ist, ist eine Abkuerzung, die niemand braucht.
listening-ip=159.195.212.167
relay-ip=159.195.212.167
listening-port=3478
# Kein TLS in dieser Stufe: Dafuer braeuchte es ein Zertifikat und damit
# einen eigenen Namen im DNS. Ohne diese beiden Zeilen versucht coturn
# trotzdem, einen TLS-Lauscher zu oeffnen, und schreibt bei jedem Start
# eine Warnung ueber fehlende Zertifikate - eine Warnung, die immer
# kommt, liest nach drei Tagen niemand mehr.
no-tls
no-dtls
# --- Der Portbereich fuer die Weiterleitung ----------------------------
# Jedes weitergeleitete Gespraech belegt einen Port. Der Standard geht
# von 49152 bis 65535 - 16000 Ports, die alle in der Firewall offen sein
# muessten. Fuer ein Team dieser Groesse sind 100 reichlich: Sie
# reichen fuer 100 gleichzeitige Weiterleitungen, und die Firewall
# bleibt entsprechend eng.
min-port=49160
max-port=49260
# --- Wer den Server benutzen darf --------------------------------------
# Keine Benutzerliste, sondern ein gemeinsames Geheimnis: Der Workspace
# rechnet daraus Zugangsdaten, die nach zwoelf Stunden verfallen
# (server/workspace-turn.js). coturn rechnet dieselbe Zahl nach.
# Vorteil gegenueber festen Benutzern: Es gibt nichts abzugleichen, und
# ein Zugang, der nicht mehr gelten soll, gilt am naechsten Tag nicht
# mehr.
use-auth-secret
realm=dogfather-universe.com
# Wie lange eine einmal ausgestellte Anmeldung nachwirkt.
stale-nonce=600
# --- DIE WICHTIGSTEN ZEILEN DER GANZEN DATEI ---------------------------
# Ein TURN-Server leitet auf Zuruf Verkehr an eine Adresse weiter, die
# der Anrufer nennt. Ohne die folgenden Sperren darf er auch 127.0.0.1
# nennen - und dann steht dieser Server als Tuer vor allem, was auf
# diesem Rechner nur oertlich lauscht. Am 18.09.2026 gemessen: dreizehn
# Dienste, darunter
#
# 127.0.0.1:2019 die Verwaltung von Caddy (!) - wer sie erreicht,
# kann JEDE Website auf diesem Server umleiten
# 127.0.0.1:3000 Gitea 127.0.0.1:4000 VanVans Shop
# 127.0.0.1:4100 Workspace 127.0.0.1:4400 Buchhaltung
# 127.0.0.1:25 der Mailserver
#
# Fuer Caddy saehe eine so weitergeleitete Anfrage aus, als kaeme sie
# von diesem Rechner selbst. Diese Liste ist deshalb keine Haertung,
# sondern die Voraussetzung dafuer, dass der Dienst ueberhaupt laufen
# darf.
no-multicast-peers
denied-peer-ip=0.0.0.0-0.255.255.255
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=100.64.0.0-100.127.255.255
denied-peer-ip=127.0.0.0-127.255.255.255
denied-peer-ip=169.254.0.0-169.254.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.0.0.0-192.0.0.255
denied-peer-ip=192.0.2.0-192.0.2.255
denied-peer-ip=192.88.99.0-192.88.99.255
denied-peer-ip=192.168.0.0-192.168.255.255
denied-peer-ip=198.18.0.0-198.19.255.255
denied-peer-ip=198.51.100.0-198.51.100.255
denied-peer-ip=203.0.113.0-203.0.113.255
denied-peer-ip=240.0.0.0-255.255.255.255
# Dasselbe fuer IPv6 - sonst waere die Sperre oben eine halbe Sperre.
denied-peer-ip=::1
denied-peer-ip=64:ff9b::-64:ff9b::ffff:ffff
denied-peer-ip=::ffff:0.0.0.0-::ffff:255.255.255.255
denied-peer-ip=100::-100::ffff:ffff:ffff:ffff
denied-peer-ip=2001::-2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=2002::-2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff
# Weiterleitung nur fuer UDP. WebRTC braucht nichts anderes; TCP-Ziele
# waeren nur fuer jemanden interessant, der etwas anderes vorhat.
no-tcp-relay
# --- Grenzen gegen Missbrauch ------------------------------------------
# Falls Zugangsdaten doch einmal abhandenkommen, kostet es Bandbreite,
# keine Rechnung ohne Ende. 12 gleichzeitige Weiterleitungen je Person
# reichen fuer eine Vierergruppe mit Ton und Bild deutlich.
user-quota=12
total-quota=100
max-bps=3000000
# --- Die eingebaute Fernsteuerung bleibt aus ---------------------------
# coturn oeffnet sonst auf 127.0.0.1:5766 eine Verwaltungskonsole. Sie
# wird hier nicht gebraucht, und ein offener Verwaltungszugang, den
# niemand benutzt, faellt auch niemandem auf.
no-cli
# --- Protokoll ---------------------------------------------------------
# In das Systemprotokoll statt in eine eigene Datei: Damit wird es
# mitgedreht und laeuft nicht voll. Bewusst OHNE `verbose` - ein
# ausfuehrliches Protokoll eines Vermittlungsservers ist eine Liste,
# wer wann mit wem telefoniert hat.
syslog
simple-log
# Hilft Gegenstellen, die Pakete zuzuordnen; kostet nichts.
fingerprint
# --- Das gemeinsame Geheimnis wird unten angehaengt --------------------
+161 -1
View File
@@ -16,7 +16,7 @@
Aufrufen mit: node server/pruef-anruf.mjs Aufrufen mit: node server/pruef-anruf.mjs
===================================================================== */ ===================================================================== */
import { mkdtempSync, rmSync } from "node:fs"; import { mkdtempSync, rmSync, writeFileSync, unlinkSync } from "node:fs";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import { join } from "node:path"; import { join } from "node:path";
import { request as httpAnfrage } from "node:http"; import { request as httpAnfrage } from "node:http";
@@ -28,6 +28,11 @@ process.env.WORKSPACE_DB = join(ordner, "workspace.db");
process.env.PORT = String(PORT); process.env.PORT = String(PORT);
process.env.SITE_ACCESS_SECRET = "lokaler-test"; process.env.SITE_ACCESS_SECRET = "lokaler-test";
process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
/* Der Vermittlungsserver holt sein Geheimnis aus einer Datei. Hier muss
der Pfad UMGEBOGEN werden, bevor index.js geladen wird -- sonst
zeigte er auf die echte Datei unter /etc, und die Pruefung wuerde
entweder am echten Betrieb messen oder gar nichts sehen koennen. */
process.env.TURN_GEHEIMNIS_DATEI = join(ordner, "turn.geheimnis");
const express = (await import("express")).default; const express = (await import("express")).default;
const ec = express.response.cookie; const ec = express.response.cookie;
@@ -268,6 +273,161 @@ melde("=== 6. Die Verbindungsadressen ===");
"danach ist die Liste wieder leer -- der naechste Abschnitt faengt sauber an"); "danach ist die Liste wieder leer -- der naechste Abschnitt faengt sauber an");
} }
/* =======================================================================
6b. DIE ZUGANGSDATEN ZUM VERMITTLUNGSSERVER
Hier steht die Frage, an der sich entscheidet, ob coturn ein Dienst
fuer uns ist oder eine offene Weiterleitung fuer jeden: Das
Geheimnis, aus dem die Zugangsdaten gerechnet werden, darf den
Browser NIE erreichen. Wer es haette, koennte sich selbst beliebig
viele Zugaenge ausstellen -- dauerhaft, ohne Konto, ueber unsere
IP-Adresse.
Deshalb wird hier nicht nur geprueft, dass Zugangsdaten ANKOMMEN,
sondern vor allem, dass das Geheimnis es NICHT tut -- und zwar im
rohen Antworttext, nicht im ausgewerteten Objekt. Ein Feld, an das
niemand denkt, faellt beim Auswerten nicht auf.
======================================================================= */
melde("");
melde("=== 6b. Zugangsdaten fuer die Vermittlung ===");
{
const turn = await import("./workspace-turn.js");
const datei = process.env.TURN_GEHEIMNIS_DATEI;
/* Erfunden, nur fuer diesen Lauf, und am Ende wieder weg. Lang genug,
dass die Laengenpruefung nicht zufaellig anschlaegt. */
const GEHEIM = "pruef-geheimnis-nur-fuer-diesen-lauf-0123456789";
/* --- Die drei Ausgaenge beim Lesen ------------------------------- */
{
const g = turn.turnGeheimnisLesen(datei);
ok(g.wert === "" && g.grund === "fehlt",
`ohne Datei sagt es "fehlt" statt stillschweigend nichts (${g.grund})`);
}
{
writeFileSync(datei, "zu-kurz");
const g = turn.turnGeheimnisLesen(datei);
ok(g.wert === "" && g.grund === "zu_kurz",
`ein zu kurzes Geheimnis wird abgelehnt, nicht benutzt (${g.grund})`);
}
{
writeFileSync(datei, " \n");
const g = turn.turnGeheimnisLesen(datei);
ok(g.wert === "" && g.grund === "leer", `eine leere Datei meldet "leer" (${g.grund})`);
}
{
writeFileSync(datei, GEHEIM + "\n");
const g = turn.turnGeheimnisLesen(datei);
ok(g.wert === GEHEIM && g.grund === "",
"mit Datei kommt das Geheimnis an (Zeilenumbruch abgeschnitten)");
}
/* --- Ein WECHSEL muss auffallen ---------------------------------- */
{
writeFileSync(datei, GEHEIM + "-zweite-fassung-mit-anderer-laenge");
const g = turn.turnGeheimnisLesen(datei);
ok(g.wert.endsWith("-zweite-fassung-mit-anderer-laenge"),
"wird die Datei getauscht, wird der neue Wert gelesen (kein Festhaengen)");
writeFileSync(datei, GEHEIM + "\n");
}
/* --- Die Rechnung ------------------------------------------------ */
{
/* Eine FESTE Zeit als Eingabe, nicht die Wanduhr: Sonst waere die
Pruefung eine Zeitbombe, die irgendwann etwas anderes misst. */
const JETZT = 1_700_000_000_000;
const z = turn.turnZugang("w7", GEHEIM, JETZT);
const [ablaufStr, kennung] = z.username.split(":");
ok(kennung === "w7", `im Benutzernamen steht die Kennung (${kennung})`);
ok(Number(ablaufStr) === Math.floor(JETZT / 1000) + turn.TURN_GUELTIG_STUNDEN * 3600,
`und davor der Ablauf, genau ${turn.TURN_GUELTIG_STUNDEN} Stunden spaeter`);
/* FESTER VERGLEICHSWERT, ausgerechnet und nicht behauptet. Er
beweist NICHT, dass coturn ihn akzeptiert -- das beweist nur ein
Versuch am laufenden coturn (turnutils_uclient). Er beweist,
dass sich die Rechenvorschrift nicht unbemerkt aendert; genau
das wuerde jeden Anruf ueber die Vermittlung scheitern lassen,
ohne dass irgendwo ein Fehler steht. */
ok(z.credential === "9AbMBJeTDL6uebcR6KRpYVTFwt8=",
`das Passwort ist die bekannte HMAC-SHA1-Rechnung (${z.credential})`);
const nochmal = turn.turnZugang("w7", GEHEIM, JETZT);
ok(nochmal.credential === z.credential, "dieselbe Eingabe ergibt dasselbe Passwort");
const anderer = turn.turnZugang("w8", GEHEIM, JETZT);
ok(anderer.credential !== z.credential,
"eine andere Person bekommt ein anderes Passwort");
const spaeter = turn.turnZugang("w7", GEHEIM, JETZT + 60_000);
ok(spaeter.credential !== z.credential,
"und eine Minute spaeter ein anderes -- die Daten verfallen wirklich");
}
/* --- Wer bekommt Zugangsdaten und wer nicht ---------------------- */
{
const liste = [
{ urls: "stun:turn.dogfather-universe.com:3478" },
{ urls: "turn:turn.dogfather-universe.com:3478" },
{ urls: "turn:fremder-dienst.example:3478", username: "fremd", credential: "eigenes" },
];
const e = turn.mitZugangsdaten(liste, "w7", 1_700_000_000_000, datei);
ok(!e.adressen[0].credential, "ein STUN-Eintrag bekommt keine Zugangsdaten");
ok(!!e.adressen[1].credential && e.adressen[1].username.includes(":w7"),
"unser TURN-Eintrag bekommt gerechnete");
ok(e.adressen[2].credential === "eigenes" && e.adressen[2].username === "fremd",
"ein fremder Dienst behaelt seine eigenen -- wir ueberschreiben nichts");
ok(e.turn_gesamt === 2 && e.turn_bereit === 2,
`und die Zahlen stimmen (${e.turn_bereit} von ${e.turn_gesamt} bereit)`);
}
{
/* OHNE Geheimnis: Der Eintrag bleibt stehen, aber es wird gesagt. */
unlinkSync(datei);
const e = turn.mitZugangsdaten(
[{ urls: "turn:turn.dogfather-universe.com:3478" }], "w7", Date.now(), datei);
ok(e.turn_gesamt === 1 && e.turn_bereit === 0 && e.grund === "fehlt",
`ohne Geheimnis ist nichts bereit, und der Grund steht dabei (${e.grund})`);
writeFileSync(datei, GEHEIM + "\n");
}
/* --- Und jetzt ueber die Schnittstelle, wie der Browser sie sieht - */
{
await roh("/workspace/api/anruf/adressen", HAUS, { cookie: kDogi }, {
adressen: [{ urls: "turn:turn.dogfather-universe.com:3478" }],
}, "PUT");
const a = await roh("/workspace/api/anruf/adressen", CREW, { cookie: kModi });
const r = json(a);
ok(r.turn_gesamt === 1 && r.turn_bereit === 1,
`der Modi bekommt einen einsatzbereiten Eintrag (${r.turn_bereit}/${r.turn_gesamt})`);
ok(!!r.adressen?.[0]?.credential && !!r.adressen?.[0]?.username,
"mit Benutzername und Passwort");
/* DIE WICHTIGSTE PRUEFUNG DES ABSCHNITTS. Im ROHEN Text gesucht,
nicht im ausgewerteten Objekt. */
ok(!a.text.includes(GEHEIM),
"und das Geheimnis selbst steht NIRGENDS in der Antwort");
/* Jeder bekommt EIGENE Zugangsdaten -- im coturn-Protokoll steht
damit die Nummer, nicht ein Sammelkonto. */
const rDogi = json(await roh("/workspace/api/anruf/adressen", HAUS, { cookie: kDogi }));
ok(rDogi.adressen?.[0]?.username !== r.adressen?.[0]?.username,
"DogFather und der Modi bekommen verschiedene Zugangsdaten");
/* Der Ablauf muss fuer den Browser LESBAR sein -- darauf beruht das
Auffrischen vor jedem Anruf in anruf.js. */
const ablauf = Number(String(r.adressen?.[0]?.username || "").split(":")[0]);
ok(ablauf > Math.floor(Date.now() / 1000) + 3600,
`der Ablauf steht vorn im Benutzernamen und liegt in der Zukunft (${ablauf})`);
}
/* --- Aufraeumen: der naechste Abschnitt faengt sauber an ---------- */
{
await roh("/workspace/api/anruf/adressen", HAUS, { cookie: kDogi },
{ adressen: [] }, "PUT");
try { unlinkSync(datei); } catch { /* schon weg */ }
const zurueck = json(await roh("/workspace/api/anruf/adressen", HAUS, { cookie: kDogi }));
ok(zurueck.adressen?.length === 0 && !zurueck.turn_grund,
"danach ist wieder alles leer -- keine erfundene Adresse bleibt stehen");
}
}
/* ======================================================================= /* =======================================================================
7. IM BROWSER 7. IM BROWSER
======================================================================= */ ======================================================================= */
+27 -1
View File
@@ -72,6 +72,7 @@ import {
istDogFather, istDogFather,
} from "./workspace.js"; } from "./workspace.js";
import { chatEreignis, istDrinFuerAnruf, teilnehmerFuerAnruf } from "./workspace-chat.js"; import { chatEreignis, istDrinFuerAnruf, teilnehmerFuerAnruf } from "./workspace-chat.js";
import { mitZugangsdaten } from "./workspace-turn.js";
export const anrufRouter = express.Router(); export const anrufRouter = express.Router();
@@ -161,13 +162,38 @@ anrufRouter.get("/workspace/api/anruf/adressen", (req, res) => {
try { try {
let liste = []; let liste = [];
try { liste = JSON.parse(einstellung("anruf_adressen") || "[]"); } catch { liste = []; } try { liste = JSON.parse(einstellung("anruf_adressen") || "[]"); } catch { liste = []; }
/* ZUGANGSDATEN WERDEN GERECHNET, NICHT GESPEICHERT.
Warum das so ist, steht ausfuehrlich in workspace-turn.js. Kurz:
Ein festes TURN-Passwort laege dauerhaft im Browser jedes
Teilnehmers und liesse sich nie wieder entziehen. Die hier
erzeugten verfallen von selbst.
Als Kennung die PERSONENNUMMER, nicht der Name -- sie landet im
Protokoll von coturn, und das ist kein Ort fuer Klarnamen. */
const { adressen, turn_gesamt, turn_bereit, grund } =
mitZugangsdaten(liste, `w${req.person.id}`);
if (grund) {
/* DER DRITTE AUSGANG. „Kann nicht nachsehen" ist etwas anderes
als „ist nicht eingerichtet" -- und wer das verschweigt, sucht
den Fehler spaeter beim Anrufer. */
console.warn(`[anruf] TURN-Geheimnis nicht verwendbar: ${grund}`);
}
res.json({ res.json({
adressen: Array.isArray(liste) ? liste : [], adressen,
gruppe_max: GRUPPE_MAX, gruppe_max: GRUPPE_MAX,
/* WAS DIE OBERFLAECHE WISSEN MUSS, um ehrlich zu sein: Ohne /* WAS DIE OBERFLAECHE WISSEN MUSS, um ehrlich zu sein: Ohne
Adressen klappt der Anruf nur im selben Netz. Das gehoert Adressen klappt der Anruf nur im selben Netz. Das gehoert
DAVOR gesagt, nicht als Fehlermeldung danach. */ DAVOR gesagt, nicht als Fehlermeldung danach. */
nur_gleiches_netz: !Array.isArray(liste) || liste.length === 0, nur_gleiches_netz: !Array.isArray(liste) || liste.length === 0,
/* Zahlen statt Ja/Nein: Wer zaehlen kann, kann pruefen. Ein
eingetragener TURN-Server, der mangels Geheimnis niemanden
durchlaesst, sieht sonst genauso aus wie ein funktionierender. */
turn_gesamt,
turn_bereit,
...(grund ? { turn_grund: grund } : {}),
}); });
} catch (f) { } catch (f) {
console.error("[anruf] Adressen:", f?.message); console.error("[anruf] Adressen:", f?.message);
+183
View File
@@ -0,0 +1,183 @@
/* =====================================================================
ZUGANGSDATEN FÜR DEN VERMITTLUNGSSERVER (TURN) -- 18.09.2026
Ein TURN-Server leitet Ton und Bild weiter, wenn zwei Browser sich
nicht direkt erreichen (strenge Firmennetze, manche Mobilfunknetze).
Er will wissen, wer ihn benutzen darf -- also Benutzer und Passwort.
---------------------------------------------------------------------
WARUM HIER GERECHNET WIRD, STATT EIN PASSWORT EINZUTRAGEN
Der naheliegende Weg wäre ein festes Passwort in den Einstellungen.
Es wäre in zwei Minuten fertig -- und es läge danach dauerhaft im
Browser jedes Team-Mitglieds, sichtbar in jedem Netzwerk-Fenster,
gültig ohne Ende. Wer einmal dabei war, könnte den Server für immer
als Weiterleitung benutzen: fremden Verkehr über unsere IP-Adresse
schicken, unsere Bandbreite verbrauchen. Ein Zugang, den man nicht
entziehen kann, ist kein Zugang, sondern ein Loch.
Deshalb der Weg, den coturn selbst dafür vorsieht
(`use-auth-secret`): Der Server hier kennt ein GEHEIMNIS, das den
Browser nie erreicht. Aus ihm rechnet er bei jeder Abfrage frische
Zugangsdaten, die nach TURN_GUELTIG_STUNDEN von selbst verfallen.
coturn rechnet dieselbe Zahl nach und kennt dafür nur das Geheimnis
-- es muss keine Benutzerliste geben, und nichts muss abgeglichen
werden.
Benutzername = <Ablaufzeitpunkt>:<Kennung>
Passwort = base64( HMAC-SHA1( Geheimnis, Benutzername ) )
---------------------------------------------------------------------
WARUM DAS GEHEIMNIS IN EINER DATEI LIEGT UND NICHT IN DER DATENBANK
Zwei Gründe, beide im Haus schon einmal teuer gewesen:
1. `einstellungSetzen()` schreibt den WERT in das Protokoll
(`detail: "schluessel = wert"`). Ein Geheimnis dort wäre im
Klartext in der Prüfspur -- und in jeder nächtlichen Sicherung.
2. coturn braucht denselben Wert in `/etc/turnserver.conf`. Eine
Datei, die beide Seiten aus derselben Quelle bekommen, kann nicht
auseinanderlaufen; zwei Eingabefelder können es immer.
Die Datei gehört root und ist für die Gruppe des Dienstes lesbar
(640). Der Browser bekommt sie nie zu sehen -- das ist der Punkt,
an dem `pruef-anruf.mjs` nachmisst.
===================================================================== */
import { createHmac } from "node:crypto";
import { readFileSync, statSync } from "node:fs";
/** Wie lange erzeugte Zugangsdaten gelten.
*
* Zwölf Stunden ist die Spanne, die einen Arbeitstag überdeckt, ohne
* dass jemand mittendrin herausfliegt -- und kurz genug, dass ein
* Zugang, der nicht mehr gelten soll, am nächsten Tag nicht mehr
* gilt. Die Oberfläche holt die Adressen zusätzlich bei jedem Anruf
* neu; deshalb ist diese Zahl eine Obergrenze, kein Takt. */
export const TURN_GUELTIG_STUNDEN = 12;
/** Wo das gemeinsame Geheimnis liegt.
*
* Über eine Umgebungsvariable umstellbar -- nicht aus Vorliebe für
* Einstellbarkeit, sondern weil die Prüfung sonst entweder gar nicht
* messen könnte oder an die echte Datei müsste. */
export const TURN_GEHEIMNIS_DATEI =
process.env.TURN_GEHEIMNIS_DATEI || "/etc/coturn-workspace.geheimnis";
/* Gelesenes Geheimnis, gemerkt über einen ÄNDERUNGSSTEMPEL statt über
eine Laufzeit: Wer die Datei austauscht, soll nicht bis zum Ablauf
einer Frist warten. Ein `stat` je Abfrage kostet nichts, und die
Abfrage kommt ein paar Mal am Tag, nicht ein paar Mal je Sekunde. */
let gemerkt = { stempel: "", wert: "" };
/** Liest das Geheimnis.
*
* DREI AUSGÄNGE, nicht zwei -- „ich kann nicht nachsehen" ist etwas
* anderes als „es ist keins da". Der Grund wandert bis in die Antwort
* der Schnittstelle, damit an der Oberfläche nicht geraten wird.
*
* @returns {{wert: string, grund: string}} `grund` ist leer, wenn
* alles in Ordnung ist; sonst `fehlt`, `leer`, `zu_kurz` oder
* `unlesbar:<CODE>`.
*/
export function turnGeheimnisLesen(datei = TURN_GEHEIMNIS_DATEI) {
let s;
try {
s = statSync(datei);
} catch (f) {
gemerkt = { stempel: "", wert: "" };
return { wert: "", grund: f?.code === "ENOENT" ? "fehlt" : `unlesbar:${f?.code || "?"}` };
}
const stempel = `${s.mtimeMs}:${s.size}`;
if (stempel !== gemerkt.stempel) {
try {
gemerkt = { stempel, wert: readFileSync(datei, "utf8").trim() };
} catch (f) {
gemerkt = { stempel: "", wert: "" };
return { wert: "", grund: `unlesbar:${f?.code || "?"}` };
}
}
if (!gemerkt.wert) return { wert: "", grund: "leer" };
/* Ein zu kurzes Geheimnis ist schlimmer als keins: Es sieht aus wie
Sicherheit und ist in Minuten geraten. 32 Zeichen entsprechen den
16 Bytes, die der Einrichtungsbefehl erzeugt. */
if (gemerkt.wert.length < 32) return { wert: "", grund: "zu_kurz" };
return { wert: gemerkt.wert, grund: "" };
}
/** Rechnet ein Paar Zugangsdaten aus.
*
* @param kennung Wer fragt -- landet im Benutzernamen und damit im
* Protokoll von coturn. Keine Namen, nur die Nummer: Das Protokoll
* eines Vermittlungsservers ist kein Ort für Klarnamen.
* @param geheimnis Der gemeinsame Wert aus der Datei.
* @param jetzt Millisekunden. Als Eingabe übergeben, nicht aus der
* Wanduhr gelesen -- sonst wäre die Prüfung eine Zeitbombe.
*/
export function turnZugang(kennung, geheimnis, jetzt = Date.now()) {
const ablauf = Math.floor(jetzt / 1000) + TURN_GUELTIG_STUNDEN * 3600;
const username = `${ablauf}:${kennung}`;
return {
username,
credential: createHmac("sha1", geheimnis).update(username).digest("base64"),
ablauf,
};
}
/** Ergänzt eine Adressliste um frische Zugangsdaten.
*
* WELCHER EINTRAG BEKOMMT WELCHE BEHANDLUNG:
*
* stun:… nichts -- STUN kennt keine Anmeldung.
* turn:… ohne eigenes Passwort gerechnete Zugangsdaten (unser
* coturn).
* turn:… MIT username+credential unverändert -- das ist ein
* fremder Dienst, den jemand bewusst
* eingetragen hat. Wir überschreiben
* keine Zugangsdaten, die wir nicht
* ausgestellt haben.
*
* @returns {{adressen: object[], turn_gesamt: number,
* turn_bereit: number, grund: string}}
* Die ZAHLEN gehören zurückgegeben, nicht nur die Liste: Eine
* Prüfung, die „ist grün" sagen soll, muss zählen können, worüber
* sie redet -- sonst ist sie auch bei einer leeren Liste grün.
*/
export function mitZugangsdaten(liste, kennung, jetzt = Date.now(),
datei = TURN_GEHEIMNIS_DATEI) {
const rein = Array.isArray(liste) ? liste : [];
const brauchtZugang = (e) =>
/^turns?:/.test(String(e?.urls || "")) && !e?.credential;
const turn_gesamt = rein.filter((e) => /^turns?:/.test(String(e?.urls || ""))).length;
if (!rein.some(brauchtZugang)) {
return { adressen: rein, turn_gesamt, turn_bereit: turn_gesamt, grund: "" };
}
const { wert, grund } = turnGeheimnisLesen(datei);
if (!wert) {
/* KEIN STILLES WEGLASSEN. Ohne Zugangsdaten weist coturn jede
Anfrage ab -- der Anruf fällt dann auf die direkte Verbindung
zurück und scheitert genau bei den Leuten, für die der Server
gebaut wurde. Der Eintrag bleibt trotzdem in der Liste: Ein
`turn:` ohne Passwort schadet nicht, und der Grund steht dabei. */
return {
adressen: rein,
turn_gesamt,
turn_bereit: rein.filter((e) => /^turns?:/.test(String(e?.urls || "")) && e?.credential).length,
grund,
};
}
const zugang = turnZugang(kennung, wert, jetzt);
return {
adressen: rein.map((e) =>
brauchtZugang(e)
? { ...e, username: zugang.username, credential: zugang.credential }
: e),
turn_gesamt,
turn_bereit: turn_gesamt,
grund: "",
};
}
+204
View File
@@ -0,0 +1,204 @@
/* =====================================================================
DEN VERMITTLUNGSSERVER EINTRAGEN UND NACHMESSEN -- 18.09.2026
Aufrufen (auf dem Server, als Benutzer dogiweb):
node tools/turn-einrichten.mjs pruefen
node tools/turn-einrichten.mjs eintragen turn:159.195.212.167:3478
node tools/turn-einrichten.mjs entfernen
---------------------------------------------------------------------
WARUM EIN BEFEHL UND KEIN EINGABEFELD
Die Adresse wird einmal eingetragen und dann jahrelang nicht mehr.
Ein Menuepunkt dafuer waere eine Seite, die niemand findet, wenn er
sie braucht -- und eine, die gepflegt werden muss. Der Befehl steht
dagegen neben den anderen Einrichtungsschritten, in derselben
Anleitung.
WAS DIESER BEFEHL NICHT TUT: Er erzeugt das Geheimnis nicht. Das
gehoert root, weil es auch in /etc/turnserver.conf muss. Hier wird
nur NACHGESEHEN, ob es aus Sicht des Dienstes lesbar ist -- der
haeufigste Fehler beim Einrichten sind Dateirechte, und der faellt
sonst erst beim ersten Anruf auf, der nicht zustande kommt.
DAS GEHEIMNIS WIRD NIE AUSGEGEBEN, auch nicht gekuerzt.
===================================================================== */
import { createSocket } from "node:dgram";
import { randomBytes } from "node:crypto";
import { DatabaseSync } from "node:sqlite";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { turnGeheimnisLesen, TURN_GEHEIMNIS_DATEI } from "../server/workspace-turn.js";
const __dirname = dirname(fileURLToPath(import.meta.url));
const DB_PFAD = process.env.WORKSPACE_DB
|| join(__dirname, "..", "..", "workspace-daten", "workspace.db");
const [, , befehl, wert] = process.argv;
/* ---------------------------------------------------------------------
EIN ECHTER VERSUCH, KEIN BLICK IN DIE KONFIGURATION
Eine Konfigurationsdatei zu lesen beweist, dass jemand etwas
hingeschrieben hat. Ob der Dienst laeuft, ob die Firewall offen ist
und ob er von aussen antwortet, beweist nur eine Anfrage.
Ein STUN-Binding ist dafuer die kleinste moegliche: 20 Byte hin,
und zurueck kommt die eigene Adresse, wie der Server sie sieht.
--------------------------------------------------------------------- */
function stunFragen(host, port = 3478, frist = 3000) {
return new Promise((fertig) => {
const kennung = randomBytes(12);
const anfrage = Buffer.alloc(20);
anfrage.writeUInt16BE(0x0001, 0); // Binding Request
anfrage.writeUInt16BE(0x0000, 2); // keine Attribute
anfrage.writeUInt32BE(0x2112a442, 4); // der feste Erkennungswert
kennung.copy(anfrage, 8);
const s = createSocket("udp4");
const uhr = setTimeout(() => {
try { s.close(); } catch { /* schon zu */ }
fertig({ ok: false, grund: "keine Antwort innerhalb von 3 Sekunden" });
}, frist);
s.on("error", (f) => {
clearTimeout(uhr);
try { s.close(); } catch { /* schon zu */ }
fertig({ ok: false, grund: f.message });
});
s.on("message", (a) => {
clearTimeout(uhr);
try { s.close(); } catch { /* schon zu */ }
if (a.length < 20 || a.readUInt16BE(0) !== 0x0101) {
return fertig({ ok: false, grund: "Antwort ist kein STUN-Erfolg" });
}
if (!a.subarray(8, 20).equals(kennung)) {
return fertig({ ok: false, grund: "Antwort gehoert zu einer anderen Anfrage" });
}
/* XOR-MAPPED-ADDRESS heraussuchen -- die Adresse ist mit dem
Erkennungswert verodert, damit unterwegs niemand daran
herumbastelt. */
let p = 20;
while (p + 4 <= a.length) {
const art = a.readUInt16BE(p), laenge = a.readUInt16BE(p + 2);
if (art === 0x0020 && laenge >= 8) {
const hafen = a.readUInt16BE(p + 6) ^ 0x2112;
const ip = [0, 1, 2, 3]
.map((i) => a[p + 8 + i] ^ [0x21, 0x12, 0xa4, 0x42][i]).join(".");
return fertig({ ok: true, gesehenAls: `${ip}:${hafen}` });
}
p += 4 + laenge + ((4 - (laenge % 4)) % 4);
}
fertig({ ok: true, gesehenAls: "(ohne Adressangabe)" });
});
s.send(anfrage, port, host, (f) => {
if (f) {
clearTimeout(uhr);
try { s.close(); } catch { /* schon zu */ }
fertig({ ok: false, grund: f.message });
}
});
});
}
function adressenLesen(db) {
try {
const r = db.prepare("SELECT wert FROM einstellungen WHERE schluessel = 'anruf_adressen'").get();
const l = JSON.parse(r?.wert || "[]");
return Array.isArray(l) ? l : [];
} catch { return []; }
}
function adressenSchreiben(db, liste) {
db.prepare(`
INSERT INTO einstellungen (schluessel, wert, geaendert, von)
VALUES ('anruf_adressen', ?, ?, NULL)
ON CONFLICT(schluessel) DO UPDATE SET
wert = excluded.wert, geaendert = excluded.geaendert`)
.run(JSON.stringify(liste), new Date().toISOString());
}
/* --------------------------------------------------------------------- */
if (!["pruefen", "eintragen", "entfernen"].includes(befehl)) {
console.log("Aufruf: node tools/turn-einrichten.mjs pruefen"
+ "\n node tools/turn-einrichten.mjs eintragen turn:159.195.212.167:3478"
+ "\n node tools/turn-einrichten.mjs entfernen");
process.exit(2);
}
const db = new DatabaseSync(DB_PFAD);
if (befehl === "eintragen") {
if (!/^(stun|turn|turns):[^\s]+$/.test(wert || "")) {
console.error("FEHLT: eine Adresse wie turn:159.195.212.167:3478");
process.exit(2);
}
const alt = adressenLesen(db);
/* Doppelte Eintraege vermeiden -- zweimal dieselbe Adresse bedeutet,
dass jeder Browser sie zweimal probiert. */
const neu = alt.filter((e) => e.urls !== wert).concat([{ urls: wert }]);
adressenSchreiben(db, neu);
console.log(`Eingetragen. Vorher ${alt.length}, jetzt ${neu.length} Adresse(n).`);
} else if (befehl === "entfernen") {
adressenSchreiben(db, []);
console.log("Alle Adressen entfernt.");
}
/* ---------- Der Zustand, immer ---------------------------------------- */
console.log("");
console.log("=== Zustand ===");
const adressen = adressenLesen(db);
console.log(`Eingetragene Adressen: ${adressen.length}`);
for (const a of adressen) console.log(` ${a.urls}`);
const g = turnGeheimnisLesen();
console.log("");
if (g.wert) {
console.log(`Geheimnis: lesbar (${g.wert.length} Zeichen) aus ${TURN_GEHEIMNIS_DATEI}`);
} else {
console.log(`Geheimnis: NICHT verwendbar -- ${g.grund}`);
console.log(` erwartet unter ${TURN_GEHEIMNIS_DATEI}`);
if (g.grund.startsWith("unlesbar")) {
console.log(" (Dateirechte: root:dogiweb mit 640)");
}
}
/* Wirklich anfragen -- bei jeder eingetragenen Adresse. */
let erreichbar = 0, versucht = 0;
for (const a of adressen) {
const m = /^(?:stun|turns?):([^:/?]+)(?::(\d+))?/.exec(a.urls || "");
if (!m) continue;
versucht++;
const antwort = await stunFragen(m[1], Number(m[2] || 3478));
if (antwort.ok) {
erreichbar++;
console.log(`\n${a.urls}: antwortet. Von dort aus sehen wir aus wie ${antwort.gesehenAls}`);
} else {
console.log(`\n${a.urls}: KEINE ANTWORT -- ${antwort.grund}`);
console.log(" Meist eins von dreien: Dienst laeuft nicht "
+ "(systemctl status coturn), Firewall zu (ufw), oder falsche Adresse.");
}
}
db.close();
console.log("");
if (!adressen.length) {
console.log("ERGEBNIS: Keine Adresse eingetragen -- Anrufe klappen nur im selben Netz.");
process.exit(1);
}
if (!g.wert) {
console.log("ERGEBNIS: Adresse da, aber ohne Geheimnis weist coturn jeden ab.");
process.exit(1);
}
if (versucht && erreichbar < versucht) {
console.log(`ERGEBNIS: ${erreichbar} von ${versucht} Adressen antworten.`);
process.exit(1);
}
console.log("ERGEBNIS: Eingetragen, Geheimnis lesbar, Server antwortet.");
+186
View File
@@ -0,0 +1,186 @@
#!/bin/bash
# =====================================================================
# BEWEIST, DASS COTURN UNSERE ZUGANGSDATEN AKZEPTIERT -- 18.09.2026
#
# Aufrufen auf dem Server: bash tools/turn-probelauf.sh
#
# ---------------------------------------------------------------------
# WARUM ES DIESES SKRIPT GIBT
#
# `pruef-anruf.mjs` prueft die Rechenvorschrift gegen einen festen
# Vergleichswert. Das schuetzt davor, dass sie sich unbemerkt aendert --
# es beweist aber NICHT, dass coturn das Ergebnis akzeptiert. Wir
# koennten beide Seiten konsequent falsch rechnen, und jede Pruefung
# waere gruen, waehrend kein einziger Anruf ueber die Vermittlung
# zustande kaeme. Das faellt im Betrieb nicht auf: Es scheitern nur die
# 15-25 %, die den Server ueberhaupt brauchen.
#
# Hier laeuft deshalb ein echter coturn und ein echter Client.
#
# ---------------------------------------------------------------------
# DASS ES NICHTS ANFASST, IST ABSICHT
#
# Eigenes Heimatverzeichnis, eigener Port, nur 127.0.0.1, kein root,
# kein Systemdienst. Der laufende coturn auf 3478 bleibt unberuehrt,
# und von aussen ist der Probe-Server nicht erreichbar.
# =====================================================================
set -u
P="${TMPDIR:-/tmp}/turn-probe-$$"
FEHLER=0
ok() { echo " ok $1"; }
fehl() { echo " FEHL $1"; FEHLER=$((FEHLER+1)); }
aufraeumen() { pkill -f "$P/turn.conf" 2>/dev/null; kill "${GEGENPART:-0}" 2>/dev/null; sleep 1; rm -rf "$P"; }
trap aufraeumen EXIT
command -v turnserver >/dev/null || { echo "KANN NICHT NACHSEHEN: coturn ist nicht installiert."; exit 3; }
command -v turnutils_uclient >/dev/null || { echo "KANN NICHT NACHSEHEN: turnutils_uclient fehlt."; exit 3; }
NODE=$(command -v node || echo /usr/local/bin/node)
[ -x "$NODE" ] || { echo "KANN NICHT NACHSEHEN: node nicht gefunden."; exit 3; }
mkdir -p "$P"
# ZIEL FUER DEN SPERRTEST -- und warum ausgerechnet dieses.
# Der erste Anlauf nahm 127.0.0.1. Die Gegenprobe (Abschnitt 4) zeigte:
# Auch OHNE Sperrliste kam 403 -- coturn verbietet Loopback-Ziele von
# sich aus. Der Abschnitt haette also NICHT unsere Sperrliste gemessen,
# sondern eine eingebaute Regel, und waere gruen geblieben, wenn jemand
# die Liste entfernt. Genau dafuer ist die Gegenprobe da.
# Die Docker-Bruecke ist ein echtes oertliches Ziel auf diesem Server
# und KEIN Loopback -- ohne unsere Liste waere sie erreichbar.
ZIEL="${TURN_PROBE_ZIEL:-172.17.0.1}"
# WARUM HIER KEIN `-y` STEHT (und im ersten Anlauf stand):
# `-y` heisst "client-to-client" -- der Client verbindet dann zwei
# EIGENE Kanaele miteinander und ignoriert `-e` vollstaendig. Das Ziel
# war damit immer die Relay-Adresse 127.0.0.1, also Loopback, und das
# sperrt coturn von sich aus. Die Gegenprobe blieb deshalb zweimal rot,
# ohne dass an der Sperrliste etwas falsch war. Erst die Hilfeseite des
# Werkzeugs hat es gezeigt -- nicht das dritte Herumprobieren.
# Jetzt laeuft ein echter Gegenpart auf der Docker-Bruecke.
gegenpart_starten() {
turnutils_peer -L "$ZIEL" -p 3480 >/dev/null 2>&1 &
GEGENPART=$!
sleep 1
}
gegenpart_beenden() { kill "$GEGENPART" 2>/dev/null; }
GEHEIM=$(head -c 24 /dev/urandom | od -An -tx1 | tr -d ' \n')
# Die Konfiguration entspricht deploy/turnserver.conf; abweichend nur
# Adresse, Ports und dass das Protokoll in den Probeordner geht.
schreibe_konf() { # $1 = "mit-sperre" oder "ohne-sperre"
cat > "$P/turn.conf" <<CONF
listening-ip=127.0.0.1
relay-ip=127.0.0.1
listening-port=34780
no-tls
no-dtls
min-port=49400
max-port=49420
use-auth-secret
static-auth-secret=$GEHEIM
realm=dogfather-universe.com
no-multicast-peers
no-tcp-relay
no-cli
user-quota=12
total-quota=100
CONF
if [ "$1" = "mit-sperre" ]; then
cat >> "$P/turn.conf" <<'CONF'
denied-peer-ip=127.0.0.0-127.255.255.255
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.168.0.0-192.168.255.255
CONF
fi
}
starten() {
turnserver -c "$P/turn.conf" --no-stdout-log --log-file="$P/turn.log" >/dev/null 2>&1 &
for _ in 1 2 3 4 5 6 7 8 9 10; do
ss -lnu 2>/dev/null | grep -q 34780 && return 0
sleep 0.5
done
return 1
}
anhalten() { pkill -f "$P/turn.conf" 2>/dev/null; sleep 1; }
# Dieselbe Rechnung wie server/workspace-turn.js -- bewusst nachgebaut
# statt importiert: Wenn beide Seiten aus derselben Zeile kaemen, waere
# ein Fehler in dieser Zeile hier unsichtbar.
zugang() {
"$NODE" -e "
const {createHmac} = require('node:crypto');
const u = (Math.floor(Date.now()/1000) + 12*3600) + ':w7';
console.log(u + '\t' + createHmac('sha1', process.argv[1]).update(u).digest('base64'));
" "$GEHEIM"
}
echo "=== Probelauf gegen einen echten coturn ==="
echo
# --------------------------------------------------------------------
echo "--- 1. Die Konfiguration aus deploy/turnserver.conf ---"
schreibe_konf mit-sperre
gegenpart_starten
if starten; then ok "coturn startet und lauscht auf 127.0.0.1:34780"
else fehl "coturn startet nicht -- $(tail -3 "$P/turn.log" 2>/dev/null | tr '\n' ' ')"; exit 1; fi
A=$(turnutils_stunclient -p 34780 127.0.0.1 2>&1)
echo "$A" | grep -q "reflexive addr" \
&& ok "er beantwortet eine STUN-Anfrage ($(echo "$A" | grep -m1 -o '127[0-9.:]*'))" \
|| fehl "keine STUN-Antwort: $(echo "$A" | head -2 | tr '\n' ' ')"
# --------------------------------------------------------------------
echo
echo "--- 2. Die Kernfrage: nimmt coturn unsere gerechneten Daten an? ---"
L=$(zugang); U=$(echo "$L" | cut -f1); W=$(echo "$L" | cut -f2)
RICHTIG=$(turnutils_uclient -u "$U" -w "$W" -p 34780 -n 2 -c -e "$ZIEL" -r 3480 127.0.0.1 2>&1)
FALSCH=$(turnutils_uclient -u "$U" -w "nicht-das-richtige-passwort" -p 34780 -n 1 -c -e "$ZIEL" -r 3480 127.0.0.1 2>&1)
# Der Unterschied ist eindeutig und genau der richtige:
# Mit gueltigen Daten kommt der Client UEBER die Allocation hinaus und
# scheitert erst am gesperrten Ziel. Mit ungueltigen scheitert schon
# die Allocation selbst.
echo "$RICHTIG" | grep -q "Cannot complete Allocation" \
&& fehl "unsere Zugangsdaten werden ABGEWIESEN -- kein Anruf ginge ueber die Vermittlung" \
|| ok "unsere Zugangsdaten werden angenommen (die Zuteilung gelingt)"
echo "$FALSCH" | grep -q "Cannot complete Allocation" \
&& ok "ein falsches Passwort wird abgewiesen -- die Pruefung prueft wirklich" \
|| fehl "ein FALSCHES Passwort kam durch -- der Server steht jedem offen"
# --------------------------------------------------------------------
echo
echo "--- 3. Die Sperrliste: kommt jemand an die oertlichen Dienste? ---"
echo "$RICHTIG" | grep -qi "403" \
&& ok "ein Ziel im oertlichen Netz ($ZIEL) wird verweigert (403)" \
|| fehl "$ZIEL war als Ziel ERLAUBT -- ein Weg zu jedem oertlichen Dienst"
# --------------------------------------------------------------------
echo
echo "--- 4. Gegenprobe: ohne Sperrliste MUSS derselbe Versuch durchgehen ---"
echo " (sonst haette Abschnitt 3 nur gemessen, dass irgendetwas scheitert)"
anhalten
schreibe_konf ohne-sperre
if starten; then
L=$(zugang); U=$(echo "$L" | cut -f1); W=$(echo "$L" | cut -f2)
OHNE=$(turnutils_uclient -u "$U" -w "$W" -p 34780 -n 4 -c -e "$ZIEL" -r 3480 127.0.0.1 2>&1)
echo "$OHNE" | grep -qi "403" \
&& fehl "auch ohne Sperrliste 403 -- Abschnitt 3 beweist dann nichts" \
|| ok "ohne Sperrliste laeuft derselbe Versuch durch -- die Sperre war die Ursache"
echo "$OHNE" | grep -qiE "total transmit|tot_send_msgs|Total transmit time" \
&& ok "und es fliessen wirklich Daten ueber die Weiterleitung" \
|| echo " -- Datenfluss nicht ablesbar (nicht schlimm, der Rest steht)"
else
fehl "zweiter Start misslungen"
fi
echo
if [ "$FEHLER" -eq 0 ]; then
echo "ALLES IN ORDNUNG -- coturn und der Workspace rechnen dasselbe."
exit 0
fi
echo "NICHT IN ORDNUNG -- $FEHLER Punkt(e)."
exit 1
+4 -4
View File
@@ -22,25 +22,25 @@
}, },
"icons": [ "icons": [
{ {
"src": "/assets/img/app-symbole/workspace-192.png?v=202609181442", "src": "/assets/img/app-symbole/workspace-192.png?v=202609181521",
"sizes": "192x192", "sizes": "192x192",
"type": "image/png", "type": "image/png",
"purpose": "any" "purpose": "any"
}, },
{ {
"src": "/assets/img/app-symbole/workspace-512.png?v=202609181442", "src": "/assets/img/app-symbole/workspace-512.png?v=202609181521",
"sizes": "512x512", "sizes": "512x512",
"type": "image/png", "type": "image/png",
"purpose": "any" "purpose": "any"
}, },
{ {
"src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609181442", "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609181521",
"sizes": "192x192", "sizes": "192x192",
"type": "image/png", "type": "image/png",
"purpose": "maskable" "purpose": "maskable"
}, },
{ {
"src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609181442", "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609181521",
"sizes": "512x512", "sizes": "512x512",
"type": "image/png", "type": "image/png",
"purpose": "maskable" "purpose": "maskable"
+58 -9
View File
@@ -247,6 +247,10 @@
kastenBauen(); kastenBauen();
$('anruf').hidden = false; $('anruf').hidden = false;
lage('Mikrofon …'); lage('Mikrofon …');
/* Läuft NEBEN der Mikrofonfrage, nicht davor: Das Auffrischen
kostet sonst Zeit, in der nichts passiert -- und die Frage nach
dem Mikrofon dauert ohnehin länger als der kleine Abruf. */
const adressenFertig = adressenAuffrischen();
let eigener; let eigener;
try { try {
eigener = await eigenenStromHolen(mitVideo); eigener = await eigenenStromHolen(mitVideo);
@@ -255,6 +259,7 @@
setTimeout(() => { if (!anruf) $('anruf').hidden = true; }, 6000); setTimeout(() => { if (!anruf) $('anruf').hidden = true; }, 6000);
return; return;
} }
await adressenFertig;
anruf = { raumId, video: mitVideo, verbindungen: new Map(), anruf = { raumId, video: mitVideo, verbindungen: new Map(),
stroeme: new Map(), eigener, beginn: Date.now() }; stroeme: new Map(), eigener, beginn: Date.now() };
$('anruf-video').hidden = !mitVideo; $('anruf-video').hidden = !mitVideo;
@@ -278,6 +283,10 @@
} }
async function annehmen(raumId) { async function annehmen(raumId) {
/* Auch hier auffrischen -- wer angerufen WIRD, braucht die
Vermittlung genauso. Beide Wege einzeln abgesichert, nicht nur
der, an den man zuerst denkt. */
const adressenFertig = adressenAuffrischen();
const a = await fetch(`/workspace/api/anruf/${raumId}/dabei`, { const a = await fetch(`/workspace/api/anruf/${raumId}/dabei`, {
method: 'POST', credentials: 'same-origin', method: 'POST', credentials: 'same-origin',
}).catch(() => null); }).catch(() => null);
@@ -288,6 +297,7 @@
return; return;
} }
const d = await a.json(); const d = await a.json();
await adressenFertig;
kastenBauen(); kastenBauen();
$('anruf').hidden = false; $('anruf').hidden = false;
let eigener; let eigener;
@@ -500,6 +510,53 @@
} }
} }
/* ---------- Verbindungsadressen ----------------------------------------
Die Zugangsdaten zum Vermittlungsserver VERFALLEN (sie werden vom
Server gerechnet, nicht gespeichert -- siehe server/workspace-turn.js).
Diese Seite bleibt als App oft tagelang offen. Wer sie nur beim
Laden holt, telefoniert am zweiten Tag ohne Vermittlung -- und das
fällt nicht auf: Es scheitern nur die Anrufe derer, die sie
gebraucht hätten. Genau dafür ist sie gebaut.
Deshalb vor jedem Anruf nachsehen. Der Ablaufzeitpunkt steht im
Benutzernamen (`<ablauf>:<kennung>`) -- er lässt sich hier ablesen,
ohne dass der Server ein zusätzliches Feld schicken muss. */
/* Fünf Minuten Vorlauf: Ein Anruf, der in 30 Sekunden beginnt, soll
nicht mit Daten starten, die in 40 ablaufen. */
const ZUGANG_VORLAUF = 300;
let adressenGeholt = false;
function adressenFrisch() {
if (!adressenGeholt) return false;
const jetzt = Math.floor(Date.now() / 1000);
return adressen.every((a) => {
const m = /^(\d{10,}):/.exec(a && a.username || '');
return !m || Number(m[1]) > jetzt + ZUGANG_VORLAUF;
});
}
async function adressenAuffrischen(erzwingen) {
if (!erzwingen && adressenFrisch()) return;
try {
const a = await fetch('/workspace/api/anruf/adressen', { credentials: 'same-origin' });
if (a.ok) {
const d = await a.json();
adressen = d.adressen || [];
nurGleichesNetz = !!d.nur_gleiches_netz;
gruppeMax = d.gruppe_max || 4;
adressenGeholt = true;
/* Ein eingetragener Vermittlungsserver, der niemanden
durchlässt, ist schlimmer als keiner -- er sieht aus, als
wäre alles eingerichtet. Deshalb steht er in der Konsole. */
if (d.turn_grund) {
console.warn('[anruf] Vermittlungsserver ohne Zugangsdaten:', d.turn_grund);
}
}
} catch { /* dann eben ohne -- im selben Netz geht es trotzdem */ }
}
/* ---------- Nach außen -------------------------------------------------- */ /* ---------- Nach außen -------------------------------------------------- */
window.anrufStarten = starten; window.anrufStarten = starten;
@@ -508,15 +565,7 @@
window.anrufBereit = () => adressen.length > 0 || nurGleichesNetz; window.anrufBereit = () => adressen.length > 0 || nurGleichesNetz;
(async () => { (async () => {
try { await adressenAuffrischen();
const a = await fetch('/workspace/api/anruf/adressen', { credentials: 'same-origin' });
if (a.ok) {
const d = await a.json();
adressen = d.adressen || [];
nurGleichesNetz = !!d.nur_gleiches_netz;
gruppeMax = d.gruppe_max || 4;
}
} catch { /* dann eben ohne -- im selben Netz geht es trotzdem */ }
try { try {
const i = await fetch('/workspace/api/ich', { credentials: 'same-origin' }); const i = await fetch('/workspace/api/ich', { credentials: 'same-origin' });
if (i.ok) ichId = (await i.json())?.id ?? null; if (i.ok) ichId = (await i.json())?.id ?? null;
+12 -12
View File
@@ -12,19 +12,19 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. --> eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026). <!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an -- kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. --> ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -291,10 +291,10 @@
</form> </form>
</dialog> </dialog>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/glocke.js?v=202609181442" defer></script> <script src="assets/js/glocke.js?v=202609181521" defer></script>
<script src="assets/js/aufgaben.js?v=202609181442" defer></script> <script src="assets/js/aufgaben.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. --> eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/automation.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/automation.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026). <!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an -- kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. --> ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -168,10 +168,10 @@
</main> </main>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/glocke.js?v=202609181442" defer></script> <script src="assets/js/glocke.js?v=202609181521" defer></script>
<script src="assets/js/automation.js?v=202609181442" defer></script> <script src="assets/js/automation.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+12 -12
View File
@@ -7,14 +7,14 @@
<meta name="robots" content="noindex, nofollow" /> <meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" /> <link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/entwicklung.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -105,9 +105,9 @@
</main> </main>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/befinden.js?v=202609181442" defer></script> <script src="assets/js/befinden.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+18 -18
View File
@@ -12,24 +12,24 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. --> eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/bereich.css?v=202609181521" />
<!-- Wegen des Blocks „Willst du dazugehoeren?" auf dem Brett <!-- Wegen des Blocks „Willst du dazugehoeren?" auf dem Brett
„Mitmachen". Eine eigene Kopie der Regel hier waere die zweite „Mitmachen". Eine eigene Kopie der Regel hier waere die zweite
Stelle, an der dieselbe Sache anders aussieht. --> Stelle, an der dieselbe Sache anders aussieht. -->
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/bewerbung.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026). <!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an -- kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. --> ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -243,14 +243,14 @@
</main> </main>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/glocke.js?v=202609181442" defer></script> <script src="assets/js/glocke.js?v=202609181521" defer></script>
<script src="assets/js/checkliste.js?v=202609181442" defer></script> <script src="assets/js/checkliste.js?v=202609181521" defer></script>
<script src="assets/js/ampel.js?v=202609181442" defer></script> <script src="assets/js/ampel.js?v=202609181521" defer></script>
<script src="assets/js/schulung.js?v=202609181442" defer></script> <script src="assets/js/schulung.js?v=202609181521" defer></script>
<script src="assets/js/vorlagen.js?v=202609181442" defer></script> <script src="assets/js/vorlagen.js?v=202609181521" defer></script>
<script src="assets/js/bereich.js?v=202609181442" defer></script> <script src="assets/js/bereich.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+15 -15
View File
@@ -7,17 +7,17 @@
<meta name="robots" content="noindex, nofollow" /> <meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" /> <link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/bereich.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/treff.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/bewerbung.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein - siehe treff-regeln.html. --> <!-- haus.css MUSS die letzte Stilvorlage sein - siehe treff-regeln.html. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -107,10 +107,10 @@
</main> </main>
<!-- Der Fuß gehört zu jeder Seite - siehe pruef-css-klassen. --> <!-- Der Fuß gehört zu jeder Seite - siehe pruef-css-klassen. -->
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/auskunft.js?v=202609181442" defer></script> <script src="assets/js/auskunft.js?v=202609181521" defer></script>
<script src="assets/js/bewerben.js?v=202609181442" defer></script> <script src="assets/js/bewerben.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+14 -14
View File
@@ -7,16 +7,16 @@
<meta name="robots" content="noindex, nofollow" /> <meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" /> <link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/entwicklung.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/bewerbung.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein - siehe treff-regeln.html. --> <!-- haus.css MUSS die letzte Stilvorlage sein - siehe treff-regeln.html. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -80,10 +80,10 @@
</main> </main>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/auskunft.js?v=202609181442" defer></script> <script src="assets/js/auskunft.js?v=202609181521" defer></script>
<script src="assets/js/bewerbungen.js?v=202609181442" defer></script> <script src="assets/js/bewerbungen.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. --> eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/calls.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/calls.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026). <!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an -- kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. --> ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -89,11 +89,11 @@
</main> </main>
<script src="assets/js/ki.js?v=202609181442" defer></script> <script src="assets/js/ki.js?v=202609181521" defer></script>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/glocke.js?v=202609181442" defer></script> <script src="assets/js/glocke.js?v=202609181521" defer></script>
<script src="assets/js/calls.js?v=202609181442" defer></script> <script src="assets/js/calls.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+14 -14
View File
@@ -12,26 +12,26 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. --> eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<!-- aufgaben.css bringt die Dialog- und Formularstile mit (.dialog, <!-- aufgaben.css bringt die Dialog- und Formularstile mit (.dialog,
.neu__zeile, .dialog__knoepfe). Sie stehen dort seit dem .neu__zeile, .dialog__knoepfe). Sie stehen dort seit dem
Aufgabenbrett; sie hierher zu kopieren hiesse, sie an zwei Stellen Aufgabenbrett; sie hierher zu kopieren hiesse, sie an zwei Stellen
zu pflegen. Dieselbe Lösung nutzen kalender.html und content.html. zu pflegen. Dieselbe Lösung nutzen kalender.html und content.html.
Gemeldet von pruef-css-klassen.mjs, bevor jemand einen ungestylten Gemeldet von pruef-css-klassen.mjs, bevor jemand einen ungestylten
Dialog zu sehen bekam. --> Dialog zu sehen bekam. -->
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/chat.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/chat.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026). <!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an -- kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. --> ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -341,15 +341,15 @@
</form> </form>
</dialog> </dialog>
<script src="assets/js/bereiche.js?v=202609181442"></script> <script src="assets/js/bereiche.js?v=202609181521"></script>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442"></script> <script src="assets/js/kopf.js?v=202609181521"></script>
<script src="assets/js/glocke.js?v=202609181442"></script> <script src="assets/js/glocke.js?v=202609181521"></script>
<!-- ANRUF VOR CHAT (18.09.2026): anruf.js stellt `window.anrufStarten` <!-- ANRUF VOR CHAT (18.09.2026): anruf.js stellt `window.anrufStarten`
bereit, und chat.js baut die Knoepfe nur, wenn es das gibt. bereit, und chat.js baut die Knoepfe nur, wenn es das gibt.
Andersherum waere die Reihenfolge eine stille Abhaengigkeit -- Andersherum waere die Reihenfolge eine stille Abhaengigkeit --
die Knoepfe fehlten, und niemand wuesste warum. --> die Knoepfe fehlten, und niemand wuesste warum. -->
<script src="assets/js/anruf.js?v=202609181442"></script> <script src="assets/js/anruf.js?v=202609181521"></script>
<script src="assets/js/chat.js?v=202609181442"></script> <script src="assets/js/chat.js?v=202609181521"></script>
</body> </body>
</html> </html>
+15 -15
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. --> eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/content.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/content.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026). <!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an -- kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. --> ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -179,12 +179,12 @@
</main> </main>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/glocke.js?v=202609181442" defer></script> <script src="assets/js/glocke.js?v=202609181521" defer></script>
<script src="assets/js/checkliste.js?v=202609181442" defer></script> <script src="assets/js/checkliste.js?v=202609181521" defer></script>
<script src="assets/js/vorlagen.js?v=202609181442" defer></script> <script src="assets/js/vorlagen.js?v=202609181521" defer></script>
<script src="assets/js/content.js?v=202609181442" defer></script> <script src="assets/js/content.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+5 -5
View File
@@ -36,15 +36,15 @@
<link rel="preload" as="image" href="assets/img/buehne/crew-gate-hoch-580.webp" <link rel="preload" as="image" href="assets/img/buehne/crew-gate-hoch-580.webp"
type="image/webp" media="(max-width: 899px)" /> type="image/webp" media="(max-width: 899px)" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/crew-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/crew-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/crew-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/crew-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<!-- Diese Wand steht NUR auf der Adresse von Team Dogi (ueberall sonst <!-- Diese Wand steht NUR auf der Adresse von Team Dogi (ueberall sonst
404, siehe crewWeiche). Sie darf das Haus deshalb direkt nennen 404, siehe crewWeiche). Sie darf das Haus deshalb direkt nennen
statt ueber den Platzhalter haus.css zu gehen -- und sie MUSS es: statt ueber den Platzhalter haus.css zu gehen -- und sie MUSS es:
Eine Zugangswand, die sich erst nach dem Umbiegen faerbt, waere Eine Zugangswand, die sich erst nach dem Umbiegen faerbt, waere
die eine Seite, auf der man das Umfaerben sieht. --> die eine Seite, auf der man das Umfaerben sieht. -->
<link rel="stylesheet" href="assets/css/crew-haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/crew-haus.css?v=202609181521" />
</head> </head>
<body class="gate gate--crew"> <body class="gate gate--crew">
@@ -339,6 +339,6 @@
</main> </main>
</div> </div>
<script src="assets/js/gate.js?v=202609181442" defer></script> <script src="assets/js/gate.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+4 -4
View File
@@ -22,25 +22,25 @@
}, },
"icons": [ "icons": [
{ {
"src": "/assets/img/app-symbole/crew-192.png?v=202609181442", "src": "/assets/img/app-symbole/crew-192.png?v=202609181521",
"sizes": "192x192", "sizes": "192x192",
"type": "image/png", "type": "image/png",
"purpose": "any" "purpose": "any"
}, },
{ {
"src": "/assets/img/app-symbole/crew-512.png?v=202609181442", "src": "/assets/img/app-symbole/crew-512.png?v=202609181521",
"sizes": "512x512", "sizes": "512x512",
"type": "image/png", "type": "image/png",
"purpose": "any" "purpose": "any"
}, },
{ {
"src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609181442", "src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609181521",
"sizes": "192x192", "sizes": "192x192",
"type": "image/png", "type": "image/png",
"purpose": "maskable" "purpose": "maskable"
}, },
{ {
"src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609181442", "src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609181521",
"sizes": "512x512", "sizes": "512x512",
"type": "image/png", "type": "image/png",
"purpose": "maskable" "purpose": "maskable"
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. --> eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/dateien.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/dateien.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026). <!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an -- kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. --> ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -108,10 +108,10 @@
</main> </main>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/glocke.js?v=202609181442" defer></script> <script src="assets/js/glocke.js?v=202609181521" defer></script>
<script src="assets/js/dateien.js?v=202609181442" defer></script> <script src="assets/js/dateien.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+12 -12
View File
@@ -7,14 +7,14 @@
<meta name="robots" content="noindex, nofollow" /> <meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" /> <link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/entwicklung.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -102,9 +102,9 @@
</main> </main>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/entwicklung.js?v=202609181442" defer></script> <script src="assets/js/entwicklung.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+4 -4
View File
@@ -30,9 +30,9 @@
<link rel="preload" as="image" href="assets/img/buehne/gate-hoch-580.webp" <link rel="preload" as="image" href="assets/img/buehne/gate-hoch-580.webp"
type="image/webp" media="(max-width: 899px)" /> type="image/webp" media="(max-width: 899px)" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
</head> </head>
<body class="gate"> <body class="gate">
@@ -401,6 +401,6 @@
</main> </main>
</div> </div>
<script src="assets/js/gate.js?v=202609181442" defer></script> <script src="assets/js/gate.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. --> eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/kalender.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/kalender.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026). <!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an -- kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. --> ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -377,11 +377,11 @@
</main> </main>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/glocke.js?v=202609181442" defer></script> <script src="assets/js/glocke.js?v=202609181521" defer></script>
<script src="assets/js/kalender.js?v=202609181442" defer></script> <script src="assets/js/kalender.js?v=202609181521" defer></script>
<script src="assets/js/kalender-abo.js?v=202609181442" defer></script> <script src="assets/js/kalender-abo.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. --> eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/leistung.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/leistung.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026). <!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an -- kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. --> ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -444,10 +444,10 @@
</form> </form>
</dialog> </dialog>
<script src="assets/js/wahl.js?v=202609181442"></script> <script src="assets/js/wahl.js?v=202609181521"></script>
<script src="assets/js/bereiche.js?v=202609181442"></script> <script src="assets/js/bereiche.js?v=202609181521"></script>
<script src="assets/js/kopf.js?v=202609181442"></script> <script src="assets/js/kopf.js?v=202609181521"></script>
<script src="assets/js/glocke.js?v=202609181442"></script> <script src="assets/js/glocke.js?v=202609181521"></script>
<script src="assets/js/leistung.js?v=202609181442"></script> <script src="assets/js/leistung.js?v=202609181521"></script>
</body> </body>
</html> </html>
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. --> eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/personen.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/personen.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026). <!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an -- kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. --> ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -423,10 +423,10 @@
</main> </main>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/glocke.js?v=202609181442" defer></script> <script src="assets/js/glocke.js?v=202609181521" defer></script>
<script src="assets/js/personen.js?v=202609181442" defer></script> <script src="assets/js/personen.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. --> eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/profil.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026). <!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an -- kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. --> ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -214,11 +214,11 @@
</main> </main>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/glocke.js?v=202609181442" defer></script> <script src="assets/js/glocke.js?v=202609181521" defer></script>
<script src="assets/js/steckbrief.js?v=202609181442" defer></script> <script src="assets/js/steckbrief.js?v=202609181521" defer></script>
<script src="assets/js/profil.js?v=202609181442" defer></script> <script src="assets/js/profil.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+12 -12
View File
@@ -7,14 +7,14 @@
<meta name="robots" content="noindex, nofollow" /> <meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" /> <link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/treff.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -134,9 +134,9 @@
Beim ersten Anlauf fehlte die Zeile auf genau diesen beiden neuen Beim ersten Anlauf fehlte die Zeile auf genau diesen beiden neuen
Seiten. Gefunden hat das nicht das Auge, sondern pruef-css-klassen, Seiten. Gefunden hat das nicht das Auge, sondern pruef-css-klassen,
die seit dem 07.09. jede Seite danach absucht. --> die seit dem 07.09. jede Seite danach absucht. -->
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/rechte.js?v=202609181442" defer></script> <script src="assets/js/rechte.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. --> eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/report.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/report.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026). <!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an -- kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. --> ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -106,11 +106,11 @@
</main> </main>
<script src="assets/js/ki.js?v=202609181442" defer></script> <script src="assets/js/ki.js?v=202609181521" defer></script>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/glocke.js?v=202609181442" defer></script> <script src="assets/js/glocke.js?v=202609181521" defer></script>
<script src="assets/js/report.js?v=202609181442" defer></script> <script src="assets/js/report.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. --> eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/scouting.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/scouting.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026). <!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an -- kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. --> ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -173,10 +173,10 @@
</div> </div>
</div> </div>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/glocke.js?v=202609181442" defer></script> <script src="assets/js/glocke.js?v=202609181521" defer></script>
<script src="assets/js/scouting.js?v=202609181442" defer></script> <script src="assets/js/scouting.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+12 -12
View File
@@ -12,21 +12,21 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. --> eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<!-- Nur die Startseite: Begrüßungskachel, Uhr, Glockenmulde. Siehe <!-- Nur die Startseite: Begrüßungskachel, Uhr, Glockenmulde. Siehe
den Hinweis in start.css -- dort stand es bis zum 07.09.2026. --> den Hinweis in start.css -- dort stand es bis zum 07.09.2026. -->
<link rel="stylesheet" href="assets/css/heim.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/heim.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026). <!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an -- kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. --> ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -674,10 +674,10 @@
</p> </p>
</main> </main>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/glocke.js?v=202609181442" defer></script> <script src="assets/js/glocke.js?v=202609181521" defer></script>
<script src="assets/js/start.js?v=202609181442" defer></script> <script src="assets/js/start.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. --> eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/startcheck.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/startcheck.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026). <!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an -- kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. --> ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -85,11 +85,11 @@
</main> </main>
<script src="assets/js/ki.js?v=202609181442" defer></script> <script src="assets/js/ki.js?v=202609181521" defer></script>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/glocke.js?v=202609181442" defer></script> <script src="assets/js/glocke.js?v=202609181521" defer></script>
<script src="assets/js/startcheck.js?v=202609181442" defer></script> <script src="assets/js/startcheck.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. --> eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/profil.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026). <!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an -- kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. --> ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -146,11 +146,11 @@
</main> </main>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/auskunft.js?v=202609181442" defer></script> <script src="assets/js/auskunft.js?v=202609181521" defer></script>
<script src="assets/js/glocke.js?v=202609181442" defer></script> <script src="assets/js/glocke.js?v=202609181521" defer></script>
<script src="assets/js/steckbrief.js?v=202609181442" defer></script> <script src="assets/js/steckbrief.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+13 -13
View File
@@ -7,18 +7,18 @@
<meta name="robots" content="noindex, nofollow" /> <meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" /> <link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/entwicklung.css?v=202609181521" />
<!-- Wegen des Kastens „Anfragen“ ueber der Liste. Eine eigene Kopie <!-- Wegen des Kastens „Anfragen“ ueber der Liste. Eine eigene Kopie
der Regel hier waere die zweite Stelle, an der dieselbe Sache der Regel hier waere die zweite Stelle, an der dieselbe Sache
anders aussieht. --> anders aussieht. -->
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/bewerbung.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -111,9 +111,9 @@
</main> </main>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/talente.js?v=202609181442" defer></script> <script src="assets/js/talente.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+13 -13
View File
@@ -7,20 +7,20 @@
<meta name="robots" content="noindex, nofollow" /> <meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" /> <link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/team.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/team.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026). <!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an -- kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. --> ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -110,10 +110,10 @@
</main> </main>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/glocke.js?v=202609181442" defer></script> <script src="assets/js/glocke.js?v=202609181521" defer></script>
<script src="assets/js/team.js?v=202609181442" defer></script> <script src="assets/js/team.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+12 -12
View File
@@ -7,20 +7,20 @@
<meta name="robots" content="noindex, nofollow" /> <meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" /> <link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/team.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/team.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026). <!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an -- kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. --> ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<!-- `data-buehne` steht hier fest und nicht im Skript: Die Kachel zu <!-- `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 Kaputt ist dabei nichts -- er funktioniert. Genau deshalb faellt
es niemandem auf, und deshalb prueft pruef-css-klassen die es niemandem auf, und deshalb prueft pruef-css-klassen die
ABHAENGIGKEIT statt der Optik. Hier hat sie angeschlagen. --> ABHAENGIGKEIT statt der Optik. Hier hat sie angeschlagen. -->
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/teamlage.js?v=202609181442" defer></script> <script src="assets/js/teamlage.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+11 -11
View File
@@ -7,13 +7,13 @@
<meta name="robots" content="noindex, nofollow" /> <meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" /> <link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/bereich.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -112,9 +112,9 @@
</main> </main>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/teilen.js?v=202609181442" defer></script> <script src="assets/js/teilen.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+12 -12
View File
@@ -7,14 +7,14 @@
<meta name="robots" content="noindex, nofollow" /> <meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" /> <link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/treff.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -126,9 +126,9 @@
</main> </main>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/treff-moderation.js?v=202609181442" defer></script> <script src="assets/js/treff-moderation.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+13 -13
View File
@@ -7,18 +7,18 @@
<meta name="robots" content="noindex, nofollow" /> <meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" /> <link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/treff.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein. Auf der Adresse des <!-- haus.css MUSS die letzte Stilvorlage sein. Auf der Adresse des
Treffs biegt der Server genau diesen Dateinamen auf crew-haus.css Treffs biegt der Server genau diesen Dateinamen auf crew-haus.css
um, und die Seite kommt schon beim ERSTEN Abruf in Lila und um, und die Seite kommt schon beim ERSTEN Abruf in Lila und
Babyblau an -- ohne Umfaerben im Browser, ohne Flackern. --> Babyblau an -- ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -211,10 +211,10 @@
Beim ersten Anlauf fehlte die Zeile auf genau diesen beiden neuen Beim ersten Anlauf fehlte die Zeile auf genau diesen beiden neuen
Seiten. Gefunden hat das nicht das Auge, sondern pruef-css-klassen, Seiten. Gefunden hat das nicht das Auge, sondern pruef-css-klassen,
die seit dem 07.09. jede Seite danach absucht. --> die seit dem 07.09. jede Seite danach absucht. -->
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/auskunft.js?v=202609181442" defer></script> <script src="assets/js/auskunft.js?v=202609181521" defer></script>
<script src="assets/js/treff-regeln.js?v=202609181442" defer></script> <script src="assets/js/treff-regeln.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+13 -13
View File
@@ -12,22 +12,22 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. --> eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<!-- Formular- und Dialogstile: seit dem 07.09.2026 steht hier der <!-- Formular- und Dialogstile: seit dem 07.09.2026 steht hier der
Dialog "Creator anlegen". --> Dialog "Creator anlegen". -->
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/uebersicht.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/uebersicht.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026). <!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an -- kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. --> ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -149,11 +149,11 @@
</main> </main>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/glocke.js?v=202609181442" defer></script> <script src="assets/js/glocke.js?v=202609181521" defer></script>
<script src="assets/js/uebersicht.js?v=202609181442" defer></script> <script src="assets/js/uebersicht.js?v=202609181521" defer></script>
</body> </body>
</html> </html>
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. --> eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" /> <meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181442" /> <link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609181521" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181442" /> <link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609181521" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/gate.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/start.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/start.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/aufgaben.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/wissen.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/wissen.css?v=202609181521" />
<link rel="stylesheet" href="assets/css/module.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/module.css?v=202609181521" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026). <!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an -- kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. --> ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609181442" /> <link rel="stylesheet" href="assets/css/haus.css?v=202609181521" />
</head> </head>
<body class="start"> <body class="start">
@@ -190,10 +190,10 @@
</main> </main>
<script src="assets/js/wahl.js?v=202609181442" defer></script> <script src="assets/js/wahl.js?v=202609181521" defer></script>
<script src="assets/js/bereiche.js?v=202609181442" defer></script> <script src="assets/js/bereiche.js?v=202609181521" defer></script>
<script src="assets/js/kopf.js?v=202609181442" defer></script> <script src="assets/js/kopf.js?v=202609181521" defer></script>
<script src="assets/js/glocke.js?v=202609181442" defer></script> <script src="assets/js/glocke.js?v=202609181521" defer></script>
<script src="assets/js/wissen.js?v=202609181442" defer></script> <script src="assets/js/wissen.js?v=202609181521" defer></script>
</body> </body>
</html> </html>