Unsere Seiten, klickbare Video-Karten -- und coturn, das nie vermittelt hat

Drei Sachen von Filipe, und eine davon war ein Fehler von mir.

1. DER VERMITTLUNGSSERVER HAT NIE EINEN ANRUF GETRAGEN.

Am 18.09. habe ich gemeldet, coturn laufe und sei "von aussen
nachgewiesen". Das war falsch. Im Protokoll: null Zuteilungen, jemals.
Beim ersten echten Versuch "check_stun_auth: Cannot find credentials"
-- in /etc/turnserver.conf fehlte `static-auth-secret`, also suchte
coturn die Zugangsdaten in seiner eigenen Datenbank und wies jeden ab.

Und mein eigenes Werkzeug meldete die ganze Zeit gruen:
"Eingetragen, Geheimnis lesbar, Server antwortet." Jedes Wort wahr --
und keins beantwortete die Frage, um die es geht. Eine STUN-Bindung
fragt "bist du da?", eine Zuteilung fragt "traegst du meinen Anruf?".

`tools/turn-sprache.mjs` (neu) versucht jetzt eine ECHTE Zuteilung und
schickt ein Paket darueber. `turn-einrichten.mjs` benutzt sie und hat
drei Ausgaenge statt zwei: traegt / traegt nicht / konnte nicht
nachsehen. Nach Filipes Reparatur auf dem Server gemessen: Zuteilung
auf Port 49199, Paket angekommen -- von Luxemburg durch Deutschland.

2. UNSERE SEITEN -- die erste Kachel, die HINAUSFUEHRT.

Website und VanVans Shop, mit dem Rabattcode DOGI10. Der Code wurde
nachgesehen, nicht abgeschrieben: partnercodes.json, 10 Prozent,
aktiv, "zum weitergeben an Community" -- und giltAufSale: false,
weshalb der Satz zu reduzierten Artikeln danebensteht. Der zweite Code
dort ist als "nur fuer Filipe persoenlich" vermerkt und steht nirgends.

Auch Name und Beschreibung des Shops stammen von der Seite selbst.
Meine erste Fassung hiess "Van's DIY Bastelbedarf" und nannte "Perlen,
Anhaenger, Werkzeug" -- ausgedacht und falsch. Er heisst mit "&" und
verkauft Haekelwerke, Plushies, Schmuck.

Der Ton der Kachel wurde GESUCHT, nicht gewaehlt: sieben geratene
Blautoene schafften den noetigen Abstand nicht, also 372 600
Kombinationen abgesucht. #087ce7, Abstand 0.310, Kontrast 4.51:1.

3. DIE GANZE VIDEO-KARTE KLICKT -- "egal wo man drauf drückt".

Und hier steckte der lehrreiche Fehler: Zuerst stand der Klick-Block
unten vor `return k`. Diese Funktion hat DREI Rueckgabepunkte, und der
erste lautet `if (!darfEintragen()) { ...; return k; }` -- also genau
fuer die Mitglieder, fuer die Filipe es wollte. Beim Team ging es, bei
der Community nicht. Jetzt haengt er dort, wo `videoWeg` entsteht.

Nicht klickbar bleiben Karten ohne Video und solche, deren Video bei
TikTok geloescht wurde -- sonst fuehrt der Klick auf eine Fehlerseite,
und wir sehen kaputt aus.

GEMESSEN: pruef-wege-nach-draussen (neu) 63 Pruefungen, 0 Fehler, auf
1400 und 412 px. Mit Gegenproben: der Kopierknopf oeffnet nichts, der
eigene Knopf wirkt genau einmal, auf einer Karte ohne Video passiert
nichts. pruef-community-sicht 10/0 auf jetzt 11 Seiten, 0 tote Wege.

Nebenbei: Der Pfeil klebte bei langen Untertiteln am Text (auf dem
Bildschirmfoto gesehen). 0.45rem Abstand.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-19 01:17:09 +02:00
co-authored by Claude Opus 5
parent d766be57b6
commit f30227c873
46 changed files with 2096 additions and 411 deletions
+513
View File
@@ -0,0 +1,513 @@
/* =====================================================================
WEGE NACH DRAUSSEN -- 19.09.2026
Zwei Aufträge von Filipe, und beide haben dieselbe Wurzel:
„ich brauch im community bereich noch eine kachel, die wenn man
drauf drückt dan kacheln hat die auf die website von dogfather
universe geht ... und die shop site von vanvan."
„die community soll sofort auf das video geschickt werden sobald
sie auf die kachel drücken, also die ganze kacheln will ich egal
wo man drauf drückt, foto text.... egal wo."
Beides führt AUS dem Treff hinaus. Und beides ist die Sorte
Funktion, die man für erledigt hält, weil sie im Quelltext
dasteht -- bis jemand daraufklickt und nichts passiert.
DIE DREI FALLEN, DIE HIER ABGESICHERT WERDEN:
1. EIN LINK IM LINK. Die Video-Karte enthält bereits Links und
Knöpfe. Hätte ich die ganze Karte in ein <a> gepackt, würde der
Browser das still reparieren -- und ein Teil der Karte wäre tot,
ohne Fehlermeldung. Deshalb wird hier gemessen, dass BEIDES
funktioniert: die Fläche UND die Knöpfe darin.
2. EINE FLÄCHE, DIE ÜBERALL KLICKT. Genauso falsch wäre das
Gegenteil: Wer auf „löschen“ tippt, darf nicht bei TikTok
landen. Die Gegenprobe steht deshalb gleichberechtigt daneben.
3. EIN TOTER WEG. Eine Kachel, die ein Mitglied sieht und nicht
öffnen darf, ist schlimmer als keine. Genau diese Sorte hat die
Sichtprüfung am 18.09. zehnmal gefunden -- alle derselbe
Chat-Knopf.
Aufrufen mit: node server/pruef-wege-nach-draussen.mjs
===================================================================== */
import { mkdtempSync, rmSync, readFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { request as httpAnfrage } from "node:http";
import { portMussFreiSein } from "./helfer-port.mjs";
const PORT = await portMussFreiSein(4196, "pruef-wege-nach-draussen");
const ordner = mkdtempSync(join(tmpdir(), "ws-wege-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
process.env.PORT = String(PORT);
process.env.SITE_ACCESS_SECRET = "lokaler-test";
process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
notbremse(150_000, "pruef-wege-nach-draussen");
await new Promise((r) => setTimeout(r, 700));
process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); });
let fehler = 0, geprueft = 0;
const melde = (t) => console.log(t);
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
const CREW = "crew.dogfather-universe.com";
const WURZEL = join(import.meta.dirname, "..");
function roh(pfad, host, kopf = {}, koerper = null) {
return new Promise((fertig, schief) => {
const a = httpAnfrage({
host: "127.0.0.1", port: PORT, path: pfad,
method: koerper ? "POST" : "GET",
headers: { Host: host, ...(koerper ? { "Content-Type": "application/json" } : {}), ...kopf },
}, (antwort) => {
let text = "";
antwort.on("data", (s) => { text += s; });
antwort.on("end", () => fertig({ code: antwort.statusCode, text, kopf: antwort.headers }));
});
a.on("error", schief);
if (koerper) a.write(JSON.stringify(koerper));
a.end();
});
}
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes } = await import("node:crypto");
/* ---- Leute anlegen ------------------------------------------------- */
const LEUTE = [
["Filipe", "admin", "CODE-DOGI-0001"],
["Mara", "gast", "CODE-GAST-0001"],
["Nico", "creator", "CODE-CREA-0001"],
];
{
const d = new DatabaseSync(process.env.WORKSPACE_DB);
for (const [name, rolle, code] of LEUTE) {
const salz = randomBytes(16).toString("hex");
const hash = scryptSync(code, salz, 64,
{ N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, aktiv, erstellt)"
+ " VALUES (?,?,?,?,?,1,?)")
.run(name, rolle, hash, salz, 32768, new Date().toISOString());
}
d.close();
}
async function anmelden(rolle, code, host) {
let a = await roh("/workspace/api/anmelden", host, {}, { rolle, code });
if (a.code === 400 && a.text.includes("alter_offen")) {
a = await roh("/workspace/api/anmelden", host, {}, { rolle, code, alter_ok: true });
}
return [].concat(a.kopf["set-cookie"] || []).map((z) => z.split(";")[0]).join("; ");
}
/* =======================================================================
1. DIE KACHEL -- GIBT ES SIE, UND FUEHRT SIE IRGENDWOHIN?
======================================================================= */
melde("");
melde("=== 1. Die Kachel „Unsere Seiten“ ===");
/* GEFRAGT WIRD, WAS EIN MITGLIED BEKOMMT -- nicht, was in einer
internen Liste steht. `TREFF_BEREICHE` ist absichtlich nicht
exportiert; `bereicheFuer` ist die Funktion, die auch den Browser
beliefert. Damit misst dieser Abschnitt denselben Weg, den die
Community geht, statt einen daneben. */
const { bereicheFuer, TREFF_BRETTER } = await import("./workspace.js");
{
const TREFF_BEREICHE = bereicheFuer({ rolle: "gast" });
ok(Array.isArray(TREFF_BEREICHE) && TREFF_BEREICHE.length > 0,
`ein Mitglied bekommt ${TREFF_BEREICHE?.length} Kacheln`);
const kachel = TREFF_BEREICHE.find((k) => k.ziel === "unsere-seiten.html");
ok(!!kachel, "sie steht in der Liste der Treff-Kacheln");
ok(kachel?.name === "Unsere Seiten", `sie heisst „${kachel?.name}“`);
ok(!!kachel?.unter && kachel.unter.length > 5,
`und sagt, was dahinter steckt („${kachel?.unter}“)`);
/* SIE DARF KEIN BRETT SEIN. Waere sie eins, wuerde der Server ein
Brett „unsere-seiten“ erwarten, das es nicht gibt -- und die
Ableitung unten nimmt sie nur deshalb nicht mit, weil im Ziel kein
„?b=“ steht. Das ist eine stille Abhaengigkeit, also wird sie
gemessen. */
ok(!TREFF_BRETTER.includes("unsere-seiten"),
`sie ist kein Brett (${TREFF_BRETTER.length} Bretter abgeleitet)`);
ok(TREFF_BRETTER.length === 7,
`es bleiben genau die sieben Bretter (${TREFF_BRETTER.join(", ")})`);
/* DAS RASTER. Drei Spalten, „Der Treff“ belegt zwei -- die Summe
muss durch drei teilbar sein, sonst klafft eine Luecke. */
const plaetze = TREFF_BEREICHE.reduce((n, k) => n + (k.gross ? 2 : 1), 0);
ok(plaetze % 3 === 0,
`das Raster geht auf: ${TREFF_BEREICHE.length} Kacheln = ${plaetze} Plaetze, ${plaetze / 3} volle Reihen`);
/* EIGENER TON. Zwei Kacheln mit derselben Farbe nebeneinander sind
auf einem Bildschirmfoto nicht zu unterscheiden. */
const toene = TREFF_BEREICHE.map((k) => k.ton);
ok(new Set(toene).size === toene.length,
`jede Kachel hat einen eigenen Ton (${toene.join(", ")})`);
/* UND DEN TON MUSS ES IM CSS GEBEN. Fehlt er, faellt die Kachel auf
die Vorgabefarbe zurueck -- und sieht aus wie eine andere. */
const css = readFileSync(join(WURZEL, "workspace/assets/css/start.css"), "utf8");
ok(css.includes(`[data-ton="${kachel?.ton}"]`),
`Ton ${kachel?.ton} ist im CSS hinterlegt`);
/* UND DAS ZEICHEN. Ein unbekannter Name gibt ein leeres Feld -- ohne
Fehlermeldung. */
const zeichenDatei = readFileSync(join(WURZEL, "workspace/assets/js/bereiche.js"), "utf8");
ok(new RegExp(`^\\s{4}${kachel?.zeichen}: \\{`, "m").test(zeichenDatei),
`das Zeichen „${kachel?.zeichen}“ gibt es wirklich`);
}
/* =======================================================================
2. DARF SIE AUCH JEMAND OEFFNEN? -- der tote Weg
======================================================================= */
melde("");
melde("=== 2. Wer darf hinein ===");
const kGast = await anmelden("gast", "CODE-GAST-0001", CREW);
const kDogi = await anmelden("admin", "CODE-DOGI-0001", CREW);
const kCreator = await anmelden("creator", "CODE-CREA-0001", CREW);
{
ok(!!kGast && !!kDogi, "Mitglied und DogFather sind angemeldet");
const a = await roh("/workspace/unsere-seiten.html", CREW, { cookie: kGast });
ok(a.code === 200, `ein Mitglied bekommt die Seite (${a.code})`);
ok(a.text.includes("DOGI10"), "der Rabattcode steht darin");
ok(a.text.includes("vans-diy-bastelbedarf.com"), "die Adresse des Shops steht darin");
ok(a.text.includes("dogfather-universe.com"), "die Adresse der Website steht darin");
/* KEIN PERSOENLICHER CODE AUF EINER SEITE FUER ALLE. In derselben
Datei des Shops steht ein zweiter, der ausdruecklich „Nur fuer
Filipe persoenlich" ist. Stuende er hier, waere er binnen eines
Tages in jedem Gruppenchat. */
ok(!/FILIPE\d/i.test(a.text),
"der persoenliche Partnercode steht NICHT darin");
const b = await roh("/workspace/unsere-seiten.html", CREW, { cookie: kDogi });
ok(b.code === 200, `DogFather auch (${b.code})`);
/* GEGENPROBE -- ohne sie beweist „200“ nichts. Die Agentur hat mit
dem Treff nichts zu tun. */
const c = await roh("/workspace/unsere-seiten.html", CREW, { cookie: kCreator });
ok(c.code === 403 || c.code === 302,
`ein Creator kommt NICHT hinein (${c.code})`);
const d = await roh("/workspace/unsere-seiten.html", CREW);
ok(d.code === 302 || d.code === 401 || d.code === 403,
`ohne Anmeldung auch nicht (${d.code})`);
}
/* =======================================================================
3. EIN HIGHLIGHT MIT VIDEO -- die Daten dafuer
======================================================================= */
melde("");
melde("=== 3. Drei Karten zum Vergleich ===");
const VIDEO = "https://www.tiktok.com/@dogfather0804/video/7500000000000000000";
{
const d = new DatabaseSync(process.env.WORKSPACE_DB);
const wer = d.prepare("SELECT id FROM personen WHERE name = 'Filipe'").get().id;
const jetzt = new Date().toISOString();
/* (a) mit Video -- muss klickbar sein */
d.prepare("INSERT INTO eintraege (bereich, art, titel, text, datum, status, erstellt, erstellt_von,"
+ " quelle_url, quelle_kanal) VALUES (?,?,?,?,?,?,?,?,?,?)")
.run("highlight", "clip", "Wer ist euer Komet", "Schreibt es in die Kommentare",
jetzt.slice(0, 10), "offen", jetzt, wer, VIDEO, "tiktok");
/* (b) ohne Video -- darf NICHT klickbar sein */
d.prepare("INSERT INTO eintraege (bereich, art, titel, text, datum, status, erstellt, erstellt_von)"
+ " VALUES (?,?,?,?,?,?,?,?)")
.run("highlight", "clip", "Nur ein Bild", "Ohne Quelle",
jetzt.slice(0, 10), "offen", jetzt, wer);
/* (c) Video bei TikTok geloescht -- darf NICHT klickbar sein, sonst
fuehrt der Klick auf eine Fehlerseite und WIR sehen kaputt aus */
d.prepare("INSERT INTO eintraege (bereich, art, titel, text, datum, status, erstellt, erstellt_von,"
+ " quelle_url, quelle_kanal, quelle_fehlt_seit) VALUES (?,?,?,?,?,?,?,?,?,?,?)")
.run("highlight", "clip", "Nicht mehr da", "Wurde geloescht",
jetzt.slice(0, 10), "offen", jetzt, wer, VIDEO, "tiktok", jetzt);
/* EIN ECHTES BILD AN DIE ERSTE KARTE.
Filipe hat „foto" ausdrücklich genannt -- es ist die größte Fläche
der Karte und das, worauf ein Daumen zuerst landet. Ohne
angehängtes Bild meldete dieser Abschnitt beim ersten Lauf
„Bildweg nicht gemessen", und damit wäre genau der Teil ungeprüft
geblieben, um den es geht.
ES MUSS EIN ECHTES PNG SEIN. Die Bildroute entscheidet an den
ersten Bytes der Datei, nicht am behaupteten Typ -- und schlägt
sie fehl, entfernt der Fehlerbehandler im Browser den Rahmen
wieder. Eine leere Datei hätte also dasselbe Loch hinterlassen,
nur unauffälliger. */
const EIN_PNG = Buffer.from(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==",
"base64");
{
const { mkdirSync, writeFileSync } = await import("node:fs");
const bildOrdner = join(ordner, "dateien");
mkdirSync(bildOrdner, { recursive: true });
writeFileSync(join(bildOrdner, "probe-standbild.png"), EIN_PNG);
const mitVideoId = d.prepare(
"SELECT id FROM eintraege WHERE bereich = 'highlight' AND quelle_url IS NOT NULL"
+ " AND quelle_fehlt_seit IS NULL").get().id;
d.prepare("INSERT INTO dateien (name_original, name_datei, groesse, typ, status,"
+ " hochgeladen_von, erstellt, eintrag_id) VALUES (?,?,?,?,?,?,?,?)")
.run("standbild.png", "probe-standbild.png", EIN_PNG.length, "image/png",
"freigegeben", wer, jetzt, mitVideoId);
}
/* FREIGEBEN -- sonst sieht ein Mitglied gar nichts.
Im Treff ist ein Beitrag erst sichtbar, wenn jemand aus dem Team
ihn freigegeben hat (`treff_freigaben`). Beim ersten Lauf hat
genau das gefehlt: Die drei Karten standen in der Datenbank, das
Brett blieb leer, und vier Prüfungen meldeten „0 Karten" -- was wie
ein Fehler am Klickverhalten aussah und keiner war. Wer eine
Oberfläche prüft, muss den Zustand herstellen, in dem sie im
Alltag steht. */
const wann = new Date().toISOString();
for (const z of d.prepare("SELECT id FROM eintraege WHERE bereich = 'highlight'").all()) {
d.prepare("INSERT INTO treff_freigaben (eintrag_id, zeitpunkt, von) VALUES (?,?,?)")
.run(z.id, wann, wer);
}
const n = d.prepare("SELECT COUNT(*) AS n FROM eintraege WHERE bereich = 'highlight'").get().n;
const f = d.prepare("SELECT COUNT(*) AS n FROM treff_freigaben").get().n;
d.close();
ok(n === 3, `drei Karten angelegt: mit Video, ohne Video, geloescht (${n})`);
ok(f === 3, `und alle drei freigegeben, sonst sieht ein Mitglied nichts (${f})`);
}
/* =======================================================================
4. IM BROWSER -- klickt die Flaeche wirklich?
ALLES DAVOR IST VORARBEIT. Ob eine Karte anklickbar ist, entscheidet
sich nicht im Quelltext, sondern unter einem Finger.
======================================================================= */
melde("");
melde("=== 4. Im Browser ===");
{
let pw = null;
try {
pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
} catch { /* dritter Ausgang unten */ }
if (!pw) {
/* DRITTER AUSGANG: nicht „bestanden“, sondern „konnte nicht
nachsehen" -- und die Zaehlung sagt das auch. */
melde(" -- Playwright ist nicht da -- im Browser wurde NICHT nachgesehen.");
melde(" Das ist kein Befund. Die Abschnitte 1-3 gelten weiter.");
} else {
const browser = await pw.chromium.launch();
try {
for (const breite of [1400, 412]) {
melde("");
melde(` --- Breite ${breite} px ---`);
const ctx = await browser.newContext({
viewport: { width: breite, height: 950 },
isMobile: breite < 700, hasTouch: breite < 700 });
/* ANMELDUNG ALS KEKS, NICHT ÜBER DAS FORMULAR. Der Weg über die
Oberfläche prüft hier nichts, was nicht anderswo schon
geprüft wird -- er bricht nur bei jeder Änderung an der
Anmeldeseite, und dann sieht diese Prüfung kaputt aus,
obwohl an den Wegen nach draußen nichts fehlt. */
await ctx.addCookies(kGast.split("; ").filter(Boolean).map((z) => {
const [name, ...rest] = z.split("=");
return { name, value: rest.join("="), domain: "127.0.0.1", path: "/" };
}));
const seite = await ctx.newPage();
const kaputt = [];
seite.on("pageerror", (e) => kaputt.push(String(e).slice(0, 140)));
await seite.goto(`http://127.0.0.1:${PORT}/workspace/start.html`,
{ waitUntil: "networkidle" });
await seite.waitForTimeout(900);
/* ---- 4a. Steht die Kachel wirklich auf dem Bildschirm? ---- */
const kachel = await seite.evaluate(() => {
const a = [...document.querySelectorAll('a[href*="unsere-seiten"]')][0];
if (!a) return null;
const r = a.getBoundingClientRect();
return { text: a.textContent.trim().slice(0, 40), breit: Math.round(r.width),
hoch: Math.round(r.height), sichtbar: r.width > 0 && r.height > 0 };
});
ok(!!kachel && kachel.sichtbar,
`die Kachel ist da und sichtbar (${kachel?.breit}x${kachel?.hoch} px)`);
/* GROSS GENUG FUER EINEN DAUMEN. 44 px ist das Mass, unter dem
ein Ziel auf dem Handy regelmaessig verfehlt wird. */
ok((kachel?.hoch || 0) >= 44,
`und hoch genug zum Antippen (${kachel?.hoch} px, mindestens 44)`);
/* ---- 4b. Die Seite selbst ---- */
await seite.goto(`http://127.0.0.1:${PORT}/workspace/unsere-seiten.html`,
{ waitUntil: "networkidle" });
await seite.waitForTimeout(500);
/* window.open abfangen, damit keine echten Fenster aufgehen --
und damit messbar wird, WOHIN es gegangen waere. */
await seite.evaluate(() => {
window.__wohin = [];
window.open = (u) => { window.__wohin.push(String(u)); return null; };
});
const karten = await seite.evaluate(() =>
[...document.querySelectorAll(".aussen[data-weg]")].map((k) => k.dataset.weg));
ok(karten.length === 2, `zwei Karten nach draussen (${karten.length})`);
ok(karten.some((w) => w.includes("dogfather-universe.com")), "eine zur Website");
ok(karten.some((w) => w.includes("vans-diy-bastelbedarf.com")), "eine zum Shop");
/* KLICK AUF DEN TEXT -- nicht auf den Knopf. Genau das war
Filipes Wunsch: „egal wo man drauf drückt“. */
await seite.click(".aussen--shop .aussen__satz");
await seite.waitForTimeout(250);
let wohin = await seite.evaluate(() => window.__wohin);
ok(wohin.length === 1 && wohin[0].includes("vans-diy-bastelbedarf"),
`ein Klick auf den TEXT fuehrt zum Shop (${wohin.join(", ") || "nichts"})`);
/* GEGENPROBE: Der Kopierknopf darf NICHT zusaetzlich die Karte
ausloesen. Ohne diese Zeile waere „die Flaeche klickt“ auch
dann gruen, wenn sie ueberall klickt -- auch dort, wo es
stoert. */
await seite.evaluate(() => { window.__wohin = []; });
await seite.click("#code-knopf");
await seite.waitForTimeout(250);
wohin = await seite.evaluate(() => window.__wohin);
ok(wohin.length === 0,
`der Kopierknopf oeffnet NICHTS (${wohin.length} Fenster)`);
const hinweis = await seite.textContent("#code-hinweis");
ok(/kopiert|markiert|abschreiben/.test(hinweis || ""),
`und sagt, was er getan hat: ${(hinweis || "").trim()}`);
/* GEGENPROBE 2: Der eigene Knopf oeffnet genau EINMAL -- nicht
zweimal, weil die Karte mitklickt. */
await seite.evaluate(() => {
window.__wohin = [];
/* Der Knopf ist ein echter Link; sein Standardverhalten
unterbinden, sonst navigiert die Prueferin weg. */
for (const a of document.querySelectorAll(".aussen__knopf")) {
a.addEventListener("click", (e) => {
e.preventDefault();
window.__wohin.push(a.href);
});
}
});
await seite.click(".aussen--dogi .aussen__knopf");
await seite.waitForTimeout(250);
wohin = await seite.evaluate(() => window.__wohin);
ok(wohin.length === 1,
`der Knopf „Seite oeffnen“ wirkt genau einmal (${wohin.length}x)`);
/* ---- 4c. Die Highlight-Karten ---- */
await seite.goto(`http://127.0.0.1:${PORT}/workspace/bereich.html?b=highlight`,
{ waitUntil: "networkidle" });
await seite.waitForTimeout(1200);
await seite.evaluate(() => {
window.__wohin = [];
window.open = (u) => { window.__wohin.push(String(u)); return null; };
});
const stand = await seite.evaluate(() =>
[...document.querySelectorAll(".eintrag-karte")].map((k) => ({
titel: k.querySelector(".eintrag-titel")?.textContent?.trim() || "",
klickbar: k.dataset.klickbar === "ja",
})));
ok(stand.length === 3, `drei Karten stehen da (${stand.length})`);
const mitVideo = stand.find((s) => s.titel.includes("Komet"));
const ohneVideo = stand.find((s) => s.titel.includes("Nur ein Bild"));
const fort = stand.find((s) => s.titel.includes("Nicht mehr da"));
ok(mitVideo?.klickbar === true, "die Karte MIT Video ist klickbar");
/* DIE BEIDEN GEGENPROBEN. Ohne sie waere „klickbar“ auch dann
gruen, wenn ALLE Karten klicken -- und ein Klick auf ein
geloeschtes Video fuehrt auf eine Fehlerseite. */
ok(ohneVideo?.klickbar === false, "die Karte OHNE Video ist es nicht");
ok(fort?.klickbar === false,
"und die mit geloeschtem Video auch nicht (sonst Fehlerseite)");
/* Klick auf die Ueberschrift -- „egal wo“. */
const titelWahl = ".eintrag-karte[data-klickbar='ja'] .eintrag-titel";
await seite.click(titelWahl);
await seite.waitForTimeout(250);
wohin = await seite.evaluate(() => window.__wohin);
ok(wohin.length === 1 && wohin[0] === VIDEO,
`ein Klick auf die UEBERSCHRIFT fuehrt zum Video (${wohin[0] || "nichts"})`);
/* Und auf den Fliesstext. */
await seite.evaluate(() => { window.__wohin = []; });
const textWahl = ".eintrag-karte[data-klickbar='ja'] .eintrag-text";
if (await seite.$(textWahl)) {
await seite.click(textWahl);
await seite.waitForTimeout(250);
wohin = await seite.evaluate(() => window.__wohin);
ok(wohin.length === 1 && wohin[0] === VIDEO,
`und ein Klick auf den TEXT ebenso (${wohin[0] || "nichts"})`);
} else {
ok(false, "der Fliesstext der Karte liess sich nicht finden");
}
/* GEGENPROBE: Auf der NICHT klickbaren Karte passiert nichts. */
await seite.evaluate(() => { window.__wohin = []; });
const stiller = ".eintrag-karte:not([data-klickbar='ja']) .eintrag-titel";
await seite.click(stiller);
await seite.waitForTimeout(250);
wohin = await seite.evaluate(() => window.__wohin);
ok(wohin.length === 0,
`auf einer Karte ohne Video passiert NICHTS (${wohin.length})`);
/* DER TIKTOK-KNOPF MUSS WEITER EIN LINK SEIN. Waere er von
einem <a> umschlossen worden, haette der Browser ihn still
aufgeloest -- die haeufigste Art, eine Karte kaputtzumachen. */
const knopf = await seite.evaluate(() => {
const a = document.querySelector(".eintrag-karte[data-klickbar='ja'] .videozeile__weg");
return a ? { tag: a.tagName, href: a.getAttribute("href"),
imLink: !!a.closest("a a") } : null;
});
ok(knopf?.tag === "A" && knopf.href === VIDEO,
`der TikTok-Knopf ist noch ein echter Link (${knopf?.tag})`);
ok(knopf?.imLink === false, "und steckt in keinem zweiten Link");
/* DAS BILD FUEHRT ZUM VIDEO, nicht mehr ins Vollbild -- das war
Filipes „foto“. Ohne Bilddatei gibt es keinen Bildstreifen;
dann ist hier nichts zu messen, und das muss man sehen. */
const bild = await seite.evaluate(() => {
const a = document.querySelector(".eintrag-karte[data-klickbar='ja'] .bildstreifen__feld");
return a ? { href: a.getAttribute("href"), label: a.getAttribute("aria-label") } : null;
});
if (bild) {
ok(bild.href === VIDEO, `das Bild fuehrt zum Video (${bild.href})`);
ok(/Video ansehen/.test(bild.label || ""),
`und sagt das auch vorlesbar („${bild.label}“)`);
} else {
/* KEIN STILLES UEBERSPRINGEN. Das Bild ist angelegt; fehlt der
Streifen trotzdem, ist etwas kaputt -- entweder lud das
Bild nicht (dann entfernt der Fehlerbehandler den Rahmen)
oder die Karte zeigt gar keine Anhaenge mehr. Beides ist
ein Befund, kein Grund zu schweigen. */
ok(false, "der Bildstreifen fehlt, obwohl ein Bild angehaengt ist");
}
ok(kaputt.length === 0,
`kein Skriptfehler auf dem Weg${kaputt.length ? ": " + kaputt.join(" | ") : ""}`);
await ctx.close();
}
} finally {
await browser.close();
}
}
}
melde("");
melde(`${geprueft} Pruefungen, ${fehler} Fehler`);
melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Rest */ }
process.exit(fehler ? 1 : 0);
+14
View File
@@ -351,6 +351,20 @@ export const SEITEN = {
sondern eine Einladung zur Vergeltung. */
"/workspace/treff-moderation.html": ["modi", "hand", "admin"],
/* UNSERE SEITEN (19.09.2026). Die Website und VanVans Shop, mit dem
Rabattcode fuer die Community.
GENAU DIE VIER ROLLEN DES TREFFS -- also MIT "gast", anders als die
Moderationsseite eine Zeile darueber. Sie ist ausdruecklich FUER die
Community gebaut; ohne "gast" waere die Kachel bei jedem Mitglied
sichtbar und beim Antippen zu. Genau diese Sorte toter Weg hat die
Sichtpruefung am 18.09. zehnmal gefunden.
Die Agentur steht bewusst nicht dabei (siehe TREFF_ROLLEN in
workspace.js): Creator, Scouts und Spicy Media haben mit dem Treff
nichts zu tun. */
"/workspace/unsere-seiten.html": ["gast", "modi", "hand", "admin"],
/* WER SIEHT WAS. DogFather und die rechte Hand (Filipe, 11.09.2026:
"ich will die kategorie auch auf der team dogi seite fuer die
dogfather und rechte hand rollen").
+22
View File
@@ -938,6 +938,28 @@ const TREFF_BEREICHE = [
zeichen: "schutz", ton: 28, ziel: "bereich.html?b=regeln", szene: "studio" },
{ ...TREFF_GRUPPE, name: "Mitmachen", unter: "Wie du dazugeh\u00f6ren kannst",
zeichen: "personen", ton: 32, ziel: "bereich.html?b=mitmachen", szene: "garage" },
/* UNSERE SEITEN (19.09.2026) -- die einzige Kachel, die HINAUSFUEHRT.
Filipe: "ich brauch im community bereich noch eine kachel, die wenn
man drauf dr\u00fcckt dan kacheln hat die auf die website von dogfather
universe geht ... und die shop site von vanvan."
SIE IST KEIN BRETT, sondern eine Seite -- deshalb ein Dateiname als
Ziel und kein "bereich.html?b=...". Die Ableitung von TREFF_BRETTER
weiter unten nimmt sie dadurch von selbst nicht mit, genau wie die
Moderationskachel. Eine zweite Liste, die man pflegen muesste,
entsteht nicht.
UND SIE STEHT AM ENDE. Die sieben davor fuehren nach innen: lesen,
schreiben, mitmachen. Diese eine verlaesst den Treff -- das ist der
letzte Schritt, nicht der erste.
RASTERRECHNUNG (drei Spalten, "Der Treff" belegt zwei): Mit dieser
Kachel sind es fuer die Community 7 + 1 Kacheln = 9 Plaetze, also
genau drei volle Reihen. Vorher waren es acht Plaetze und damit
eine Luecke am Ende. Sie passt also nicht nur inhaltlich. */
{ ...TREFF_GRUPPE, name: "Unsere Seiten", unter: "Website, Shop & dein Rabatt",
zeichen: "hinaus", ton: 38, ziel: "unsere-seiten.html", szene: "portal" },
];
/* MELDUNGEN & MASSNAHMEN -- FUER DAS TEAM, NICHT FUER DIE COMMUNITY
+78 -2
View File
@@ -30,7 +30,8 @@ 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";
import { turnGeheimnisLesen, turnZugang, TURN_GEHEIMNIS_DATEI } from "../server/workspace-turn.js";
import { zuteilungPruefen } from "./turn-sprache.mjs";
const __dirname = dirname(fileURLToPath(import.meta.url));
const DB_PFAD = process.env.WORKSPACE_DB
@@ -188,6 +189,61 @@ for (const a of adressen) {
db.close();
/* ---- TRAEGT DIE VERMITTLUNG AUCH? (19.09.2026) ---------------------
BIS HIERHER HAT DIESES WERKZEUG NUR GEFRAGT: "bist du da?" Und
genau daran ist es gescheitert.
Vom 18. auf den 19.09. stand hier vier Tage lang gruen:
"Eingetragen, Geheimnis lesbar, Server antwortet." Jedes Wort wahr.
Und trotzdem hatte coturn in dieser ganzen Zeit KEINE EINZIGE
Zuteilung geschafft -- in /etc/turnserver.conf fehlte
`static-auth-secret`, also suchte er die Zugangsdaten in seiner
eigenen Datenbank und wies jeden ab. Ich habe Filipe auf Basis
dieser gruenen Zeile gesagt, Anrufe aus fremden Netzen seien
nachgewiesen. Sie waren nie moeglich.
Eine STUN-Bindung beantwortet "laeuft der Dienst". Eine Zuteilung
beantwortet "traegt er einen Anruf". Das sind zwei Fragen, und nur
die zweite ist die, wegen der coturn ueberhaupt aufgesetzt wurde. */
let vermittlung = null;
if (adressen.length && g.wert) {
const erste = adressen.find((a) => /^turns?:/.test(a.urls || ""));
const m = erste && /^turns?:([^:/?]+)(?::(\d+))?/.exec(erste.urls);
if (!m) {
vermittlung = { stand: "weiss_nicht",
grund: "keine turn:-Adresse eingetragen (nur stun: vermittelt nichts)" };
} else {
/* Die Zugangsdaten rechnen wir uns selbst aus -- mit derselben
Funktion, die der Server fuer den Browser benutzt. Waeren es
andere, wuerde die Pruefung etwas messen, das im Betrieb nie
vorkommt. Die Kennung 0 sagt im coturn-Protokoll: niemand
Bestimmtes. */
const z = turnZugang(0, g.wert);
console.log("");
console.log("Zuteilung wird versucht ...");
vermittlung = await zuteilungPruefen({
host: m[1], port: Number(m[2] || 3478),
user: z.username, pass: z.credential,
});
}
if (vermittlung.stand === "traegt") {
const p = vermittlung.zugeteilt;
console.log(` Zuteilung gelingt: ${p.ip}:${p.hafen} (Bereich „${vermittlung.bereich}")`);
if (vermittlung.paket === true) {
console.log(" Ein Testpaket kommt darueber an -- die Vermittlung traegt wirklich.");
} else if (vermittlung.paket === false) {
console.log(` ABER: ${vermittlung.paketGrund}`);
}
} else if (vermittlung.stand === "traegt_nicht") {
console.log(` ZUTEILUNG ABGELEHNT (Fehler ${vermittlung.code})`);
console.log(` ${vermittlung.grund}`);
} else {
console.log(` KONNTE NICHT NACHSEHEN: ${vermittlung.grund}`);
}
}
console.log("");
if (!adressen.length) {
console.log("ERGEBNIS: Keine Adresse eingetragen -- Anrufe klappen nur im selben Netz.");
@@ -201,4 +257,24 @@ if (versucht && erreichbar < versucht) {
console.log(`ERGEBNIS: ${erreichbar} von ${versucht} Adressen antworten.`);
process.exit(1);
}
console.log("ERGEBNIS: Eingetragen, Geheimnis lesbar, Server antwortet.");
/* DREI AUSGAENGE, NICHT ZWEI. "Konnte nicht nachsehen" darf nicht wie
"in Ordnung" aussehen -- sonst entsteht wieder eine gruene Zeile,
die nichts weiss. Rueckgabewert 2 sagt das auch einem Skript. */
if (vermittlung?.stand === "traegt_nicht") {
console.log("ERGEBNIS: Der Server antwortet, aber er VERMITTELT NICHT.");
console.log(" Anrufe aus Mobilfunk und strengen WLANs kommen nicht zustande.");
process.exit(1);
}
if (!vermittlung || vermittlung.stand === "weiss_nicht") {
console.log("ERGEBNIS: Ob die Vermittlung traegt, konnte nicht geprueft werden.");
console.log(` ${vermittlung?.grund || "kein Versuch moeglich"}`);
console.log(" Das ist KEIN Befund -- es heisst nur, dass gerade niemand nachsehen konnte.");
process.exit(2);
}
if (vermittlung.paket === false) {
console.log("ERGEBNIS: Die Zuteilung gelingt, aber kein Paket kommt durch.");
console.log(` ${vermittlung.paketGrund}`);
process.exit(1);
}
console.log("ERGEBNIS: Eingetragen, Geheimnis lesbar, Server antwortet");
console.log(" UND die Vermittlung traegt (Zuteilung geprueft, Paket angekommen).");
+257
View File
@@ -0,0 +1,257 @@
/* =====================================================================
DIE SPRACHE DES VERMITTLUNGSSERVERS (19.09.2026)
WARUM ES DIESE DATEI GIBT -- und warum sie einen Fehler behebt, der
mich Filipes Vertrauen gekostet hat:
Am 18.09. habe ich ihm gemeldet, coturn laufe und sei „von aussen
nachgewiesen". Am 19.09. stellte sich heraus: Der Vermittlungsserver
hatte NIE eine einzige Zuteilung geschafft. In turnserver.conf fehlte
`static-auth-secret`; coturn suchte die Zugangsdaten in seiner
eigenen Datenbank und wies jeden ab.
Und mein Einrichtungswerkzeug meldete die ganze Zeit:
"ERGEBNIS: Eingetragen, Geheimnis lesbar, Server antwortet."
Jedes Wort davon war wahr. Die Adresse war eingetragen, das
Geheimnis war lesbar, der Server antwortete auf eine STUN-Anfrage.
Nur beantwortet das zusammen NICHT die Frage, die zaehlt: Vermittelt
er auch? Eine gruene Meldung, die das Falsche prueft -- genau die
Sorte, vor der in CLAUDE.md seit dem 01.09. ein Absatz steht.
DER UNTERSCHIED IN EINEM SATZ: Eine STUN-Bindung fragt „bist du da?".
Eine Zuteilung fragt „traegst du meinen Anruf?". Das erste kann
gelingen, waehrend das zweite unmoeglich ist -- und genau so war es
vier Tage lang.
Deshalb liegt das Protokoll jetzt HIER und nicht zweimal daneben:
`turn-einrichten.mjs` und `turn-von-aussen.mjs` benutzen dieselbe
Rechnung. Eine zweite Abschrift waere die, die beim naechsten Umbau
auseinanderlaeuft.
===================================================================== */
import { createSocket } from "node:dgram";
import { randomBytes, createHash, createHmac } from "node:crypto";
/** Der feste Erkennungswert aus RFC 5389 -- er steht in jeder
* Nachricht und trennt STUN von allem anderen, was auf dem Port
* ankommt. */
export const KOCHTOPF = 0x2112a442;
/** Die Nachrichtenarten, die wir brauchen. */
export const M = {
bindung: 0x0001, // "wie sehe ich von draussen aus?"
zuteilung: 0x0003, // "gib mir eine Adresse, ueber die man mich erreicht"
erlaubnis: 0x0008, // "von dieser Gegenstelle nehme ich Pakete an"
};
/** Die Felder in einer Nachricht. */
export const A = {
ABBILD: 0x0001, BENUTZER: 0x0006, NACHRICHT_ECHT: 0x0008, FEHLER: 0x0009,
GEGENSTELLE: 0x0012, BEREICH: 0x0014, EINMALWERT: 0x0015,
WEITERLEITUNG: 0x0016, DATEN: 0x0013, TRANSPORT: 0x0019, XOR_ABBILD: 0x0020,
};
/** Antwortarten, die wir erkennen wollen. */
export const ANTWORT = {
zuteilung_ok: 0x0103, zuteilung_nein: 0x0113,
erlaubnis_ok: 0x0108, daten: 0x0017,
};
export function attribut(art, inhalt) {
const fuell = (4 - (inhalt.length % 4)) % 4;
const b = Buffer.alloc(4 + inhalt.length + fuell);
b.writeUInt16BE(art, 0);
b.writeUInt16BE(inhalt.length, 2); // Laenge OHNE Fuellbytes -- das ist die Falle
inhalt.copy(b, 4);
return b;
}
export function nachricht(art, kennung, attribute, zugang) {
let koerper = Buffer.concat(attribute);
const kopf = Buffer.alloc(20);
kopf.writeUInt16BE(art, 0);
kopf.writeUInt32BE(KOCHTOPF, 4);
kennung.copy(kopf, 8);
if (zugang) {
/* Der Pruefwert wird ueber die Nachricht gerechnet, deren Laenge
ihn schon MITZAEHLT -- sonst rechnet die Gegenseite etwas
anderes aus als wir. Das ist der Punkt, an dem eigene
STUN-Versuche typischerweise scheitern. */
kopf.writeUInt16BE(koerper.length + 24, 2);
const schluessel = createHash("md5")
.update(`${zugang.user}:${zugang.bereich}:${zugang.pass}`, "utf8").digest();
const wert = createHmac("sha1", schluessel)
.update(Buffer.concat([kopf, koerper])).digest();
koerper = Buffer.concat([koerper, attribut(A.NACHRICHT_ECHT, wert)]);
}
kopf.writeUInt16BE(koerper.length, 2);
return Buffer.concat([kopf, koerper]);
}
export function lies(b) {
if (b.length < 20) return null;
const art = b.readUInt16BE(0);
const attribute = {};
let p = 20;
while (p + 4 <= b.length) {
const a = b.readUInt16BE(p), l = b.readUInt16BE(p + 2);
attribute[a] = b.subarray(p + 4, p + 4 + l);
p += 4 + l + ((4 - (l % 4)) % 4);
}
return { art, kennung: b.subarray(8, 20), attribute };
}
/** Adressen stehen verodert in der Nachricht -- sonst wuerden Geraete
* unterwegs daran herumbessern, weil sie meinen, sie muessten. */
export function adresse(b, kennung) {
if (!b || b.length < 8) return null;
const hafen = b.readUInt16BE(2) ^ (KOCHTOPF >>> 16);
const schluessel = Buffer.concat([
Buffer.from([(KOCHTOPF >>> 24) & 255, (KOCHTOPF >>> 16) & 255,
(KOCHTOPF >>> 8) & 255, KOCHTOPF & 255]), kennung]);
const ip = [];
for (let i = 0; i < b.length - 4; i++) ip.push(b[4 + i] ^ schluessel[i]);
return { ip: ip.join("."), hafen };
}
/** Eine Adresse als Gegenstellen-Feld verpacken. */
export function gegenstelle(ip) {
const teile = String(ip).split(".").map(Number);
const b = Buffer.alloc(8);
b[1] = 1; // IPv4
b.writeUInt16BE(1 ^ (KOCHTOPF >>> 16), 2);
const s = [(KOCHTOPF >>> 24) & 255, (KOCHTOPF >>> 16) & 255,
(KOCHTOPF >>> 8) & 255, KOCHTOPF & 255];
for (let i = 0; i < 4; i++) b[4 + i] = teile[i] ^ s[i];
return b;
}
export function schicke(s, daten, host, port, frist = 4000) {
return new Promise((fertig) => {
const uhr = setTimeout(() => { s.removeListener("message", zuhoerer); fertig(null); }, frist);
const zuhoerer = (a) => { clearTimeout(uhr); s.removeListener("message", zuhoerer); fertig(a); };
s.on("message", zuhoerer);
s.send(daten, port, host);
});
}
/* =====================================================================
DIE EIGENTLICHE FRAGE: TRAEGT DIE VERMITTLUNG?
Gibt IMMER dreierlei zurueck, nie nur ja/nein:
stand: "traegt" -- eine Zuteilung kam zustande
"traegt_nicht" -- der Server hat abgelehnt (mit Code)
"weiss_nicht" -- wir konnten gar nicht fragen
Der dritte Ausgang ist der wichtige. Ohne ihn sieht „keine Antwort"
genauso aus wie „abgelehnt", und ein Netzproblem wuerde als kaputte
Konfiguration gemeldet.
===================================================================== */
export async function zuteilungPruefen({ host, port = 3478, user, pass,
paketPruefen = true, frist = 4000 }) {
if (!user || !pass) {
return { stand: "weiss_nicht", grund: "keine Zugangsdaten uebergeben" };
}
const s = createSocket("udp4");
try {
await new Promise((f) => s.bind(f));
/* 1. Ohne Anmeldung fragen. Die Ablehnung liefert Bereich und
Einmalwert, die der zweite Versuch braucht. */
const k1 = randomBytes(12);
const a1 = lies(await schicke(s, nachricht(M.zuteilung, k1,
[attribut(A.TRANSPORT, Buffer.from([17, 0, 0, 0]))]), host, port, frist) || Buffer.alloc(0));
if (!a1) {
return { stand: "weiss_nicht",
grund: `keine Antwort von ${host}:${port} -- Dienst aus, Port zu oder Adresse falsch` };
}
const bereich = a1.attribute[A.BEREICH]?.toString("utf8") || "";
const einmal = a1.attribute[A.EINMALWERT];
if (a1.art !== ANTWORT.zuteilung_nein || !bereich || !einmal) {
return { stand: "weiss_nicht",
grund: "der Server verlangt gar keine Anmeldung -- das ist kein coturn im erwarteten Zustand" };
}
/* 2. Mit Anmeldung. DAS ist die Frage, die zaehlt. */
const zugang = { user, pass, bereich };
const k2 = randomBytes(12);
const a2 = lies(await schicke(s, nachricht(M.zuteilung, k2, [
attribut(A.TRANSPORT, Buffer.from([17, 0, 0, 0])),
attribut(A.BENUTZER, Buffer.from(user, "utf8")),
attribut(A.BEREICH, Buffer.from(bereich, "utf8")),
attribut(A.EINMALWERT, einmal),
], zugang), host, port, frist) || Buffer.alloc(0));
if (!a2) return { stand: "weiss_nicht", bereich, grund: "keine Antwort auf die Zuteilung" };
if (a2.art !== ANTWORT.zuteilung_ok) {
const c = a2.attribute[A.FEHLER];
const code = c ? c[2] * 100 + c[3] : 0;
const erklaerung =
code === 401 ? "Die Zugangsdaten passen nicht zum Geheimnis auf dem Server. "
+ "Meist fehlt `static-auth-secret` in /etc/turnserver.conf "
+ "oder es steht ein anderer Wert darin als in der Geheimnisdatei."
: code === 486 || code === 508
? "Die Grenzen in turnserver.conf sind erreicht (Kontingent oder Ports)."
: "Unerwartete Ablehnung.";
return { stand: "traegt_nicht", bereich, code, grund: erklaerung };
}
const weiter = adresse(a2.attribute[A.WEITERLEITUNG], k2);
const meins = adresse(a2.attribute[A.XOR_ABBILD], k2);
if (!weiter) {
return { stand: "traegt_nicht", bereich, code: 0,
grund: "Zuteilung ohne Adresse -- damit laesst sich nichts anfangen" };
}
if (!paketPruefen) {
return { stand: "traegt", bereich, zugeteilt: weiter, wirSind: meins, paket: null };
}
/* 3. Uns selbst als Gegenstelle eintragen und ein Paket schicken.
ERST DAS beantwortet, ob der Portbereich offen ist -- alles
davor beantwortet es NICHT. */
const k3 = randomBytes(12);
const a3 = lies(await schicke(s, nachricht(M.erlaubnis, k3, [
attribut(A.GEGENSTELLE, gegenstelle(meins?.ip || "0.0.0.0")),
attribut(A.BENUTZER, Buffer.from(user, "utf8")),
attribut(A.BEREICH, Buffer.from(bereich, "utf8")),
attribut(A.EINMALWERT, einmal),
], zugang), host, port, frist) || Buffer.alloc(0));
if (a3?.art !== ANTWORT.erlaubnis_ok) {
return { stand: "traegt", bereich, zugeteilt: weiter, wirSind: meins,
paket: false, paketGrund: "die Gegenstelle liess sich nicht eintragen" };
}
const gegen = createSocket("udp4");
try {
await new Promise((f) => gegen.bind(0, f));
const losung = "hallo-vermittlung";
const kam = await new Promise((fertig) => {
const uhr = setTimeout(() => fertig(false), 5000);
s.on("message", (b) => {
const n = lies(b);
if (n?.art === ANTWORT.daten && n.attribute[A.DATEN]) {
clearTimeout(uhr);
fertig(n.attribute[A.DATEN].toString("utf8") === losung);
}
});
gegen.send(Buffer.from(losung), weiter.hafen, weiter.ip);
});
return { stand: "traegt", bereich, zugeteilt: weiter, wirSind: meins,
paket: kam,
paketGrund: kam ? "" : `nichts kam an Port ${weiter.hafen} an -- der Portbereich ist zu` };
} finally {
try { gegen.close(); } catch { /* egal */ }
}
} catch (f) {
return { stand: "weiss_nicht", grund: `Fehler beim Nachsehen: ${f?.message || f}` };
} finally {
try { s.close(); } catch { /* egal */ }
}
}
+4 -4
View File
@@ -5,9 +5,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>Anruf-Probe · Team Dogi</title>
<meta name="robots" content="noindex, nofollow" />
<link rel="stylesheet" href="assets/css/basis.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/kopf.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/basis.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/kopf.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
<style>
/* Bewusst in der Seite und nicht in einer eigenen Datei: Diese Seite
soll auch dann noch etwas anzeigen, wenn genau das schiefgeht,
@@ -58,6 +58,6 @@
<p class="probe__ende" id="rat"></p>
</main>
<script src="assets/js/anruf-probe.js?v=202609190028" defer></script>
<script src="assets/js/anruf-probe.js?v=202609190116" defer></script>
</body>
</html>
+4 -4
View File
@@ -22,25 +22,25 @@
},
"icons": [
{
"src": "/assets/img/app-symbole/workspace-192.png?v=202609190028",
"src": "/assets/img/app-symbole/workspace-192.png?v=202609190116",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/img/app-symbole/workspace-512.png?v=202609190028",
"src": "/assets/img/app-symbole/workspace-512.png?v=202609190116",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609190028",
"src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609190116",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609190028",
"src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609190116",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
+37
View File
@@ -14,6 +14,43 @@
.eintrag-karte[data-dringlich="niedrig"] { border-left-color: #3c4a5a; }
.eintrag-karte[data-status="erledigt"] { opacity: .55; }
/* DIE KLICKBARE KARTE (19.09.2026) -- nur dort, wo ein Video dahinter
liegt.
Sie muss SAGEN, dass sie klickbar ist, bevor jemand darauf tippt.
Eine Fläche, die erst beim Berühren verrät, dass sie ein Weg war,
ist auf dem Handy unsichtbar -- dort gibt es kein Darüberfahren.
Deshalb der ruhige, dauerhaft sichtbare Rand links: Er steht schon
da, bevor die Maus kommt.
Bewusst KEIN Farbwechsel der Fläche und kein Leuchten. Zwischen
siebzehn Karten untereinander wäre das Unruhe, und was hier gefragt
ist, ist eine Einladung, kein Blinken. */
.eintrag-karte[data-klickbar="ja"] {
cursor: pointer;
border-left-color: color-mix(in srgb, var(--akzent, #5aa0e0) 55%, var(--rand));
transition: border-color .18s ease, transform .18s ease, box-shadow .18s ease;
}
.eintrag-karte[data-klickbar="ja"]:hover {
transform: translateY(-2px);
border-color: color-mix(in srgb, var(--akzent, #5aa0e0) 34%, var(--rand));
border-left-color: color-mix(in srgb, var(--akzent, #5aa0e0) 85%, transparent);
box-shadow: 0 10px 22px -16px rgba(0, 0, 0, .9);
}
/* Wer über den Knopf fährt, soll den Knopf spüren und nicht die ganze
Karte wackeln sehen. */
.eintrag-karte[data-klickbar="ja"]:has(.videozeile__weg:hover) {
transform: none;
}
/* Das Standbild bekommt denselben Hinweis: Der Zeiger sagt „hier geht
es weiter", schon bevor man liest. */
.eintrag-karte[data-klickbar="ja"] .bildstreifen__feld { cursor: pointer; }
@media (prefers-reduced-motion: reduce) {
.eintrag-karte[data-klickbar="ja"] { transition: none; }
.eintrag-karte[data-klickbar="ja"]:hover { transform: none; }
}
.eintrag-kopf {
display: flex; align-items: baseline; flex-wrap: wrap; gap: 10px;
}
+27
View File
@@ -1098,6 +1098,24 @@ body.start > main { position: relative; z-index: 1; }
ist die nächste Frage nicht „noch ein Ton", sondern „braucht diese
Kachel wirklich eine eigene Farbe". */
[data-ton="37"] { --ton: #61ff09; } /* Wie geht's dir? */
/* UNSERE SEITEN (19.09.2026) -- GESUCHT, NICHT GEWAEHLT.
Die Treff-Gruppe trug Grün (28, 30, 32), Magenta (29), Orange (31)
und zweimal Rosa (33, 34). Ein achter Ton von Hand danebengesetzt
wäre mit hoher Wahrscheinlichkeit einem davon zu nah gekommen — beim
ersten Versuch war genau das der Fall: sieben geratene Blautöne, und
keiner schaffte den nötigen Abstand.
Deshalb abgesucht statt geschätzt: 372 600 Kombinationen aus
Helligkeit, Buntheit und Farbton, davon 213 260 überhaupt
darstellbar und 140 973 mit ausreichendem Kontrast auf #0a121e.
Gewonnen hat der mit dem GRÖSSTEN Mindestabstand zu allen sieben —
und bei Gleichstand die leisere Fassung, damit kein Neon entsteht.
Ergebnis: OKLCH L=0.59 C=0.185 H=254. Abstand 0.310 zum nächsten
(Ton 33), Kontrast 4.51:1. Blau ist die einzige Ecke, die im Treff
noch frei war — das ist kein Zufall, sondern das Ergebnis. */
[data-ton="38"] { --ton: #087ce7; } /* Unsere Seiten */
/* Rückfallwert für alles, was einen Ton braucht, aber keine Nummer hat.
Steht bewusst in :where() -- damit zählt die Zeile beim Wettstreit der
@@ -1739,6 +1757,15 @@ body.start > main { position: relative; z-index: 1; }
.kachel__pfeil {
position: relative; z-index: 1;
flex: none; margin-left: auto;
/* ABSTAND ZUM UNTERTITEL (19.09.2026, auf dem Bildschirmfoto
gesehen). Bei langen Untertiteln — „Wie du dazugehören kannst",
„Website, Shop & dein Rabatt" — stieß der Text direkt an den
Pfeil, ohne eine Lücke dazwischen. Kaputt war nichts: Der
Untertitel kürzt sich selbst mit „…". Es sah nur gedrängt aus,
und zwar genau auf den Kacheln mit dem meisten zu sagen.
Der Zuschlag nimmt dem Text ein paar Pixel, die er ohnehin
abschneidet. */
padding-left: .45rem;
color: var(--text-still); font-size: 1rem;
transition: color var(--tempo), transform var(--tempo);
}
+288
View File
@@ -0,0 +1,288 @@
/* =====================================================================
UNSERE SEITEN (19.09.2026)
Zwei Karten, die aus dem Treff hinausführen: die Website und
VanVans Shop.
WARUM SIE ANDERS AUSSEHEN ALS DIE BRETTER-KACHELN: Sie tun etwas
anderes. Eine Brett-Kachel öffnet eine Liste im selben Fenster; diese
hier verlassen den Treff. Dass man das vorher sieht, ist kein
Schmuck, sondern die Ansage — deshalb der eigene Zuschnitt, der
deutlich sichtbare Adressname und der Pfeil nach außen.
AUGENSCHONEND, WIE ÜBERALL IM HAUS: Die Farbe liegt im Verlauf und
im Rand, nie in einer leuchtenden Fläche hinter Text. Bewegung gibt
es nur beim Darüberfahren, und `prefers-reduced-motion` schaltet sie
ganz ab.
===================================================================== */
.seiten-gitter {
display: grid;
gap: 1.1rem;
/* EINE REGEL STATT EINER ZAHL. Zwei Spalten, sobald zweimal 21rem
plus Abstand hineinpassen — und sonst eine. Kommt eine dritte
Karte dazu, ordnet sich das von selbst, ohne dass jemand eine
Schwelle nachziehen muss. */
grid-template-columns: repeat(auto-fit, minmax(min(21rem, 100%), 1fr));
margin: 1.4rem 0 0;
}
.aussen {
position: relative;
isolation: isolate;
overflow: hidden;
display: flex;
flex-direction: column;
gap: .75rem;
padding: 1.35rem 1.35rem 1.2rem;
border-radius: 1.1rem;
border: 1px solid color-mix(in srgb, var(--aussen-ton) 30%, transparent);
background:
radial-gradient(120% 100% at 8% 0%,
color-mix(in srgb, var(--aussen-ton) 15%, transparent), transparent 62%),
linear-gradient(160deg,
color-mix(in srgb, var(--aussen-ton) 7%, #0d1523),
#0a111d 68%);
box-shadow: 0 16px 34px -22px color-mix(in srgb, var(--aussen-ton) 75%, transparent);
cursor: pointer;
transition: transform .22s ease, border-color .22s ease, box-shadow .22s ease;
}
/* Die Töne der beiden Karten. Sie stehen hier und nicht im HTML,
damit eine dritte Karte eine Zeile braucht und nicht zehn. */
.aussen--dogi { --aussen-ton: #087ce7; }
.aussen--shop { --aussen-ton: #e0559b; }
/* Der stille Schein im Hintergrund. Liegt UNTER allem (`z-index: -1`)
und ist so schwach, dass er bei Tageslicht kaum auffällt — er soll
Tiefe geben, nicht leuchten. */
.aussen__schein {
position: absolute;
inset: -40% -20% auto -20%;
height: 78%;
z-index: -1;
background: radial-gradient(60% 60% at 50% 50%,
color-mix(in srgb, var(--aussen-ton) 22%, transparent), transparent 70%);
filter: blur(26px);
opacity: .55;
pointer-events: none;
}
.aussen:hover,
.aussen:focus-within {
transform: translateY(-3px);
border-color: color-mix(in srgb, var(--aussen-ton) 52%, transparent);
box-shadow: 0 22px 40px -20px color-mix(in srgb, var(--aussen-ton) 85%, transparent);
}
.aussen__kopf {
display: flex;
align-items: center;
gap: .85rem;
}
.aussen__zeichen {
flex: 0 0 auto;
display: grid;
place-items: center;
width: 3rem;
height: 3rem;
border-radius: .8rem;
border: 1px solid color-mix(in srgb, var(--aussen-ton) 38%, transparent);
background: linear-gradient(150deg,
color-mix(in srgb, var(--aussen-ton) 20%, transparent), transparent 74%);
}
.aussen__zeichen svg {
width: 1.65rem;
height: 1.65rem;
fill: none;
stroke: color-mix(in srgb, var(--aussen-ton) 78%, #dceaf8);
stroke-width: 1.6;
stroke-linecap: round;
stroke-linejoin: round;
}
.aussen__marke {
margin: 0 0 .1rem;
font-size: .74rem;
letter-spacing: .12em;
text-transform: uppercase;
color: color-mix(in srgb, var(--aussen-ton) 55%, #9fb4c9);
}
.aussen__titel {
margin: 0;
font-size: 1.32rem;
line-height: 1.2;
/* Text bleibt hell und ruhig. Die Farbe steckt im Rahmen, nicht in
der Schrift — farbige Überschriften auf dunklem Grund sind der
schnellste Weg zu müden Augen. */
color: #e9f2fb;
}
.aussen__satz {
margin: 0;
color: var(--text-leise, #93a7bd);
font-size: .95rem;
line-height: 1.5;
}
/* Was es auf der Seite gibt. Ruhig gesetzt: Der Punkt links ist ein
kleiner Strich in der Kartenfarbe, kein Aufzählungszeichen — vier
bunte Punkte untereinander wären mehr Unruhe als Information. */
.aussen__liste {
margin: .1rem 0 .2rem;
padding: 0;
list-style: none;
display: grid;
gap: .42rem;
}
.aussen__liste li {
position: relative;
padding-left: 1.05rem;
font-size: .89rem;
line-height: 1.45;
color: var(--text-leise, #93a7bd);
}
.aussen__liste li::before {
content: "";
position: absolute;
left: 0;
top: .62em;
width: .5rem;
height: 1px;
background: color-mix(in srgb, var(--aussen-ton) 60%, transparent);
}
.aussen__weg {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: .6rem;
margin: auto 0 0;
padding-top: .35rem;
}
.aussen__adresse {
font-size: .82rem;
color: color-mix(in srgb, var(--aussen-ton) 45%, #8ea3b9);
word-break: break-all;
}
.aussen__knopf {
flex: 0 0 auto;
display: inline-block;
padding: .55rem 1rem;
border-radius: .6rem;
border: 1px solid color-mix(in srgb, var(--aussen-ton) 45%, transparent);
background: color-mix(in srgb, var(--aussen-ton) 14%, transparent);
color: #eaf3fc;
font-weight: 600;
font-size: .92rem;
text-decoration: none;
/* 44 px hoch, damit ein Daumen ihn trifft. */
min-height: 2.75rem;
display: inline-flex;
align-items: center;
transition: background-color .18s ease, border-color .18s ease;
}
.aussen__knopf:hover,
.aussen__knopf:focus-visible {
background: color-mix(in srgb, var(--aussen-ton) 26%, transparent);
border-color: color-mix(in srgb, var(--aussen-ton) 68%, transparent);
}
/* ---- DER GUTSCHEIN ---------------------------------------------------
Sieht aus wie ein abgerissener Abschnitt, weil genau das gemeint
ist: etwas zum Mitnehmen. Die gestrichelte Linie ist das einzige
Stilmittel — sie sagt "hier abtrennen", ohne ein Wort dafür zu
brauchen. */
.gutschein {
margin: .2rem 0 .1rem;
padding: .9rem 1rem 1rem;
border-radius: .8rem;
border: 1px dashed color-mix(in srgb, var(--aussen-ton) 42%, transparent);
background: color-mix(in srgb, var(--aussen-ton) 8%, transparent);
}
.gutschein__zeile {
display: flex;
align-items: baseline;
gap: .55rem;
margin: 0 0 .6rem;
}
.gutschein__prozent {
font-size: 1.5rem;
font-weight: 700;
line-height: 1;
color: #f2f7fc;
}
.gutschein__wofuer {
font-size: .88rem;
color: var(--text-leise, #93a7bd);
}
.gutschein__code {
display: flex;
align-items: center;
justify-content: space-between;
gap: .7rem;
width: 100%;
min-height: 3rem;
padding: .6rem .9rem;
border-radius: .6rem;
border: 1px solid color-mix(in srgb, var(--aussen-ton) 50%, transparent);
background: rgba(255, 255, 255, .04);
color: #f2f7fc;
cursor: pointer;
text-align: left;
transition: background-color .18s ease, border-color .18s ease;
}
.gutschein__code:hover,
.gutschein__code:focus-visible {
background: rgba(255, 255, 255, .08);
border-color: color-mix(in srgb, var(--aussen-ton) 72%, transparent);
}
.gutschein__wort {
font-family: ui-monospace, "SFMono-Regular", "Cascadia Code", monospace;
font-size: 1.22rem;
font-weight: 700;
letter-spacing: .1em;
}
.gutschein__hinweis {
font-size: .78rem;
color: var(--text-leise, #93a7bd);
text-align: right;
}
/* Nach dem Kopieren steht dort die Bestätigung — in Grün, aber in
einem gedeckten, das auf Dunkel nicht sticht. */
.gutschein__code[data-kopiert="ja"] .gutschein__hinweis { color: #74c79a; }
.gutschein__klein {
margin: .6rem 0 0;
font-size: .78rem;
line-height: 1.45;
color: var(--text-leise, #93a7bd);
}
.seiten-fuss {
margin: 1.4rem 0 0;
font-size: .85rem;
color: var(--text-leise, #93a7bd);
}
/* Wer Bewegung ausgeschaltet hat, bekommt keine. Die Karte bleibt
trotzdem erkennbar bedienbar — über Rand und Schatten. */
@media (prefers-reduced-motion: reduce) {
.aussen,
.aussen__knopf,
.gutschein__code { transition: none; }
.aussen:hover,
.aussen:focus-within { transform: none; }
}
+99 -3
View File
@@ -355,6 +355,82 @@
k.dataset.dringlich = e.dringlichkeit;
k.dataset.status = e.status;
/* DER GANZE KASTEN IST DER WEG ZUM VIDEO (19.09.2026).
Filipe: „die community soll sofort auf das video geschickt werden
sobald sie auf die kachel drücken, also die ganze kacheln will
ich egal wo man drauf drückt, foto text.... egal wo sobald man
drauf drückt weiter geleitet".
Er hat recht, und der Grund ist handfest: Auf dem Handy ist der
Knopf „Auf TikTok ansehen" ein Ziel von rund 150 × 44 px in
einer Karte von 350 × 600. Wer das Bild antippt — die größte und
einladendste Fläche — erwartet, dass etwas passiert, und es
passiert nichts. Das fühlt sich an wie eine kaputte Seite.
NUR DORT, WO ES AUCH EIN VIDEO GIBT. Auf den anderen Brettern
bleibt die Karte eine Karte: Eine Fläche, die manchmal klickt und
manchmal nicht, ist schlimmer als eine, die es nie tut.
UND NUR, SOLANGE DAS VIDEO NOCH DA IST. Ist es bei TikTok
gelöscht (`quelle_fehlt_seit`, Stufe 5), führt der Weg auf eine
Fehlerseite — und wer darauf tippt, hält UNS für kaputt. Dann
bleibt die Karte still, und der Hinweis „Bei TikTok nicht mehr
da" steht weiterhin da, wo er hingehört. */
const videoWeg = (e.quelle_url && !e.quelle_fehlt_seit) ? String(e.quelle_url) : '';
/* ---- UND DER KLICK HAENGT GLEICH HIER DRAN ---------------------
ER STAND ZUERST GANZ UNTEN, VOR `return k` -- und war fuer die
Community wirkungslos. Diese Funktion hat DREI Rueckgabepunkte,
und der erste davon lautet:
if (!darfEintragen()) { k.append(fuss); return k; }
Wer nicht schreiben darf, bekommt die Karte also frueher zurueck
-- und genau das sind die Mitglieder, fuer die Filipe es
ueberhaupt wollte. Beim Team funktionierte es, bei der Community
nicht: der unangenehmste Fehler ueberhaupt, weil er beim
Selbsttest unsichtbar ist.
Deshalb steht es jetzt dort, wo `videoWeg` entsteht: „Diese
Karte ist ein Weg" ist eine Eigenschaft der Daten, kein
Nachtrag am Ende. Damit gilt sie fuer jeden Rueckgabeweg, auch
fuer den naechsten, den jemand einbaut.
WARUM KEIN <a> UM ALLES: In der Karte stecken schon Links (der
TikTok-Knopf, „Daraus wurde", die Bilder) und Knoepfe (loeschen,
melden, zur Aufgabe). Ein Link in einem Link ist ungueltiges
HTML -- der Browser beendet den aeusseren still, und danach ist
ein Teil der Karte tot, ohne Fehlermeldung.
WARUM DIE KARTE NICHT FOKUSSIERBAR WIRD: Der Knopf „Auf TikTok
ansehen" ist bereits der Tastaturweg. Ein zweiter Halt mit
demselben Ziel saehe fuer jemanden, der sich durchtabbt, nach
einem Fehler aus. Die Flaeche ist eine Bequemlichkeit fuer den
Daumen, nicht der eigentliche Weg. */
if (videoWeg) {
k.dataset.klickbar = 'ja';
const eigeneBedeutung = (ziel) =>
!!ziel.closest('a, button, input, textarea, select, label, [role="button"]');
k.addEventListener('click', (ev) => {
if (ev.button !== 0) return;
if (eigeneBedeutung(ev.target)) return;
/* Wer Text markiert, will lesen und nicht weg -- der haeufigste
Aerger an klickbaren Karten ueberhaupt. */
const auswahl = window.getSelection?.();
if (auswahl && !auswahl.isCollapsed && String(auswahl).trim()) return;
window.open(videoWeg, '_blank', 'noopener,noreferrer');
});
/* Mittlere Maustaste: dieselbe Erwartung wie bei einem Link. */
k.addEventListener('auxclick', (ev) => {
if (ev.button !== 1) return;
if (eigeneBedeutung(ev.target)) return;
ev.preventDefault();
window.open(videoWeg, '_blank', 'noopener,noreferrer');
});
}
const kopf = el('div', 'eintrag-kopf');
const marke = el('span', 'marke-art', einstellung.arten[e.art] || e.art);
/* Die Art als Merkmal am Element -- daran haengt der Ton (siehe
@@ -454,17 +530,36 @@
if (e.bilder.length > 1) bg.dataset.mehrere = 'ja';
for (const b of e.bilder) {
const a = el('a', 'bildstreifen__feld');
a.href = `/workspace/api/dateien/${b.id}/bild`;
/* GIBT ES EIN VIDEO, FÜHRT AUCH DAS BILD DORTHIN (19.09.2026).
Vorher öffnete das Bild sich selbst in voller Größe. Bei
einem Clip ist das Bild aber das Standbild des Videos — wer
darauf tippt, will das Video sehen und nicht das Standbild
größer. Genau das meinte Filipe mit „foto".
Ohne Video bleibt es beim Vollbild: Auf den anderen Brettern
ist ein Bild wirklich nur ein Bild. */
a.href = videoWeg || `/workspace/api/dateien/${b.id}/bild`;
a.target = '_blank';
a.rel = 'noopener';
a.rel = videoWeg ? 'noopener noreferrer' : 'noopener';
const bild = el('img', 'bildstreifen__bild');
bild.src = `/workspace/api/dateien/${b.id}/bild`;
bild.loading = 'lazy';
bild.decoding = 'async';
/* Der Titel des Beitrags ist die beste Beschreibung, die es
hier gibt -- ein Dateiname wie "IMG_2831.jpg" sagt niemandem
etwas. */
etwas.
UND DAS ZIEL GEHOERT DAZU (19.09.2026): Seit das Bild zum
Video führt, wäre „Bild zu ..." eine Falschauskunft für jeden,
der die Seite vorlesen lässt -- er würde ein Bild erwarten
und in einer fremden App landen. */
bild.alt = e.titel ? `Bild zu „${e.titel}“` : (b.name || 'Bild');
if (videoWeg) {
a.setAttribute('aria-label',
e.titel ? `Video ansehen: ${e.titel}` : 'Video ansehen');
a.title = 'Öffnet das Video in einem neuen Fenster';
}
/* Ist es doch kein Bild (oder die Datei fehlt), verschwindet
der Rahmen, statt ein kaputtes Symbol zu zeigen. */
bild.addEventListener('error', () => { a.remove(); if (!bg.childNodes.length) bg.remove(); });
@@ -1055,6 +1150,7 @@
}
fuss.append(knoepfe);
k.append(fuss);
return k;
}
+14
View File
@@ -283,6 +283,20 @@
'M3.5 12.6h17',
'M10.9 11.6h2.2v2h-2.2z'],
},
/* HINAUS (19.09.2026) -- fuer "Unsere Seiten".
Jedes andere Zeichen im Haus zeigt auf etwas INNERHALB des
Workspace. Dieses eine muss sagen: hier geht es hinaus. Das
Sinnbild dafuer ist ueberall dasselbe -- ein angefangener Rahmen
mit einem Pfeil, der ihn nach oben rechts verlaesst. Es zu
erfinden waere ein Fehler; wer es kennt, weiss sofort, dass sich
ein neues Fenster oeffnet. */
hinaus: {
/* Nur der Pfeilkopf ist Flaeche -- er ist die Aussage. */
fuellung: ['M13.8 4H20v6.2z'],
linien: ['M11 4.8H5.4A1.4 1.4 0 0 0 4 6.2v12.4a1.4 1.4 0 0 0 1.4 1.4h12.4a1.4 1.4 0 0 0 1.4-1.4V13',
'M13.8 4H20v6.2',
'M19.4 4.6 12 12'],
},
zahlen: {
fuellung: ['M4 16 9.5 11.5 13 14 20 6.5 20 19.5 4 19.5z'],
/* Der Linienzug selbst, die Grundlinie darunter und ein Punkt am
+144
View File
@@ -0,0 +1,144 @@
/* =====================================================================
UNSERE SEITEN (19.09.2026)
Zwei Dinge macht diese Datei, und beide gehen auf denselben Satz von
Filipe zurück: „egal wo man drauf drückt ... sobald man drauf drückt
weiter geleitet".
1. Die GANZE Karte ist ein Weg nach draußen, nicht nur der Knopf.
2. Der Rabattcode wandert mit einem Tippen in die Zwischenablage.
WARUM DIE KARTE KEIN <a> IST, DAS ALLES UMSCHLIESST: Darin steckt ein
Knopf (der Code) und ein weiterer Link (der Knopf „Shop öffnen").
Ein Link in einem Link ist kein gültiges HTML — Browser reparieren
das still, indem sie den äußeren beenden, und dann ist die halbe
Karte tot. Deshalb: ein Klick-Zuhörer auf der Karte, der Kinder mit
eigener Bedeutung in Ruhe lässt.
UND DER LINK BLEIBT TROTZDEM EIN LINK. Wer mit der Tastatur
unterwegs ist, bekommt weiterhin den Knopf „Seite öffnen" als
Haltepunkt. Die Karte selbst wird NICHT fokussierbar gemacht: Das
gäbe einen zweiten Tab-Halt mit demselben Ziel — für jemanden, der
sich durchtabbt, sieht das aus wie ein Fehler.
===================================================================== */
(() => {
'use strict';
/* ---- 1. DIE GANZE KARTE IST DER WEG ------------------------------ */
/** Hat der Klick schon eine eigene Bedeutung getroffen?
*
* Ein Klick auf „Shop öffnen" soll den Shop öffnen (und nicht
* zusätzlich die Karte auslösen), ein Klick auf den Code soll
* kopieren. Alles andere — Bild, Überschrift, Text, leere Fläche —
* ist Karte. */
function hatEigeneBedeutung(ziel) {
return !!ziel.closest('a, button, input, textarea, select, label, [role="button"]');
}
/** Markiert gerade jemand Text? Dann will er lesen, nicht weg.
*
* Ohne diese Abfrage reißt es einen beim Loslassen der Maus aus der
* Seite — der häufigste Ärger an klickbaren Karten überhaupt. */
function markiertGerade() {
const s = window.getSelection?.();
return !!s && !s.isCollapsed && String(s).trim().length > 0;
}
for (const karte of document.querySelectorAll('.aussen[data-weg]')) {
const weg = karte.dataset.weg;
if (!weg) continue;
karte.addEventListener('click', (e) => {
if (e.button !== 0) return; /* nur die linke Taste */
if (hatEigeneBedeutung(e.target)) return;
if (markiertGerade()) return;
/* `noopener` ist Pflicht: Ohne das könnte die geöffnete Seite
über `window.opener` auf unsere zugreifen. */
window.open(weg, '_blank', 'noopener,noreferrer');
});
/* Mittlere Maustaste = „in neuem Tab" — dieselbe Erwartung wie bei
einem echten Link, also dieselbe Antwort. */
karte.addEventListener('auxclick', (e) => {
if (e.button !== 1) return;
if (hatEigeneBedeutung(e.target)) return;
e.preventDefault();
window.open(weg, '_blank', 'noopener,noreferrer');
});
}
/* ---- 2. DER RABATTCODE ------------------------------------------- */
const knopf = document.getElementById('code-knopf');
const hinweis = document.getElementById('code-hinweis');
if (knopf && hinweis) {
const urtext = hinweis.textContent;
let zurueck = 0;
/** Kopieren mit Auffanglösung.
*
* `navigator.clipboard` gibt es nur auf gesicherten Verbindungen,
* und auf einigen älteren Android-Browsern gar nicht. Scheitert es,
* wird der Code wenigstens MARKIERT — dann reicht ein langes
* Antippen. Ein Knopf, der nichts tut und nichts sagt, ist
* schlimmer als gar keiner. */
async function kopieren(text) {
try {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
return 'kopiert';
}
} catch { /* weiter zur Auffanglösung */ }
try {
const feld = document.createElement('textarea');
feld.value = text;
feld.setAttribute('readonly', '');
feld.style.position = 'fixed';
feld.style.opacity = '0';
document.body.append(feld);
feld.select();
const ging = document.execCommand?.('copy');
feld.remove();
if (ging) return 'kopiert';
} catch { /* auch das ging nicht */ }
/* Letzter Ausweg: den Code im Knopf markieren. */
try {
const wort = knopf.querySelector('.gutschein__wort');
if (wort) {
const bereich = document.createRange();
bereich.selectNodeContents(wort);
const auswahl = window.getSelection();
auswahl.removeAllRanges();
auswahl.addRange(bereich);
return 'markiert';
}
} catch { /* dann eben gar nicht */ }
return 'nein';
}
knopf.addEventListener('click', async () => {
const code = knopf.dataset.code || '';
if (!code) return;
const wie = await kopieren(code);
hinweis.textContent =
wie === 'kopiert' ? 'kopiert ✓' :
wie === 'markiert' ? 'markiert – jetzt kopieren' :
'bitte von Hand abschreiben';
knopf.dataset.kopiert = wie === 'kopiert' ? 'ja' : 'nein';
/* Nach ein paar Sekunden steht wieder da, was zu tun ist —
sonst sieht die Karte beim nächsten Besuch so aus, als sei
gerade eben etwas passiert. */
clearTimeout(zurueck);
zurueck = setTimeout(() => {
hinweis.textContent = urtext;
delete knopf.dataset.kopiert;
}, 4000);
});
}
})();
+12 -12
View File
@@ -12,19 +12,19 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -291,10 +291,10 @@
</form>
</dialog>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/glocke.js?v=202609190028" defer></script>
<script src="assets/js/aufgaben.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/glocke.js?v=202609190116" defer></script>
<script src="assets/js/aufgaben.js?v=202609190116" defer></script>
</body>
</html>
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/automation.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/automation.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -168,10 +168,10 @@
</main>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/glocke.js?v=202609190028" defer></script>
<script src="assets/js/automation.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/glocke.js?v=202609190116" defer></script>
<script src="assets/js/automation.js?v=202609190116" defer></script>
</body>
</html>
+12 -12
View File
@@ -7,14 +7,14 @@
<meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -105,9 +105,9 @@
</main>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/befinden.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/befinden.js?v=202609190116" defer></script>
</body>
</html>
+18 -18
View File
@@ -12,24 +12,24 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609190116" />
<!-- Wegen des Blocks „Willst du dazugehoeren?" auf dem Brett
„Mitmachen". Eine eigene Kopie der Regel hier waere die zweite
Stelle, an der dieselbe Sache anders aussieht. -->
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -251,14 +251,14 @@
</main>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/glocke.js?v=202609190028" defer></script>
<script src="assets/js/checkliste.js?v=202609190028" defer></script>
<script src="assets/js/ampel.js?v=202609190028" defer></script>
<script src="assets/js/schulung.js?v=202609190028" defer></script>
<script src="assets/js/vorlagen.js?v=202609190028" defer></script>
<script src="assets/js/bereich.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/glocke.js?v=202609190116" defer></script>
<script src="assets/js/checkliste.js?v=202609190116" defer></script>
<script src="assets/js/ampel.js?v=202609190116" defer></script>
<script src="assets/js/schulung.js?v=202609190116" defer></script>
<script src="assets/js/vorlagen.js?v=202609190116" defer></script>
<script src="assets/js/bereich.js?v=202609190116" defer></script>
</body>
</html>
+15 -15
View File
@@ -7,17 +7,17 @@
<meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein - siehe treff-regeln.html. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -108,10 +108,10 @@
</main>
<!-- Der Fuß gehört zu jeder Seite - siehe pruef-css-klassen. -->
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/auskunft.js?v=202609190028" defer></script>
<script src="assets/js/bewerben.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/auskunft.js?v=202609190116" defer></script>
<script src="assets/js/bewerben.js?v=202609190116" defer></script>
</body>
</html>
+14 -14
View File
@@ -7,16 +7,16 @@
<meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein - siehe treff-regeln.html. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -80,10 +80,10 @@
</main>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/auskunft.js?v=202609190028" defer></script>
<script src="assets/js/bewerbungen.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/auskunft.js?v=202609190116" defer></script>
<script src="assets/js/bewerbungen.js?v=202609190116" defer></script>
</body>
</html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/calls.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/calls.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -89,11 +89,11 @@
</main>
<script src="assets/js/ki.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/glocke.js?v=202609190028" defer></script>
<script src="assets/js/calls.js?v=202609190028" defer></script>
<script src="assets/js/ki.js?v=202609190116" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/glocke.js?v=202609190116" defer></script>
<script src="assets/js/calls.js?v=202609190116" defer></script>
</body>
</html>
+14 -14
View File
@@ -12,26 +12,26 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<!-- aufgaben.css bringt die Dialog- und Formularstile mit (.dialog,
.neu__zeile, .dialog__knoepfe). Sie stehen dort seit dem
Aufgabenbrett; sie hierher zu kopieren hiesse, sie an zwei Stellen
zu pflegen. Dieselbe Lösung nutzen kalender.html und content.html.
Gemeldet von pruef-css-klassen.mjs, bevor jemand einen ungestylten
Dialog zu sehen bekam. -->
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/chat.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/chat.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -341,15 +341,15 @@
</form>
</dialog>
<script src="assets/js/bereiche.js?v=202609190028"></script>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028"></script>
<script src="assets/js/glocke.js?v=202609190028"></script>
<script src="assets/js/bereiche.js?v=202609190116"></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116"></script>
<script src="assets/js/glocke.js?v=202609190116"></script>
<!-- ANRUF VOR CHAT (18.09.2026): anruf.js stellt `window.anrufStarten`
bereit, und chat.js baut die Knoepfe nur, wenn es das gibt.
Andersherum waere die Reihenfolge eine stille Abhaengigkeit --
die Knoepfe fehlten, und niemand wuesste warum. -->
<script src="assets/js/anruf.js?v=202609190028"></script>
<script src="assets/js/chat.js?v=202609190028"></script>
<script src="assets/js/anruf.js?v=202609190116"></script>
<script src="assets/js/chat.js?v=202609190116"></script>
</body>
</html>
+15 -15
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/content.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/content.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -179,12 +179,12 @@
</main>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/glocke.js?v=202609190028" defer></script>
<script src="assets/js/checkliste.js?v=202609190028" defer></script>
<script src="assets/js/vorlagen.js?v=202609190028" defer></script>
<script src="assets/js/content.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/glocke.js?v=202609190116" defer></script>
<script src="assets/js/checkliste.js?v=202609190116" defer></script>
<script src="assets/js/vorlagen.js?v=202609190116" defer></script>
<script src="assets/js/content.js?v=202609190116" defer></script>
</body>
</html>
+5 -5
View File
@@ -36,15 +36,15 @@
<link rel="preload" as="image" href="assets/img/buehne/crew-gate-hoch-580.webp"
type="image/webp" media="(max-width: 899px)" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/crew-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/crew-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/crew-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/crew-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<!-- Diese Wand steht NUR auf der Adresse von Team Dogi (ueberall sonst
404, siehe crewWeiche). Sie darf das Haus deshalb direkt nennen
statt ueber den Platzhalter haus.css zu gehen -- und sie MUSS es:
Eine Zugangswand, die sich erst nach dem Umbiegen faerbt, waere
die eine Seite, auf der man das Umfaerben sieht. -->
<link rel="stylesheet" href="assets/css/crew-haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/crew-haus.css?v=202609190116" />
</head>
<body class="gate gate--crew">
@@ -339,6 +339,6 @@
</main>
</div>
<script src="assets/js/gate.js?v=202609190028" defer></script>
<script src="assets/js/gate.js?v=202609190116" defer></script>
</body>
</html>
+4 -4
View File
@@ -22,25 +22,25 @@
},
"icons": [
{
"src": "/assets/img/app-symbole/crew-192.png?v=202609190028",
"src": "/assets/img/app-symbole/crew-192.png?v=202609190116",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/img/app-symbole/crew-512.png?v=202609190028",
"src": "/assets/img/app-symbole/crew-512.png?v=202609190116",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609190028",
"src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609190116",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609190028",
"src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609190116",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/dateien.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/dateien.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -108,10 +108,10 @@
</main>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/glocke.js?v=202609190028" defer></script>
<script src="assets/js/dateien.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/glocke.js?v=202609190116" defer></script>
<script src="assets/js/dateien.js?v=202609190116" defer></script>
</body>
</html>
+12 -12
View File
@@ -7,14 +7,14 @@
<meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -102,9 +102,9 @@
</main>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/entwicklung.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/entwicklung.js?v=202609190116" defer></script>
</body>
</html>
+4 -4
View File
@@ -30,9 +30,9 @@
<link rel="preload" as="image" href="assets/img/buehne/gate-hoch-580.webp"
type="image/webp" media="(max-width: 899px)" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
</head>
<body class="gate">
@@ -401,6 +401,6 @@
</main>
</div>
<script src="assets/js/gate.js?v=202609190028" defer></script>
<script src="assets/js/gate.js?v=202609190116" defer></script>
</body>
</html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/kalender.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/kalender.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -377,11 +377,11 @@
</main>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/glocke.js?v=202609190028" defer></script>
<script src="assets/js/kalender.js?v=202609190028" defer></script>
<script src="assets/js/kalender-abo.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/glocke.js?v=202609190116" defer></script>
<script src="assets/js/kalender.js?v=202609190116" defer></script>
<script src="assets/js/kalender-abo.js?v=202609190116" defer></script>
</body>
</html>
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/leistung.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/leistung.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -444,10 +444,10 @@
</form>
</dialog>
<script src="assets/js/wahl.js?v=202609190028"></script>
<script src="assets/js/bereiche.js?v=202609190028"></script>
<script src="assets/js/kopf.js?v=202609190028"></script>
<script src="assets/js/glocke.js?v=202609190028"></script>
<script src="assets/js/leistung.js?v=202609190028"></script>
<script src="assets/js/wahl.js?v=202609190116"></script>
<script src="assets/js/bereiche.js?v=202609190116"></script>
<script src="assets/js/kopf.js?v=202609190116"></script>
<script src="assets/js/glocke.js?v=202609190116"></script>
<script src="assets/js/leistung.js?v=202609190116"></script>
</body>
</html>
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/personen.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/personen.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -423,10 +423,10 @@
</main>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/glocke.js?v=202609190028" defer></script>
<script src="assets/js/personen.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/glocke.js?v=202609190116" defer></script>
<script src="assets/js/personen.js?v=202609190116" defer></script>
</body>
</html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -214,11 +214,11 @@
</main>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/glocke.js?v=202609190028" defer></script>
<script src="assets/js/steckbrief.js?v=202609190028" defer></script>
<script src="assets/js/profil.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/glocke.js?v=202609190116" defer></script>
<script src="assets/js/steckbrief.js?v=202609190116" defer></script>
<script src="assets/js/profil.js?v=202609190116" defer></script>
</body>
</html>
+12 -12
View File
@@ -7,14 +7,14 @@
<meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -134,9 +134,9 @@
Beim ersten Anlauf fehlte die Zeile auf genau diesen beiden neuen
Seiten. Gefunden hat das nicht das Auge, sondern pruef-css-klassen,
die seit dem 07.09. jede Seite danach absucht. -->
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/rechte.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/rechte.js?v=202609190116" defer></script>
</body>
</html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/report.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/report.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -106,11 +106,11 @@
</main>
<script src="assets/js/ki.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/glocke.js?v=202609190028" defer></script>
<script src="assets/js/report.js?v=202609190028" defer></script>
<script src="assets/js/ki.js?v=202609190116" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/glocke.js?v=202609190116" defer></script>
<script src="assets/js/report.js?v=202609190116" defer></script>
</body>
</html>
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/scouting.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/scouting.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -173,10 +173,10 @@
</div>
</div>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/glocke.js?v=202609190028" defer></script>
<script src="assets/js/scouting.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/glocke.js?v=202609190116" defer></script>
<script src="assets/js/scouting.js?v=202609190116" defer></script>
</body>
</html>
+12 -12
View File
@@ -12,21 +12,21 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<!-- Nur die Startseite: Begrüßungskachel, Uhr, Glockenmulde. Siehe
den Hinweis in start.css -- dort stand es bis zum 07.09.2026. -->
<link rel="stylesheet" href="assets/css/heim.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/heim.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -678,10 +678,10 @@
</p>
</main>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/glocke.js?v=202609190028" defer></script>
<script src="assets/js/start.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/glocke.js?v=202609190116" defer></script>
<script src="assets/js/start.js?v=202609190116" defer></script>
</body>
</html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/startcheck.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/startcheck.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -85,11 +85,11 @@
</main>
<script src="assets/js/ki.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/glocke.js?v=202609190028" defer></script>
<script src="assets/js/startcheck.js?v=202609190028" defer></script>
<script src="assets/js/ki.js?v=202609190116" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/glocke.js?v=202609190116" defer></script>
<script src="assets/js/startcheck.js?v=202609190116" defer></script>
</body>
</html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -146,11 +146,11 @@
</main>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/auskunft.js?v=202609190028" defer></script>
<script src="assets/js/glocke.js?v=202609190028" defer></script>
<script src="assets/js/steckbrief.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/auskunft.js?v=202609190116" defer></script>
<script src="assets/js/glocke.js?v=202609190116" defer></script>
<script src="assets/js/steckbrief.js?v=202609190116" defer></script>
</body>
</html>
+13 -13
View File
@@ -7,18 +7,18 @@
<meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609190116" />
<!-- Wegen des Kastens „Anfragen“ ueber der Liste. Eine eigene Kopie
der Regel hier waere die zweite Stelle, an der dieselbe Sache
anders aussieht. -->
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -111,9 +111,9 @@
</main>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/talente.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/talente.js?v=202609190116" defer></script>
</body>
</html>
+13 -13
View File
@@ -7,20 +7,20 @@
<meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/team.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/team.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -110,10 +110,10 @@
</main>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/glocke.js?v=202609190028" defer></script>
<script src="assets/js/team.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/glocke.js?v=202609190116" defer></script>
<script src="assets/js/team.js?v=202609190116" defer></script>
</body>
</html>
+12 -12
View File
@@ -7,20 +7,20 @@
<meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/team.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/team.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<!-- `data-buehne` steht hier fest und nicht im Skript: Die Kachel zu
@@ -84,9 +84,9 @@
Kaputt ist dabei nichts -- er funktioniert. Genau deshalb faellt
es niemandem auf, und deshalb prueft pruef-css-klassen die
ABHAENGIGKEIT statt der Optik. Hier hat sie angeschlagen. -->
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/teamlage.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/teamlage.js?v=202609190116" defer></script>
</body>
</html>
+11 -11
View File
@@ -7,13 +7,13 @@
<meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -112,9 +112,9 @@
</main>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/teilen.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/teilen.js?v=202609190116" defer></script>
</body>
</html>
+12 -12
View File
@@ -7,14 +7,14 @@
<meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -126,9 +126,9 @@
</main>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/treff-moderation.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/treff-moderation.js?v=202609190116" defer></script>
</body>
</html>
+13 -13
View File
@@ -7,18 +7,18 @@
<meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein. Auf der Adresse des
Treffs biegt der Server genau diesen Dateinamen auf crew-haus.css
um, und die Seite kommt schon beim ERSTEN Abruf in Lila und
Babyblau an -- ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -221,10 +221,10 @@
Beim ersten Anlauf fehlte die Zeile auf genau diesen beiden neuen
Seiten. Gefunden hat das nicht das Auge, sondern pruef-css-klassen,
die seit dem 07.09. jede Seite danach absucht. -->
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/auskunft.js?v=202609190028" defer></script>
<script src="assets/js/treff-regeln.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/auskunft.js?v=202609190116" defer></script>
<script src="assets/js/treff-regeln.js?v=202609190116" defer></script>
</body>
</html>
+13 -13
View File
@@ -12,22 +12,22 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<!-- Formular- und Dialogstile: seit dem 07.09.2026 steht hier der
Dialog "Creator anlegen". -->
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/uebersicht.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/uebersicht.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -149,11 +149,11 @@
</main>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/glocke.js?v=202609190028" defer></script>
<script src="assets/js/uebersicht.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/glocke.js?v=202609190116" defer></script>
<script src="assets/js/uebersicht.js?v=202609190116" defer></script>
</body>
</html>
+197
View File
@@ -0,0 +1,197 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>Unsere Seiten · Der Treff</title>
<meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/unsere-seiten.css?v=202609190116" />
</head>
<body class="start">
<div class="schleier" aria-hidden="true"></div>
<header class="kopfleiste">
<button type="button" class="zurueck-knopf" id="zurueck" hidden>
<svg class="zurueck-knopf__pfeil" viewBox="0 0 24 24" aria-hidden="true">
<path d="M14.5 5.5 8 12l6.5 6.5" />
</svg>
<span class="zurueck-knopf__text">Zurück</span>
</button>
<p class="marke"><span class="marke__text"><a class="zurueck" href="start.html">Der&nbsp;Treff</a> · Unsere Seiten</span></p>
<div class="kopfleiste__rechts">
<span class="wer" id="wer">…</span>
<button type="button" class="abmelden" id="abmelden">Abmelden</button>
</div>
</header>
<main class="inhalt inhalt--breit">
<section class="kopf-zeile">
<div>
<p class="marke">Der Treff · Unsere Seiten</p>
<h1 class="titel">Unsere Seiten</h1>
<p class="unterzeile">
Was es außerhalb vom Treff noch gibt — und wo dein Rabatt wartet.
</p>
</div>
</section>
<!-- ==================================================================
WARUM DIESE SEITE EXISTIERT (19.09.2026)
Filipe: "ich brauch im community bereich noch eine kachel, die
wenn man drauf drückt dan kacheln hat die auf die website von
dogfather universe geht ... und die shop site von vanvan."
ZWEI GRUENDE, WARUM DAS EINE EIGENE SEITE IST und nicht zwei
Kacheln direkt in der Uebersicht:
1. Die sieben Kacheln im Treff fuehren alle NACH INNEN -- auf
ein Brett, auf dem man liest und schreibt. Zwei Kacheln
dazwischen, die den Browser verlassen, waeren ein Bruch: Man
tippt auf etwas, das aussieht wie ein Brett, und ist
ploetzlich woanders. Eine eigene Seite sagt vorher, was
passiert.
2. Ein Rabattcode braucht Platz. Er muss gross, kopierbar und
mit seinen Bedingungen dastehen -- sonst wird er falsch
erinnert und jemand steht enttaeuscht an der Kasse.
DER CODE IST ECHT UND WURDE NACHGESEHEN, nicht abgeschrieben:
Er steht in partnercodes.json von VanVans Shop als DOGI10, 10
Prozent, aktiv, ohne Ablaufdatum, mit der Notiz "Zum weitergeben
an Community, Zuschauer, Freunde etc." -- und mit
"giltAufSale": false. Genau deshalb steht der Satz zu den
reduzierten Artikeln hier drunter. Ein Rabatt, der an der Kasse
anders ausfaellt als angekuendigt, kostet mehr Vertrauen, als
er Umsatz bringt.
Der zweite Code in derselben Datei ist als "Nur fuer Filipe
persoenlich" vermerkt und steht deshalb NIRGENDWO auf dieser
Seite.
============================================================== -->
<div class="seiten-gitter">
<!-- ---- DOGFATHER UNIVERSE ------------------------------------ -->
<article class="aussen aussen--dogi" data-weg="https://dogfather-universe.com/">
<div class="aussen__schein" aria-hidden="true"></div>
<div class="aussen__kopf">
<span class="aussen__zeichen" aria-hidden="true">
<svg viewBox="0 0 24 24">
<circle cx="12" cy="12" r="8.4" />
<path d="M3.6 12h16.8M12 3.6c2.4 2.5 2.4 13.9 0 16.8M12 3.6c-2.4 2.5-2.4 13.9 0 16.8" />
</svg>
</span>
<div>
<p class="aussen__marke">Die Website</p>
<h2 class="aussen__titel">DogFather&nbsp;Universe</h2>
</div>
</div>
<p class="aussen__satz">
Die Heimat von DogFather als Streamer, der HasiDog-Community
und seiner Arbeit als Manager bei Spicy Media.
</p>
<!-- WAS ES DORT GIBT -- ABGELESEN, NICHT AUSGEDACHT.
Die Punkte stammen aus der Navigation von
dogfather-universe.com (streamplan.html, hasidog.html,
zeitreise.html, stimmen.html). Eine erfundene Aufzählung
wäre beim ersten Klick als Lüge aufgeflogen.
UND SIE SIND KEINE LINKS: Die ganze Karte führt zur
Startseite; einzelne Links darin würden diesen Klick
zerschneiden und wären auf dem Handy außerdem zu kleine
Ziele. -->
<ul class="aussen__liste">
<li>Der Streamplan – wann was läuft</li>
<li>HasiDog: die Community rund um Dogi</li>
<li>Die Zeitreise durch alles Bisherige</li>
<li>Stimmen von Leuten, die dabei waren</li>
</ul>
<p class="aussen__weg">
<span class="aussen__adresse">dogfather-universe.com</span>
<a class="aussen__knopf" href="https://dogfather-universe.com/"
target="_blank" rel="noopener noreferrer">Seite öffnen&nbsp;→</a>
</p>
</article>
<!-- ---- VANVANS SHOP ------------------------------------------ -->
<article class="aussen aussen--shop" data-weg="https://vans-diy-bastelbedarf.com/">
<div class="aussen__schein" aria-hidden="true"></div>
<div class="aussen__kopf">
<span class="aussen__zeichen" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path d="M5 8h14l-1.1 11.2a1.4 1.4 0 0 1-1.4 1.3H7.5a1.4 1.4 0 0 1-1.4-1.3z" />
<path d="M9 8V6.4a3 3 0 0 1 6 0V8" />
</svg>
</span>
<div>
<p class="aussen__marke">Der Shop</p>
<!-- NAME UND BESCHREIBUNG STAMMEN VON DER SEITE SELBST
(og:site_name und meta description). Meine erste Fassung
hieß „Van's DIY Bastelbedarf" und nannte „Perlen,
Anhänger, Werkzeug" — beides ausgedacht und beides
falsch. Der Laden heißt mit „&", und er verkauft vor
allem Handgemachtes. -->
<h2 class="aussen__titel">Van's&nbsp;DIY &amp; Bastelbedarf</h2>
</div>
</div>
<p class="aussen__satz">
VanVans eigener Laden: handgemachte Häkelwerke, Plushies,
Schmuck und Bastelzubehör.
</p>
<!-- Der Rabatt. Eigener Block, weil er das Wichtigste auf
dieser Karte ist -- und kopierbar, weil niemand einen Code
abtippen soll, den er sich vorher merken muss. -->
<div class="gutschein">
<p class="gutschein__zeile">
<span class="gutschein__prozent">10&nbsp;%</span>
<span class="gutschein__wofuer">für alle aus der Community</span>
</p>
<button type="button" class="gutschein__code" id="code-knopf"
data-code="DOGI10"
aria-label="Rabattcode DOGI10 kopieren">
<span class="gutschein__wort">DOGI10</span>
<span class="gutschein__hinweis" id="code-hinweis">tippen zum Kopieren</span>
</button>
<p class="gutschein__klein">
Beim Bestellen im Feld für den Gutscheincode eintragen.
Gilt nicht auf bereits reduzierte Artikel.
</p>
</div>
<p class="aussen__weg">
<span class="aussen__adresse">vans-diy-bastelbedarf.com</span>
<a class="aussen__knopf" href="https://vans-diy-bastelbedarf.com/"
target="_blank" rel="noopener noreferrer">Shop öffnen&nbsp;→</a>
</p>
</article>
</div>
<p class="seiten-fuss">
Beide Seiten öffnen sich in einem neuen Fenster — der Treff bleibt
also offen.
</p>
</main>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/unsere-seiten.js?v=202609190116" defer></script>
</body>
</html>
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190028" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190028" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/wissen.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190028" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609190116" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609190116" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/start.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/wissen.css?v=202609190116" />
<link rel="stylesheet" href="assets/css/module.css?v=202609190116" />
<!-- haus.css MUSS die letzte Stilvorlage sein (10.09.2026).
Auf der Agenturadresse ist sie leer -- das Haus hier IST der
Grundzustand. Auf der Adresse von Team Dogi biegt der Server
genau diesen Dateinamen auf crew-haus.css um, und die Seite
kommt schon beim ERSTEN Abruf in Lila und Babyblau an --
ohne Umfaerben im Browser, ohne Flackern. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609190028" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609190116" />
</head>
<body class="start">
@@ -190,10 +190,10 @@
</main>
<script src="assets/js/wahl.js?v=202609190028" defer></script>
<script src="assets/js/bereiche.js?v=202609190028" defer></script>
<script src="assets/js/kopf.js?v=202609190028" defer></script>
<script src="assets/js/glocke.js?v=202609190028" defer></script>
<script src="assets/js/wissen.js?v=202609190028" defer></script>
<script src="assets/js/wahl.js?v=202609190116" defer></script>
<script src="assets/js/bereiche.js?v=202609190116" defer></script>
<script src="assets/js/kopf.js?v=202609190116" defer></script>
<script src="assets/js/glocke.js?v=202609190116" defer></script>
<script src="assets/js/wissen.js?v=202609190116" defer></script>
</body>
</html>