Eine Kachel "Unterstuetzen" -- und die Gespraechsliste neu gebaut

=== 1. UNTERSTUETZEN (neue Kachel im Community-Bereich) ===

Filipe: "ich brauch auch noch eine kachel im community bereich. wo mein
paypal und meine amazon liste sein wird. wo die leute alle supporten
koennen auf andere art anstatt nur tiktok. jeder soll diese kachel sehen
aber nur dogfather soll sie veraendern koennen oder vieles mehr sehen."

Neu: unterstuetzen.html + css + js, server/workspace-unterstuetzung.js,
server/unterstuetzung-tabellen.js. Ton 45 (#7567fe) ist gerechnet, das
Kachelzeichen ist ein Herz ueber zwei offenen Haenden.

DIE WEGE STEHEN IN DER DATENBANK, nicht im Quelltext -- ein Recht, das
man nur ueber einen Entwickler ausueben kann, ist keines. DogFather
schreibt Titel, Text, Knopf und Adresse selbst, blendet Wege aus und
nimmt neue dazu.

DER PAYPAL-LINK STEHT LEER UND UNSICHTBAR DA. Filipe schrieb "mein
paypal kennst du ja schon" -- gesucht im ganzen Haus und im Vault,
nirgends gefunden. Eine Zahlungsadresse zu RATEN waere der
gefaehrlichste Fehler dieser Seite: Geld an einen Fremden, und niemand
merkt es. Also steht dort nichts, der Weg ist ausgeblendet, und die
Seite sagt genau EINER Person, dass er fehlt.

DREI ARTEN, UND DIE DRITTE IST DIE WICHTIGSTE: geld, geschenk, frei.
"Kostet nichts" bekommt dieselbe Kartenform und dieselbe Groesse.
Waeren die freien Wege kleiner oder weiter unten, waere die Aussage
"das ist zweite Wahl" -- und die Mehrheit derer, die hier lesen, waere
damit zweite Wahl.

DIE ZAEHLUNG IST ANONYM, UND ZWAR VON DER TABELLE HER. DogFather sieht,
wie oft ein Weg geoeffnet wurde (7/30 Tage/gesamt). Was er NICHT sieht,
ist WER -- weil es in unterstuetzung_striche keine Spalte dafuer gibt.
Geprueft wird das ueber PRAGMA table_info, nicht ueber eine Abfrage:
eine Abfrage liesse sich morgen erweitern, eine fehlende Spalte nicht.

Schreiben haengt an istDogFather, nicht an istLeitung -- die rechte Hand
fuehrt dieses Team mit und kommt trotzdem nicht an diesen Link. Jede
Adresse wird beim SCHREIBEN geprueft (nur https:// oder eine Seite
dieses Hauses); javascript:, data:, http:// und // werden abgewiesen.

Neu: server/pruef-unterstuetzung.mjs -- 70 Pruefungen, alle gruen.
Sie misst ueber die CREW-ADRESSE: Beim ersten Lauf waren vier Rollen
gruen und der Zuschauer rot, und es sah nach einem Rechtefehler aus.
Es war ein Messfehler -- ueber 127.0.0.1 landet man still im
Agenturhaus, und dort gibt es die Rolle "gast" gar nicht.

=== 2. DIE GESPRAECHSLISTE (screen1 + screen2) ===

Filipe: "ich will dass die komplette kachel viel krasser und geiler
aussieht. der hintergrund der kachel soll gleich bleiben."

Der Hintergrund ist unangetastet. Zwei echte Fehler auf seinem Bild:

  - Bei "Das Rudel" stand das "@" rechts oben und die orange "6" eine
    ZEILE TIEFER. Der Knopf ist ein Raster mit DREI Spalten und bekam
    VIER Kinder -- das vierte fiel um. Ausgerechnet die wichtigste
    Auskunft der Liste landete an der unauffaelligsten Stelle.
  - Jedes Gespraech mit Profilbild haengte das Bild ZWEIMAL ein: ein
    Block stand Zeichen fuer Zeichen doppelt da. Zu sehen war nichts,
    gekostet hat es die doppelte Ladelast bei jedem Neuzeichnen.

Und eine tote Regel: `.chat-raum__knopf[data-an="ja"]` beschrieb die
Schiene am offenen Gespraech -- gesetzt wird aber `data-offen` am <li>.
Die Regel griff nie, und daneben stand eine zweite, blassere Fassung
derselben Sache. Jetzt steht alles einmal, und die Schiene ist da.

Dazu: Zeilen als Karten, 44px-Gesichter, ein Zaehler neben "Gespraeche",
Suchfeld als Pille mit Lupe, runder Farbfleck statt Quadrat, und der
leere Raum rechts bekommt eine Mitte statt eines Satzes in der Ecke.

=== 3. VIER FUNDE NEBENBEI ===

  - supportAufraeumen() war exportiert und wurde NIRGENDS gerufen. Die
    Meldungen samt Bildschirmfotos waeren fuer immer liegen geblieben,
    obwohl "90 Tage" dokumentiert ist. Jetzt im Loeschkonzept.
  - aufgaben_zuteilung fehlte ebenfalls im Loeschkonzept (seit 21.09.).
    pruef-aufbewahrung ist damit wieder gruen.
  - pruef-start-ansicht war rot, seit "Aufgaben" am 23.09. die silberne
    Kachel bekam -- die ueberschreibt ihr --ton absichtlich. Die
    Pruefung nimmt sie jetzt aus UND prueft die Ausnahme selbst.
  - pruef-buehne meldete auf entwicklung.html 1,79:1 Kontrast bei "Wen
    gehst du durch?" -- die Ueberschrift lag direkt auf dem Foto. Sie
    steht jetzt auf einer deckenden Flaeche.

OFFEN: pruef-buehne meldet treff-regeln.html mal "0 von 40", mal "3 von
40" -- dieselbe Seite, verschiedene Antworten. Eine Pruefung, die
schwankt, ist schlimmer als eine rote. Nicht in diesem Zug behoben.

Gemessen: pruef-unterstuetzung 70, pruef-start-ansicht, pruef-buehne
(bis auf den Wackler), pruef-workspace-seiten, pruef-aufbewahrung 45,
pruef-rechtetafel 19, pruef-css-klassen, pruef-chatkachel 36,
pruef-chat-ausbau, pruef-entwicklung-kacheln 31 -- gruen.

Neu: server/mess-chat-liste.mjs (zeigt die Liste mit sieben Gespraechen
verschiedener Art, prueft nichts).

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-24 14:14:25 +02:00
co-authored by Claude Opus 5
parent 5b244ab7ec
commit aa2e1a4ae2
57 changed files with 3349 additions and 678 deletions
+4
View File
@@ -38,6 +38,7 @@ import { uebernahmeRouter } from "./workspace-uebernahme.js";
import { befindenRouter } from "./workspace-befinden.js";
import { hilfeRouter } from "./workspace-hilfe.js";
import { supportRouter } from "./workspace-support.js";
import { unterstuetzungRouter } from "./workspace-unterstuetzung.js";
import { reportRouter } from "./workspace-reports.js";
import { scoutRouter } from "./workspace-scouts.js";
import { callRouter } from "./workspace-calls.js";
@@ -311,6 +312,9 @@ app.use(uebernahmeRouter);
app.use(befindenRouter);
app.use(hilfeRouter);
app.use(supportRouter);
/* Unterstützen (24.09.2026): die Wege, auf denen die Community etwas
beitragen kann – lesen alle, ändern nur DogFather. */
app.use(unterstuetzungRouter);
app.use(bewerbungRouter);
app.use(reportRouter);
app.use(scoutRouter);
+214
View File
@@ -0,0 +1,214 @@
/* =====================================================================
WIE SIEHT DIE GESPRÄCHSLISTE AUS? (24.09.2026)
Filipe, mit zwei Bildschirmfotos: "ich will dass die komplette
kachel viel krasser und geiler aussieht ... viel moderner. hau
richtig rein und mach was spezielles."
---------------------------------------------------------------------
WARUM ES DIESE DATEI GIBT
Es gibt acht `pruef-chat*`-Dateien. Sie beweisen, dass der Chat
FUNKTIONIERT -- dass zwei Leute einander erreichen, dass die
Nachtruhe haelt, dass niemand in einen fremden Raum sieht. Keine
davon zeigt, wie die Liste AUSSIEHT, wenn sieben Gespraeche
verschiedener Art darin stehen: ein Kanal mit Erwaehnung und sechs
Ungelesenen, eine Gruppe, vier Zweiergespraeche mit Gesichtern.
Genau das war der Zustand auf Filipes Bildschirmfoto -- und genau
dort fiel auf, dass die "6" eine Zeile tiefer stand als das "@".
Diese Datei legt den Bestand an, den er sieht, und macht Bilder.
Sie prueft NICHTS und meldet keinen Fehlschlag.
Sie heisst `mess-` und nicht `pruef-`: Die Portvergabe leitet sich
aus den `pruef-`-Namen ab, und eine Messdatei darf die Nummern der
Pruefungen nicht verschieben.
Aufrufen mit: node server/mess-chat-liste.mjs
Bilder: server/mess-chat-liste-breit.png
server/mess-chat-liste-handy.png
===================================================================== */
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
const PORT = 5397;
const ordner = mkdtempSync(join(tmpdir(), "ws-messchat-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
process.env.PORT = String(PORT);
process.env.SITE_ACCESS_SECRET = "lokaler-test";
process.env.SITE_ACCESS_CODE_DOGI = "DOGI-TEST-1";
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(240_000, "mess-chat-liste");
await new Promise((r) => setTimeout(r, 800));
const BASIS = `http://127.0.0.1:${PORT}`;
process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); });
process.on("unhandledRejection", (f) => { console.error("ABSTURZ (Versprechen):", f); process.exit(7); });
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
const d = new DatabaseSync(process.env.WORKSPACE_DB);
const jetzt = new Date().toISOString();
const schluessel = randomBytes(32).toString("hex");
d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)"
+ " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING")
.run("code_kennung_schluessel", schluessel, jetzt);
function anlegen(name, rolle, code) {
const salt = randomBytes(16).toString("hex");
const hash = scryptSync(code, salt, 64,
{ N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
d.prepare("INSERT INTO personen"
+ " (name, rolle, code_hash, code_salt, code_n, code_kennung, aktiv, erstellt)"
+ " VALUES (?,?,?,?,?,?,1,?)")
.run(name, rolle, hash, salt, 32768,
createHmac("sha256", schluessel).update(code).digest("hex"), jetzt);
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
}
/* Dieselbe Besetzung wie auf Filipes Bildschirmfoto -- damit sich das
Bild unmittelbar mit seinem vergleichen laesst. */
const ich = anlegen("Filipe", "admin", "CODE-DOGI-0001");
const leute = [
{ name: "VanVan", rolle: "hand" },
{ name: "Ghost", rolle: "modi" },
{ name: "Diene", rolle: "modi" },
{ name: "Marina", rolle: "modi" },
{ name: "BananaStift", rolle: "modi" },
{ name: "Patrick", rolle: "modi" },
];
for (const p of leute) p.id = anlegen(p.name, p.rolle, `CODE-${p.name.toUpperCase()}-1`);
d.close();
/* ---- Anmelden ------------------------------------------------------- */
async function anmelden(rolle, code) {
const a = await fetch(BASIS + "/workspace/api/anmelden", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ rolle, code }),
});
return (a.headers.getSetCookie?.() || []).map((z) => z.split(";")[0]).join("; ");
}
const keksIch = await anmelden("admin", "CODE-DOGI-0001");
for (const p of leute) p.keks = await anmelden(p.rolle, `CODE-${p.name.toUpperCase()}-1`);
const hin = (weg, keks, koerper) => fetch(BASIS + weg, {
method: "POST", headers: { "Content-Type": "application/json", Cookie: keks },
body: JSON.stringify(koerper),
});
const wer = (name) => leute.find((p) => p.name === name);
/* ---- Die Gespräche -------------------------------------------------- */
const raeume = {};
{
/* Ein Kanal, eine Gruppe, vier Zweiergespräche -- die drei Arten, die
die Liste unterscheiden muss. */
const k = await hin("/workspace/api/chat/kanal", keksIch,
{ kategorie: "", eigener_name: "Chat-Moderation", mit: leute.map((p) => p.id) });
raeume.kanal = (await k.json()).raum_id;
const k2 = await hin("/workspace/api/chat/kanal", keksIch,
{ kategorie: "", eigener_name: "Das Rudel", mit: leute.map((p) => p.id) });
raeume.rudel = (await k2.json()).raum_id;
const g = await hin("/workspace/api/chat/gruppe", keksIch,
{ name: "Abmeldungen \u{1F5D2}", mit: leute.map((p) => p.id) });
raeume.gruppe = (await g.json()).raum_id;
for (const name of ["VanVan", "Ghost", "BananaStift", "Patrick"]) {
const a = await hin("/workspace/api/chat/direkt", keksIch, { mit: wer(name).id });
raeume[name] = (await a.json()).raum_id;
}
}
/* ---- Was darin steht ------------------------------------------------- */
const schreib = (raum, keks, text) =>
hin(`/workspace/api/chat/raeume/${raum}/nachrichten`, keks, { text });
/* Sechs Ungelesene und eine Erwähnung -- der Zustand aus dem
Bildschirmfoto. */
for (let i = 1; i <= 5; i++) {
await schreib(raeume.rudel, wer("Diene").keks,
`Kurze Ansage Nummer ${i} für alle im Rudel.`);
}
await schreib(raeume.rudel, wer("Diene").keks,
"Hallo ihr Lieben, schaut gerne mal rein @Filipe – das wollte ich dir zeigen.");
await schreib(raeume.VanVan, wer("VanVan").keks,
"Da hat Miss was falsch eingegeben. Ich kümmere mich gleich drum.");
await schreib(raeume.Ghost, wer("Ghost").keks,
"Sorry hab's gefunden ist gelöscht. Hatte ich übersehen.");
await schreib(raeume.kanal, wer("Marina").keks, "Ach man \u{1F97A}\u{1F97A}");
await schreib(raeume.gruppe, wer("Diene").keks,
"Guten Morgen ihr Lieben ich schaffe es heute leider nicht.");
await schreib(raeume.BananaStift, wer("BananaStift").keks,
"Community Talk \u{1F6EB} fällt der heute aus?");
await schreib(raeume.Patrick, keksIch, "Alles klar");
/* ---- Bilder ---------------------------------------------------------- */
const { chromium } = await import(
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
const browser = await chromium.launch();
try {
for (const [wie, breite, hoehe] of [["breit", 1280, 900], ["handy", 390, 860]]) {
const kontext = await browser.newContext({ viewport: { width: breite, height: hoehe } });
await kontext.addCookies(keksIch.split("; ").map((k) => {
const [name, ...rest] = k.split("=");
return { name, value: rest.join("="), domain: "127.0.0.1", path: "/" };
}));
const seite = await kontext.newPage();
const konsole = [];
seite.on("console", (e) => { if (e.type() === "error") konsole.push(e.text()); });
await seite.goto(`${BASIS}/workspace/chat.html`, { waitUntil: "networkidle" });
await seite.waitForSelector("#raeume .chat-raum", { timeout: 15_000 });
await seite.waitForTimeout(700);
const ziel = await seite.$(wie === "breit" ? ".chat" : ".chat__spalte--liste");
await ziel.screenshot({ path: `server/mess-chat-liste-${wie}.png` });
console.log(` -- Bild (${breite} px): server/mess-chat-liste-${wie}.png`);
if (wie === "breit") {
const z = await seite.evaluate(() =>
[...document.querySelectorAll("#raeume .chat-raum")].map((li) => {
const k = li.querySelector(".chat-raum__knopf");
return {
name: li.querySelector(".chat-raum__name")?.textContent || "",
art: li.dataset.art || "",
neu: li.dataset.neu || "-",
zahl: li.querySelector(".chat-raum__zahl")?.textContent || "",
at: li.querySelector(".chat-raum__erwaehnt") ? "@" : " ",
bilder: li.querySelectorAll(".chat-raum__zeichen img").length,
hoch: Math.round(k.getBoundingClientRect().height),
};
}));
console.log("");
for (const x of z) {
console.log(` ${x.name.padEnd(18)} ${x.art.padEnd(7)} `
+ `${x.at} ${String(x.zahl).padStart(3)} ${x.hoch}px Bilder: ${x.bilder}`);
}
const hoehen = [...new Set(z.map((x) => x.hoch))];
console.log("");
console.log(` -- Zeilenhoehen: ${hoehen.join(", ")} px `
+ `(${hoehen.length === 1 ? "alle gleich" : "UNTERSCHIEDLICH"})`);
const doppelt = z.filter((x) => x.bilder > 1);
console.log(` -- Zeilen mit doppeltem Gesicht: ${doppelt.length}`);
}
if (konsole.length) console.log(" !! Konsole: " + konsole[0]);
await kontext.close();
}
} finally {
await browser.close();
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
process.exit(0);
}
+35
View File
@@ -125,6 +125,11 @@ const lesen = (seite) => seite.evaluate(() => ({
name: k.querySelector(".kachel__name")?.textContent,
unter: k.querySelector(".kachel__unter")?.textContent,
ton: k.dataset.ton,
/* DIE SILBERNE KACHEL ÜBERSCHREIBT IHREN TON (siehe start.css,
23.09.2026) -- absichtlich, und nur in der Anzeige. Wer das
nicht mitliest, vergleicht ihre Silberfläche mit dem Grün ihres
Bereichs und meldet einen Fehler, den es nicht gibt. */
silber: k.dataset.silber === "ja",
hatZeichen: !!k.querySelector(".kachel__svg"),
farbe: k.querySelector(".kachel__svg")
? getComputedStyle(k.querySelector(".kachel__svg")).stroke : null,
@@ -547,12 +552,42 @@ for (const [breite, wie] of [[1440, "Computer"], [390, "Handy"]]) {
/* Und zwar wirklich DIESELBE Farbe, an echten Bildpunkten gemessen --
"hat einen Ton" reicht nicht, sonst faellt nicht auf, wenn alle
Zeilen denselben tragen. */
/* ==== EINE AUSNAHME, UND SIE IST BEGRÜNDET (24.09.2026) ==========
Diese Zeile war rot, seit „Aufgaben" am 23.09.2026 die silberne
Kachel bekommen hat -- und es war kein Fehler. Die Kachel
überschreibt ihr `--ton` absichtlich (start.css: „Filipe hat nach
einer silbernen KACHEL gefragt, nicht nach einer silbernen
Füllung"). Der Hinweis in „Was ansteht" trägt weiter das Grün
SEINES BEREICHS, und das ist richtig: Er gehört zum Bereich
Aufgaben, nicht zur Gestaltung dieser einen Kachel.
Damit ist es dasselbe Muster wie bei den acht Prüfungen vom
23./24.09.: Die Prüfung ist nicht falsch geworden, sie ist
UNZUSTÄNDIG geworden -- eine Zusage, die jemand bewusst
zurückgenommen hat, und sie fordert sie weiter ein. Eine rote
Prüfung, die niemand mehr liest, sichert nichts.
STATT SIE ZU LOCKERN, WIRD SIE GENAUER: Die Silberkachel fällt
aus dem Farbvergleich heraus -- und bekommt dafür ihre eigene
Prüfung darunter. */
const silberToene = new Set(r.kacheln.filter((k) => k.silber).map((k) => k.ton));
const kachelFarbe = new Map(r.kacheln.map((k) => [k.ton, k.farbe]));
const falsch = r.dranZeilen.filter((z) => z.stufe !== "warnung")
.filter((z) => z.ton !== null && z.zahlFarbe !== null)
.filter((z) => !silberToene.has(z.ton))
.filter((z) => z.zahlFarbe !== kachelFarbe.get(z.ton));
ok(falsch.length === 0,
`und die Farbe ist die der Kachel (${falsch.map((z) => `${z.ton}: ${z.zahlFarbe} statt ${kachelFarbe.get(z.ton)}`).join(" | ") || "alle"})`);
/* UND DIE AUSNAHME WIRD SELBST GEPRÜFT. Ohne diese zwei Zeilen wäre
„ausgenommen" eine Lücke: Jede Kachel könnte sich `data-silber`
anhängen und dem Farbvergleich entkommen. */
const silber = r.kacheln.filter((k) => k.silber);
ok(silber.length <= 1,
`höchstens EINE silberne Kachel (${silber.length}: ${silber.map((k) => k.name).join(", ") || "keine"})`);
ok(silber.every((k) => !!k.ton),
`und sie trägt weiterhin ihre Tonnummer (${silber.map((k) => k.ton).join(",") || "—"}) `
+ "– die Farbwerkzeuge rechnen mit Zahlen, nicht mit dem, was man sieht");
/* Ueberfaellig schlaegt Bereichsfarbe -- dort muss Rot stehen, sonst
geht die Dringlichkeit in siebzehn Farben unter. */
const warnZeilen = r.dranZeilen.filter((z) => z.stufe === "warnung");
+503
View File
@@ -0,0 +1,503 @@
/* =====================================================================
UNTERSTÜTZEN — sieht jeder, ändert nur einer (24.09.2026)
Filipe: „jeder soll diese kachel sehen aber nur dogfather soll sie
verändern können oder vieles mehr sehen. alle anderen, rechte und
linke hand, modis und community sollen supporten können."
---------------------------------------------------------------------
AUF DIESER SEITE STEHT EIN ZAHLUNGSLINK. Das macht sie zur einzigen
im Haus, bei der ein Rechtefehler unmittelbar Geld kostet: Wer die
Adresse ändern darf, kann sie auf ein fremdes Konto zeigen lassen,
und es fällt niemandem auf. Deshalb wird hier nicht geprüft, ob die
Knöpfe fehlen — sondern ob der Server ABLEHNT.
---------------------------------------------------------------------
GEMESSEN WIRD ÜBER DIE CREW-ADRESSE, NICHT ÜBER 127.0.0.1
Das ist keine Formsache. Welches „Haus" eine Anmeldung betrifft,
leitet der Server aus dem `Host`-Kopf ab (`istCrewAdresse`). Über
127.0.0.1 landet man still im Agenturhaus — und dort gibt es die
Rolle `gast` gar nicht. Beim ersten Lauf dieser Datei waren deshalb
vier Rollen grün und der Zuschauer rot, und es sah nach einem
Rechtefehler aus. Es war ein MESSFEHLER: Ich hatte die falsche
Adresse gemessen.
`fetch` kann das nicht: `Host` ist ein verbotener Kopf und wird
verworfen. Deshalb `node:http` für die Schnittstelle und ein
HTTPS-Vorbau plus `--host-resolver-rules` für den Browser —
dasselbe Vorgehen wie in pruef-alter und pruef-chat-neu.
---------------------------------------------------------------------
SECHS ABSCHNITTE
1. Jede Rolle kommt auf die Seite und sieht die sichtbaren Wege.
2. NUR DogFather darf ändern. Die rechte Hand ausdrücklich NICHT —
sie führt das Team mit, und diese eine Tür bleibt trotzdem zu.
3. Eine Adresse wird geprüft, bevor sie in der Datenbank steht:
`javascript:`, `data:` und `http://` kommen nicht durch.
4. Was unsichtbar ist, wird auch nicht ausgeliefert — nicht
versteckt im Browser, sondern gar nicht erst geschickt.
5. Die Zählung ist anonym, und zwar von der TABELLE her: Es gibt
keine Spalte für eine Person. Geprüft über PRAGMA table_info,
nicht über eine Abfrage — eine Abfrage ließe sich ändern.
6. Die Seite steht im Browser und die Kachel im Community-Bereich.
MIT GEGENPROBEN: Jede Ablehnung wird gegen einen Fall gehalten, der
durchgehen MUSS. Sonst hieße „abgelehnt" vielleicht nur, dass gar
nichts geht.
Aufrufen mit: node server/pruef-unterstuetzung.mjs
Bild: SCHIRM=1 node server/pruef-unterstuetzung.mjs
===================================================================== */
import { mkdtempSync, rmSync, readFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { execFileSync } from "node:child_process";
import { request as httpAnfrage } from "node:http";
import { createServer as httpsServer } from "node:https";
import { eigenerPort } from "./helfer-port.mjs";
const PORT = await eigenerPort(import.meta, "pruef-unterstuetzung");
const HP = PORT + 1;
const CREW = "crew.dogfather-universe.com";
const ordner = mkdtempSync(join(tmpdir(), "ws-unter-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
process.env.PORT = String(PORT);
process.env.SITE_ACCESS_SECRET = "lokaler-test";
process.env.SITE_ACCESS_CODE_DOGI = "DOGI-TEST-1";
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(240_000, "pruef-unterstuetzung");
await new Promise((r) => setTimeout(r, 800));
process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); });
process.on("unhandledRejection", (f) => { console.error("ABSTURZ (Versprechen):", f); process.exit(7); });
let fehler = 0, geprueft = 0;
const melde = (t) => console.log(t);
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
/* ---- Sprechen über die Crew-Adresse ---------------------------------- */
function anfrage(pfad, art, koerper, keks) {
return new Promise((fertig, schief) => {
const kopf = { Host: CREW };
if (keks) kopf.Cookie = keks;
if (koerper) kopf["Content-Type"] = "application/json";
const a = httpAnfrage(
{ host: "127.0.0.1", port: PORT, path: pfad, method: art, headers: kopf },
(antwort) => {
let text = "";
antwort.on("data", (s) => { text += s; });
antwort.on("end", () => fertig({
code: antwort.statusCode, text, kopf: antwort.headers,
}));
});
a.on("error", schief);
if (koerper) a.write(JSON.stringify(koerper));
a.end();
});
}
const json = (a) => { try { return JSON.parse(a.text); } catch { return null; } };
/* ---- Der Bestand ----------------------------------------------------- */
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
const d = new DatabaseSync(process.env.WORKSPACE_DB);
const jetzt = new Date().toISOString();
const schluessel = randomBytes(32).toString("hex");
d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)"
+ " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING")
.run("code_kennung_schluessel", schluessel, jetzt);
function anlegen(name, rolle, code) {
const salt = randomBytes(16).toString("hex");
const hash = scryptSync(code, salt, 64,
{ N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
d.prepare("INSERT INTO personen"
+ " (name, rolle, code_hash, code_salt, code_n, code_kennung, aktiv, erstellt)"
+ " VALUES (?,?,?,?,?,?,1,?)")
.run(name, rolle, hash, salt, 32768,
createHmac("sha256", schluessel).update(code).digest("hex"), jetzt);
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
}
/* Die fünf Rollen, die Filipe genannt hat — DogFather, rechte und
linke Hand, ein Modi und die Community. */
const ROLLEN = [
["Filipe", "admin", "CODE-DOGI-0001"],
["VanVan", "hand", "CODE-HAND-0001"],
["Kessi", "linke", "CODE-LINKE-0001"],
["Diene", "modi", "CODE-MODI-0001"],
["Zuschauer", "gast", "CODE-GAST-0001"],
];
for (const [name, rolle, code] of ROLLEN) anlegen(name, rolle, code);
/* Die Spalten — Abschnitt 5 prüft dagegen. Gelesen aus dem SCHEMA,
nicht aus einer Abfrage: Eine Abfrage ließe sich morgen erweitern,
eine fehlende Spalte nicht. */
const STRICH_SPALTEN = d.prepare("PRAGMA table_info(unterstuetzung_striche)")
.all().map((z) => z.name);
const WEG_SPALTEN = d.prepare("PRAGMA table_info(unterstuetzung_wege)")
.all().map((z) => z.name);
d.close();
async function anmelden(rolle, code) {
/* `alter_ok` NUR BEI DER COMMUNITY: Sie bestätigt beim ersten
Hereinkommen das Mindestalter (workspace.js). Wer zum Team gehört,
hat seinen Zugang persönlich bekommen. */
const a = await anfrage("/workspace/api/anmelden", "POST",
rolle === "gast" ? { rolle, code, alter_ok: true } : { rolle, code });
if (a.code !== 200) {
/* DER DRITTE AUSGANG: Ohne Anmeldung kann diese Prüfung gar nichts
beantworten — und das ist etwas anderes als „nicht in Ordnung". */
melde(` ABBRUCH: ${rolle} ließ sich nicht anmelden (HTTP ${a.code}, `
+ `${a.text.slice(0, 90)}). Ohne Anmeldung misst diese Prüfung nichts.`);
process.exit(2);
}
return (a.kopf["set-cookie"] || []).map((z) => z.split(";")[0]).join("; ");
}
const kekse = {};
for (const [, rolle, code] of ROLLEN) kekse[rolle] = await anmelden(rolle, code);
const lies = async (keks) => {
const a = await anfrage("/workspace/api/unterstuetzung", "GET", null, keks);
return { status: a.code, daten: a.code === 200 ? json(a) : null };
};
const schreib = (keks, id, koerper, art = "PUT") =>
anfrage(`/workspace/api/unterstuetzung${id ? "/" + id : ""}`, art, koerper, keks);
/* =====================================================================
1. JEDE ROLLE SIEHT DIE SEITE
===================================================================== */
melde("");
melde("=== 1. Sieht jeder die Wege? ===");
const gesehen = {};
for (const [, rolle] of ROLLEN) {
const r = await lies(kekse[rolle]);
gesehen[rolle] = r.daten;
ok(r.status === 200 && Array.isArray(r.daten?.wege),
`${rolle}: bekommt die Wege (HTTP ${r.status}, ${r.daten?.wege?.length ?? "?"} Stück)`);
}
ok(gesehen.gast?.wege.length > 0, `und es sind wirklich welche da (${gesehen.gast?.wege.length})`);
/* Die Wunschliste ist der Weg, den Filipe mitgeliefert hat — sie muss
bei JEDEM ankommen, nicht nur bei der Leitung. */
const wunsch = gesehen.gast?.wege.find((w) => w.schluessel === "amazon");
ok(!!wunsch && wunsch.ziel.startsWith("https://www.amazon.de/"),
`die Wunschliste steht auch beim Zuschauer (${wunsch?.ziel.slice(0, 42)}…)`);
/* GEGENPROBE ZUM GANZEN ABSCHNITT: Ohne Anmeldung gibt es nichts. */
{
const a = await anfrage("/workspace/api/unterstuetzung", "GET");
ok(a.code === 401, `ohne Anmeldung gar nichts (HTTP ${a.code})`);
}
/* =====================================================================
2. ÄNDERN DARF NUR EINER
===================================================================== */
melde("");
melde("=== 2. Wer darf ändern? ===");
ok(gesehen.admin?.darf_aendern === true, "DogFather: darf_aendern = true");
for (const rolle of ["hand", "linke", "modi", "gast"]) {
ok(gesehen[rolle]?.darf_aendern === false, `${rolle}: darf_aendern = false`);
}
const amazonId = gesehen.admin.wege.find((w) => w.schluessel === "amazon").id;
/* UND ZWAR NICHT NUR IM BROWSER. Jede der vier Rollen versucht es
wirklich — eine Oberfläche, die den Knopf versteckt, ist keine
Sperre, sondern eine Bitte. */
for (const rolle of ["hand", "linke", "modi", "gast"]) {
const a = await schreib(kekse[rolle], amazonId, { titel: "Übernommen" });
ok(a.code === 404, `${rolle}: PUT wird abgewiesen (HTTP ${a.code})`);
}
{
const a = await schreib(kekse.hand, amazonId, null, "DELETE");
ok(a.code === 404, `die rechte Hand kann auch nicht löschen (HTTP ${a.code})`);
const b = await schreib(kekse.hand, null, { titel: "Eigener Weg" }, "POST");
ok(b.code === 404, `und keinen neuen anlegen (HTTP ${b.code})`);
}
/* GEGENPROBE: DogFather kommt durch. Ohne sie hieße „404 für alle"
vielleicht nur, dass es die Route gar nicht gibt. */
{
const a = await schreib(kekse.admin, amazonId, { titel: "Wunschliste" });
ok(a.code === 200, `DogFather kommt durch (HTTP ${a.code})`);
const nach = await lies(kekse.gast);
ok(nach.daten.wege.find((w) => w.id === amazonId)?.titel === "Wunschliste",
"und die Änderung kommt bei allen an");
}
/* =====================================================================
3. DIE ADRESSE WIRD GEPRÜFT
===================================================================== */
melde("");
melde("=== 3. Was darf in eine Adresse? ===");
const BOESE = [
["javascript:alert(1)", "ein Skript"],
["data:text/html,<h1>x", "eine ganze Seite im Link"],
["http://example.com/pay", "unverschlüsselt"],
["//example.com/pay", "ohne Schema"],
];
for (const [ziel, warum] of BOESE) {
const a = await schreib(kekse.admin, amazonId, { ziel, knopf: "Los" });
ok(a.code === 400, `abgelehnt: ${warum} (${ziel.slice(0, 24)} → HTTP ${a.code})`);
}
/* GEGENPROBE: Die beiden erlaubten Formen gehen durch. */
const AMAZON = "https://www.amazon.de/hz/wishlist/ls/3BGGNNHYAO5QL";
{
const a = await schreib(kekse.admin, amazonId,
{ ziel: AMAZON, knopf: "Wunschliste ansehen" });
ok(a.code === 200, `https:// geht durch (HTTP ${a.code})`);
const b = await schreib(kekse.admin,
gesehen.admin.wege.find((w) => w.schluessel === "draussen").id,
{ ziel: "unsere-seiten.html", knopf: "Zu Draußen" });
ok(b.code === 200, `eine Seite des Hauses auch (HTTP ${b.code})`);
}
/* EIN KNOPF OHNE ZIEL IST EIN VERSPRECHEN INS LEERE. */
{
const a = await schreib(kekse.admin, amazonId, { ziel: "", knopf: "Drück mich" });
ok(a.code === 400, `Knopf ohne Adresse wird abgelehnt (HTTP ${a.code})`);
await schreib(kekse.admin, amazonId, { ziel: AMAZON, knopf: "Wunschliste ansehen" });
}
/* =====================================================================
4. UNSICHTBAR HEISST NICHT AUSGELIEFERT
===================================================================== */
melde("");
melde("=== 4. Was unsichtbar ist ===");
{
const beiAdmin = (await lies(kekse.admin)).daten.wege;
const beiGast = (await lies(kekse.gast)).daten.wege;
const paypal = beiAdmin.find((w) => w.schluessel === "paypal");
ok(!!paypal, "DogFather sieht den PayPal-Weg");
ok(paypal.sichtbar === false && paypal.ziel === "",
"er steht ohne Adresse da und ist ausgeblendet — eine Zahlungsadresse wird nicht geraten");
ok(!beiGast.some((w) => w.schluessel === "paypal"),
"und beim Zuschauer kommt er gar nicht erst an");
ok(beiAdmin.length > beiGast.length,
`DogFather sieht mehr (${beiAdmin.length} gegen ${beiGast.length})`);
ok(beiGast.every((w) => w.sichtbar), "beim Zuschauer ist alles Gelieferte auch sichtbar");
ok((await lies(kekse.admin)).daten.zahlen !== null, "DogFather bekommt die Zahlen");
for (const rolle of ["hand", "linke", "modi", "gast"]) {
ok((await lies(kekse[rolle])).daten.zahlen === null, `${rolle}: keine Zahlen`);
}
}
/* =====================================================================
5. DIE ZÄHLUNG IST ANONYM — VON DER TABELLE HER
===================================================================== */
melde("");
melde("=== 5. Zählt jemand mit? ===");
ok(STRICH_SPALTEN.length > 0,
`die Zähltabelle hat ${STRICH_SPALTEN.length} Spalten (${STRICH_SPALTEN.join(", ")})`);
const VERDAECHTIG = ["person_id", "person", "von_id", "ip", "sitzung", "name", "wer"];
ok(!STRICH_SPALTEN.some((s) => VERDAECHTIG.includes(s)),
"keine Spalte für eine Person, eine IP oder eine Sitzung");
/* GEGENPROBE: Derselbe Sucher findet so etwas auch wirklich — die
Wege-Tabelle hat `geaendert_von`, und das MUSS er sehen. Ohne diese
Zeile hieße „nichts gefunden" vielleicht nur, dass er nicht sucht. */
ok(WEG_SPALTEN.includes("geaendert_von"),
"Gegenprobe: in der Wege-Tabelle findet dieselbe Suche sehr wohl eine Person (geaendert_von)");
{
const zuvor = (await lies(kekse.admin)).daten.zahlen?.[amazonId]?.gesamt || 0;
const a = await anfrage(`/workspace/api/unterstuetzung/${amazonId}/strich`,
"POST", null, kekse.gast);
ok(a.code === 200, `ein Zuschauer darf zählen (HTTP ${a.code})`);
const danach = (await lies(kekse.admin)).daten.zahlen?.[amazonId]?.gesamt || 0;
ok(danach === zuvor + 1, `und der Strich kommt an (${zuvor} → ${danach})`);
/* EINMAL JE TAG. Ohne diese Sperre wäre die Zahl das Ergebnis von
Dauerdrücken und keine Auskunft. */
await anfrage(`/workspace/api/unterstuetzung/${amazonId}/strich`, "POST", null, kekse.gast);
const nochmal = (await lies(kekse.admin)).daten.zahlen?.[amazonId]?.gesamt || 0;
ok(nochmal === danach, `zweimal derselbe zählt nicht doppelt (${danach} → ${nochmal})`);
/* Aber ein ANDERER schon — sonst wäre die Sperre zu breit. */
await anfrage(`/workspace/api/unterstuetzung/${amazonId}/strich`, "POST", null, kekse.modi);
const dritter = (await lies(kekse.admin)).daten.zahlen?.[amazonId]?.gesamt || 0;
ok(dritter === nochmal + 1, `ein anderer Mensch zählt sehr wohl (${nochmal} → ${dritter})`);
}
/* EIN UNSICHTBARER WEG WIRD NICHT GEZÄHLT — sonst stünde eine Zahl an
einem Weg, den außer DogFather niemand sehen kann. */
{
const paypalId = (await lies(kekse.admin)).daten.wege
.find((w) => w.schluessel === "paypal").id;
const a = await anfrage(`/workspace/api/unterstuetzung/${paypalId}/strich`,
"POST", null, kekse.gast);
ok(a.code === 404, `ein ausgeblendeter Weg lässt sich nicht zählen (HTTP ${a.code})`);
}
/* =====================================================================
6. DIE SEITE UND DIE KACHEL
=====================================================================
Der Browser spricht über die Crew-Adresse, sonst misst er das
Agenturhaus (siehe den Kopf). Dafür ein HTTPS-Vorbau mit einem
selbst ausgestellten Zeugnis und `--host-resolver-rules` — dasselbe
Vorgehen wie in pruef-chat-neu. */
melde("");
melde("=== 6. Im Browser ===");
const schl = join(ordner, "b.key"), zert = join(ordner, "b.crt");
execFileSync("openssl", ["req", "-x509", "-newkey", "rsa:2048", "-nodes", "-keyout", schl,
"-out", zert, "-days", "2", "-subj", `/CN=${CREW}`, "-addext", `subjectAltName=DNS:${CREW}`],
{ stdio: "ignore" });
const vorbau = httpsServer({ key: readFileSync(schl), cert: readFileSync(zert) }, (von, nach) => {
const w = httpAnfrage({
host: "127.0.0.1", port: PORT, path: von.url, method: von.method,
headers: { ...von.headers, host: `${CREW}:${HP}` },
}, (a) => { nach.writeHead(a.statusCode || 502, a.headers); a.pipe(nach); });
von.pipe(w);
});
await new Promise((r) => vorbau.listen(HP, "127.0.0.1", r));
const SEITE = `https://${CREW}:${HP}`;
const pw = await import(
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
const browser = await pw.chromium.launch({
args: [`--host-resolver-rules=MAP ${CREW} 127.0.0.1`, "--ignore-certificate-errors"],
});
try {
for (const rolle of ["admin", "gast"]) {
const kontext = await browser.newContext({ viewport: { width: 1280, height: 1000 } });
await kontext.addCookies(kekse[rolle].split("; ").map((k) => {
const [name, ...rest] = k.split("=");
return { name, value: rest.join("="), domain: CREW, path: "/" };
}));
const seite = await kontext.newPage();
const konsole = [];
seite.on("console", (e) => {
if (e.type() === "error" && !/favicon|ERR_CERT|net::ERR/i.test(e.text())) {
konsole.push(e.text().slice(0, 120));
}
});
seite.on("pageerror", (e) => konsole.push("Absturz: " + String(e).slice(0, 120)));
/* DIE KACHEL — ohne sie findet niemand die Seite. */
await seite.goto(`${SEITE}/workspace/start.html`, { waitUntil: "networkidle" });
await seite.waitForTimeout(700);
const kachel = await seite.evaluate(() => {
const a = [...document.querySelectorAll('a[href^="unterstuetzen.html"]')][0];
if (!a) return null;
return {
name: a.querySelector(".kachel__name")?.textContent
|| a.textContent.trim().slice(0, 40),
zeichen: !!a.querySelector("svg"),
};
});
ok(!!kachel, `${rolle}: die Kachel steht auf der Startseite (${kachel?.name || "—"})`);
ok(!!kachel?.zeichen, `${rolle}: und sie trägt ihr Zeichen`);
await seite.goto(`${SEITE}/workspace/unterstuetzen.html`, { waitUntil: "networkidle" });
await seite.waitForSelector(".u-weg", { timeout: 15_000 });
await seite.waitForTimeout(500);
const b = await seite.evaluate(() => ({
karten: document.querySelectorAll(".u-weg").length,
gruppen: [...document.querySelectorAll(".u-gruppe__titel")].map((x) => x.textContent),
leitung: !document.getElementById("leitung")?.hidden,
knoepfe: [...document.querySelectorAll(".u-weg__knopf")].map((a) => ({
ziel: a.getAttribute("href"), rel: a.getAttribute("rel") || "",
neu: a.getAttribute("target") || "",
hoch: Math.round(a.getBoundingClientRect().height),
})),
}));
ok(b.karten >= 4, `${rolle}: ${b.karten} Wege stehen da`);
ok(b.gruppen.length === 2, `${rolle}: zwei Gruppen (${b.gruppen.join(" / ")})`);
ok(b.leitung === (rolle === "admin"),
`${rolle}: der Leitungskasten steht ${rolle === "admin" ? "da" : "NICHT da"}`);
/* JEDER WEG NACH DRAUSSEN BRAUCHT `noopener` — ohne ihn kann die
geöffnete Seite über `window.opener` auf diese zugreifen. */
const raus = b.knoepfe.filter((k) => /^https:/.test(k.ziel));
ok(raus.length > 0, `${rolle}: ${raus.length} Weg(e) führen nach draußen`);
ok(raus.every((k) => k.rel.includes("noopener") && k.rel.includes("noreferrer")),
`${rolle}: und alle tragen rel="noopener noreferrer"`);
ok(raus.every((k) => k.neu === "_blank"), `${rolle}: und öffnen ein neues Fenster`);
/* AM FINGER TREFFBAR. Dieser Knopf führt zu einer Zahlung; ihn zu
verfehlen ist ärgerlicher als anderswo. */
ok(b.knoepfe.length > 0 && b.knoepfe.every((k) => k.hoch >= 44),
`${rolle}: jeder Knopf ist mindestens 44 px hoch `
+ `(${b.knoepfe.map((k) => k.hoch).join(", ")})`);
/* ==== DER WEG, DEN FILIPE WIRKLICH GEHEN WIRD ================
Sein PayPal-Link steht nirgends im Haus ("mein paypal kennst du
ja schon" -- gesucht, nicht gefunden), und geraten wird er
nicht. Er traegt ihn also selbst ein, hier, auf dieser Seite.
GENAU DIESER ABLAUF WIRD GEMESSEN und nicht nur die Route
darunter: Knopf druecken, Felder ausfuellen, speichern, und
danach muss der Weg bei einem ZUSCHAUER ankommen. Eine Route,
die 200 sagt, waehrend das Formular das falsche Feld schickt,
waere gruen und trotzdem kaputt. */
if (rolle === "admin") {
await seite.click("#aendern-an");
await seite.waitForTimeout(400);
const felder = await seite.evaluate(() =>
document.querySelectorAll('.u-weg .u-form [data-feld="ziel"]').length);
ok(felder >= 4, `Änderungsmodus: ${felder} Karten zeigen ihre Felder`);
const getippt = await seite.evaluate(() => {
const karte = [...document.querySelectorAll(".u-weg")]
.find((k) => k.dataset.schluessel === "paypal");
if (!karte) return false;
const setz = (feld, wert) => {
const e = karte.querySelector(`[data-feld="${feld}"]`);
if (!e) return;
if (e.type === "checkbox") e.checked = wert; else e.value = wert;
};
setz("ziel", "https://www.paypal.com/paypalme/beispiel");
setz("knopf", "Bei PayPal öffnen");
setz("sichtbar", true);
[...karte.querySelectorAll("button")]
.find((b) => b.textContent === "Speichern")?.click();
return true;
});
ok(getippt, "die PayPal-Karte lässt sich ausfüllen");
await seite.waitForTimeout(900);
const jetztDa = await seite.evaluate(() => {
const k = [...document.querySelectorAll(".u-weg")]
.find((x) => x.dataset.schluessel === "paypal");
return { aus: k?.dataset.aus || "", offen: !!k };
});
ok(jetztDa.offen && jetztDa.aus === "", "und steht danach nicht mehr ausgeblendet da");
/* UND JETZT DER PUNKT: Kommt sie beim Zuschauer an? */
const beimGast = (await lies(kekse.gast)).daten.wege
.find((w) => w.schluessel === "paypal");
ok(!!beimGast && beimGast.ziel === "https://www.paypal.com/paypalme/beispiel"
&& beimGast.knopf === "Bei PayPal öffnen",
`der Zuschauer sieht den Weg jetzt (${beimGast?.knopf || "—"})`);
/* ZURUECK IN DEN ANZEIGEMODUS -- das Bildschirmfoto soll zeigen,
was Filipe im Alltag sieht, nicht das Formular. */
await seite.click("#aendern-an");
await seite.waitForTimeout(500);
}
if (rolle === "admin" && process.env.SCHIRM) {
await seite.screenshot({ path: "server/pruef-unterstuetzung.png" });
melde(" -- Bildschirmfoto: server/pruef-unterstuetzung.png");
}
ok(konsole.length === 0, konsole.length
? `${rolle}: Fehler in der Konsole: ${konsole[0]}`
: `${rolle}: keine Fehler in der Konsole`);
await kontext.close();
}
} finally {
await browser.close();
try { vorbau.close(); } catch { /* egal */ }
}
melde("");
melde(`${geprueft} Pruefungen, ${fehler} Fehler`);
melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
process.exit(fehler ? 1 : 0);
+17
View File
@@ -472,6 +472,23 @@ export const SEITEN = {
"/workspace/support.html": ["gast", "modi", "creator", "scout", "manager",
"linke", "hand", "spicy", "admin"],
/* UNTERSTÜTZEN (24.09.2026).
Filipe: „jeder soll diese kachel sehen aber nur dogfather soll sie
verändern können oder vieles mehr sehen. alle anderen, rechte und
linke hand, modis und community sollen supporten können."
Die Seite steht damit JEDER Rolle offen — was jemand darauf tun
darf, entscheidet workspace-unterstuetzung.js und nicht diese
Tafel. Sie beantwortet nur, wer die Tür aufbekommt.
ALLE ROLLEN AUSGESCHRIEBEN, dieselbe Überlegung wie beim Support
eine Zeile darüber: Diese Tafel ist die Stelle, an der man
nachsieht, wer wohin darf. Eine Abkürzung hier hieße, dass die
Seite in „Wer sieht was" als Lücke erscheint. */
"/workspace/unterstuetzen.html": ["gast", "modi", "creator", "scout", "manager",
"linke", "hand", "spicy", "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").
+179
View File
@@ -0,0 +1,179 @@
/* =====================================================================
UNTERSTÜTZEN — Tabellen (24.09.2026)
Filipe: „ich brauch auch noch eine kachel im community bereich. wo
mein paypal und meine amazon liste sein wird. wo die leute alle
supporten können auf andere art anstatt nur tiktok. … jeder soll
diese kachel sehen aber nur dogfather soll sie verändern können
oder vieles mehr sehen. alle anderen, rechte und linke hand, modis
und community sollen supporten können."
---------------------------------------------------------------------
WARUM DIE WEGE IN DER DATENBANK STEHEN UND NICHT IM QUELLTEXT
Es wäre kürzer gewesen, PayPal und die Wunschliste als zwei feste
Einträge in eine Datei zu schreiben. Drei Gründe dagegen, und der
dritte ist der entscheidende:
1. EIN LINK ÄNDERT SICH. Eine Wunschliste wird neu angelegt, ein
Zahlungsweg kommt dazu. Stünde er im Quelltext, hinge jede
Änderung an einem Deploy — und damit an mir.
2. FILIPE SOLL IHN SELBST ÄNDERN KÖNNEN. Genau das hat er
verlangt („nur dogfather soll sie verändern können"). Ein Recht,
das man nur über einen Entwickler ausüben kann, ist keines.
3. UND DER PAYPAL-LINK STEHT NIRGENDS IM HAUS. Filipe schrieb
„mein paypal kennst du ja schon" — gesucht habe ich, gefunden
nicht. Ihn zu RATEN wäre der schlimmste Fehler dieser ganzen
Seite: Ein falscher Zahlungslink schickt Geld an einen
Fremden, und niemand merkt es. Deshalb steht dort am Anfang
nichts, der Weg ist unsichtbar, und die Seite sagt DogFather
(und nur ihm), dass dort noch etwas fehlt.
---------------------------------------------------------------------
DREI ARTEN, UND DIE DRITTE IST DIE WICHTIGSTE
geld Direkt Geld — PayPal.
geschenk Etwas schenken — die Wunschliste.
frei Kostet nichts.
„frei" ist nicht Beiwerk. Eine Seite, die nur zwei Wege zum Zahlen
zeigt, liest sich wie eine hohle Hand — und die meisten, die hier
lesen, sind Zuschauer ohne Geld übrig. Wer gar nichts geben kann,
soll trotzdem etwas finden, das er tun kann, statt die Seite mit
einem schlechten Gefühl zu schließen. Deshalb stehen die freien
Wege gleichberechtigt daneben und nicht als Trostpflaster darunter.
---------------------------------------------------------------------
DIE ZÄHLUNG IST ANONYM — UND ZWAR VON DER TABELLE HER
„nur dogfather soll … vieles mehr sehen": Er sieht, wie oft ein Weg
geöffnet wurde. Was er NICHT sieht, ist WER — und das liegt nicht an
einer Abfrage, die man später ändern könnte, sondern daran, dass es
in dieser Tabelle keine Spalte dafür gibt.
Das ist Absicht und keine Nachlässigkeit. „Wer hat auf PayPal
gedrückt und dann doch nichts geschickt" ist eine Frage, die
niemandem nützt und mehrere Leute beschämt. Sie lässt sich hier
nicht beantworten, auch nicht von mir, auch nicht mit Datenbank-
zugriff.
Gezählt wird je Weg und je TAG. Der Tag reicht für „läuft das
überhaupt an?" und ergibt bei einer Community dieser Größe keine
Spur zu einzelnen Menschen.
===================================================================== */
/** Wie lange die Tagesstriche stehen bleiben.
*
* Ein Jahr: Lang genug, um Weihnachten mit Weihnachten zu
* vergleichen, kurz genug, dass hier nichts für immer liegt. Aufgeräumt
* wird im Rundgang, nicht beim Lesen — sonst hinge das Löschen davon
* ab, dass jemand die Seite öffnet. */
export const ZAEHLUNG_TAGE = 365;
/** Die drei Arten. Steht hier und nicht in der Oberfläche: Der Server
* prüft dagegen, und eine zweite Liste im Browser wäre die, die beim
* nächsten Zuwachs vergessen wird. */
export const UNTERSTUETZUNG_ARTEN = {
geld: "Direkt",
geschenk: "Schenken",
frei: "Kostet nichts",
};
/* Die Startbelegung. Sie wird EINMAL eingesetzt (INSERT OR IGNORE über
den Schlüssel) und danach nie wieder angefasst — wer einen Weg
löscht, soll ihn nicht beim nächsten Serverstart wiederfinden.
DER PAYPAL-WEG KOMMT OHNE ADRESSE UND UNSICHTBAR. Siehe oben: Raten
ist hier die gefährlichste aller Abkürzungen. */
const START_WEGE = [
{
schluessel: "paypal", art: "geld", platz: 10, sichtbar: 0,
titel: "PayPal",
text: "Direkt und ohne Umweg. Auch ein kleiner Betrag hilft – "
+ "und niemand muss etwas geben, um dazuzugehören.",
knopf: "Bei PayPal öffnen",
ziel: "",
},
{
schluessel: "amazon", art: "geschenk", platz: 20, sichtbar: 1,
titel: "Wunschliste",
text: "Etwas Konkretes schenken statt Geld – die Liste sagt, "
+ "was gerade gebraucht wird.",
knopf: "Wunschliste ansehen",
ziel: "https://www.amazon.de/hz/wishlist/ls/3BGGNNHYAO5QL?ref_=wl_share",
},
{
schluessel: "dabei", art: "frei", platz: 30, sichtbar: 1,
titel: "Einfach da sein",
text: "Im Live dabei sein, mitreden, jemanden begrüßen, der neu ist. "
+ "Das kostet nichts und ist am meisten wert.",
knopf: "", ziel: "",
},
{
schluessel: "weitersagen", art: "frei", platz: 40, sichtbar: 1,
titel: "Weitersagen",
text: "Einen Clip teilen oder jemandem von hier erzählen. "
+ "Neue Leute kommen über euch, nicht über Werbung.",
knopf: "", ziel: "",
},
{
schluessel: "draussen", art: "frei", platz: 50, sichtbar: 1,
titel: "Wo es Dogi sonst gibt",
text: "Kanäle, Shop und dein Rabatt stehen auf einer eigenen Seite.",
knopf: "Zu „Draußen“",
ziel: "unsere-seiten.html",
},
];
export function unterstuetzungTabellen(d) {
d.exec(`
CREATE TABLE IF NOT EXISTS unterstuetzung_wege (
id INTEGER PRIMARY KEY AUTOINCREMENT,
/* Der Schlüssel ist die Kennung für die Startbelegung und für
die Zählung. Er ändert sich nie — der TITEL darf sich ändern,
ohne dass die Striche der letzten Monate zu einem anderen Weg
gehören. */
schluessel TEXT NOT NULL UNIQUE,
art TEXT NOT NULL CHECK (art IN ('geld','geschenk','frei')),
titel TEXT NOT NULL,
text TEXT NOT NULL DEFAULT '',
/* Die Aufschrift des Knopfes. Leer heißt: Dieser Weg hat keinen
Knopf — es gibt Wege, die man nicht anklickt („einfach da
sein"). Ein Knopf ohne Ziel wäre ein Versprechen ins Leere. */
knopf TEXT NOT NULL DEFAULT '',
/* Die Adresse. Darf leer sein. Geprüft wird sie beim SCHREIBEN
(nur https:// oder eine Seite dieses Hauses) — nicht erst beim
Anzeigen: Eine Adresse, die erst im Browser auffällt, steht
vorher schon in der Datenbank. */
ziel TEXT NOT NULL DEFAULT '',
sichtbar INTEGER NOT NULL DEFAULT 1,
platz INTEGER NOT NULL DEFAULT 0,
geaendert TEXT,
geaendert_von INTEGER REFERENCES personen(id) ON DELETE SET NULL
);
/* KEINE SPALTE FÜR EINE PERSON. Siehe den Kopf dieser Datei — das
ist die eigentliche Zusage dieser Seite, und sie steht hier im
Schema und nicht in einer Abfrage, die jemand später ändert. */
CREATE TABLE IF NOT EXISTS unterstuetzung_striche (
weg_id INTEGER NOT NULL REFERENCES unterstuetzung_wege(id) ON DELETE CASCADE,
tag TEXT NOT NULL,
anzahl INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (weg_id, tag)
);
CREATE INDEX IF NOT EXISTS idx_unterstuetzung_tag
ON unterstuetzung_striche(tag);
`);
/* INSERT OR IGNORE über den eindeutigen Schlüssel: Beim ersten Start
entstehen die fünf Wege, bei jedem weiteren passiert nichts. Wer
einen löscht, findet ihn nicht beim nächsten Neustart wieder — das
wäre eine Änderung, die sich von selbst zurücknimmt, und so etwas
sucht man stundenlang. */
const setz = d.prepare(`INSERT OR IGNORE INTO unterstuetzung_wege
(schluessel, art, titel, text, knopf, ziel, sichtbar, platz)
VALUES (?,?,?,?,?,?,?,?)`);
for (const w of START_WEGE) {
setz.run(w.schluessel, w.art, w.titel, w.text, w.knopf, w.ziel, w.sichtbar, w.platz);
}
}
+81
View File
@@ -43,6 +43,10 @@ import express from "express";
zwei Wahrheiten, von denen eine veraltet. `hilfe-tabellen.js`
importiert absichtlich nichts, es kann also kein Kreis entstehen. */
import { AUFBEWAHRUNG_TAGE as HILFE_TAGE, FALL_STAND } from "./hilfe-tabellen.js";
import { AUFBEWAHRUNG_TAGE as SUPPORT_TAGE } from "./support-tabellen.js";
import { supportAufraeumen } from "./workspace-support.js";
import { ZAEHLUNG_TAGE } from "./unterstuetzung-tabellen.js";
import { unterstuetzungAufraeumen } from "./workspace-unterstuetzung.js";
import {
db, sitzungLesen, istDogFather, protokolliere, echteIp,
einstellung, einstellungSetzen,
@@ -262,6 +266,83 @@ export const FRISTEN = [
offen: (d) => zaehle(d,
"SELECT COUNT(*) AS n FROM aufgaben WHERE erledigt_am IS NOT NULL"),
},
/* ==== ZWEI EINTRÄGE, DIE HIER GEFEHLT HABEN (24.09.2026) ========
Beim Einbau des Unterstützen-Bereichs ist aufgefallen, dass
`supportAufraeumen()` in workspace-support.js zwar EXPORTIERT,
aber nirgends AUFGERUFEN wird — gesucht im ganzen Verzeichnis,
null Treffer. Die Meldungen samt Bildschirmfotos wären also für
immer liegen geblieben, obwohl im Kopf der Datei "90 Tage" steht.
Das ist dieselbe Sorte Fehler wie eine Prüfung, die nie läuft:
Es sieht fertig aus, es ist dokumentiert, und es passiert nichts.
Diese Liste ist die einzige Stelle im Haus, die wirklich räumt —
wer eine Frist verspricht und hier keinen Eintrag hat, verspricht
nichts. */
{
schluessel: "support_erledigt",
was: "Erledigte Support-Meldungen samt Bildschirmfoto",
art: "raeumen",
frist: `${SUPPORT_TAGE} Tage nach dem Erledigen`,
zweck: "Ein Fehler kommt oft wieder – dann hilft es, nachlesen zu können, "
+ "was beim letzten Mal daran war.",
grundlage: "Art. 5 Abs. 1 lit. e DSGVO – Speicherbegrenzung",
wirkung: `Die Meldung verschwindet ganz, mitsamt dem Bild auf der Platte. `
+ `Offene Meldungen werden nie geräumt – nur erledigte, und erst `
+ `${SUPPORT_TAGE} Tage danach.`,
offen: (d) => zaehle(d,
"SELECT COUNT(*) AS n FROM support_meldungen WHERE stand = 'erledigt'"
+ " AND erledigt_am IS NOT NULL AND erledigt_am < ?", vorTagen(SUPPORT_TAGE)),
/* ÜBER supportAufraeumen() UND NICHT ÜBER EIN DELETE HIER: Zu
jeder Meldung gehört eine Bilddatei auf der Platte. Ein DELETE
an dieser Stelle würde die Zeile löschen und das Bild liegen
lassen — und ein Bild ohne Zeile findet danach niemand mehr. */
raeumen: () => supportAufraeumen(),
},
{
schluessel: "unterstuetzung_striche",
was: "Tagesstriche der Unterstützen-Seite",
art: "raeumen",
frist: `${ZAEHLUNG_TAGE} Tage`,
zweck: "Sehen, ob ein Weg überhaupt benutzt wird – und ob ein neuer anläuft.",
grundlage: "Art. 6 Abs. 1 lit. f DSGVO – berechtigtes Interesse",
wirkung: "Die Zeile verschwindet. Es steht ohnehin keine Person darin: "
+ "Die Tabelle hat gar keine Spalte dafür (siehe unterstuetzung-tabellen.js) – "
+ "gezählt wird je Weg und Tag, nicht je Mensch.",
offen: (d) => zaehle(d,
"SELECT COUNT(*) AS n FROM unterstuetzung_striche WHERE tag < ?",
vorTagen(ZAEHLUNG_TAGE).slice(0, 10)),
raeumen: () => unterstuetzungAufraeumen(),
},
{
/* AUFGABEN-ZUTEILUNG (nachgetragen 24.09.2026).
Die Tabelle gibt es seit dem 21.09., im Löschkonzept stand sie
nicht -- gefunden von pruef-aufbewahrung, die jede Tabelle mit
einer "erledigt_am"-Spalte hier einfordert. Die Prüfung meldete
sie zusammen mit support_meldungen, und beide waren echte
Lücken: Für jede Tabelle muss JEMAND entschieden haben, ob sie
verschwindet oder bleibt. "Noch nicht bedacht" ist keine der
beiden Antworten.
DIE ANTWORT HIER IST "BLEIBT", und sie ist begründet: Wer welche
Aufgabe übernommen, abgelehnt oder erledigt hat, ist der
Werdegang eines Menschen im Team -- dieselbe Überlegung wie bei
den Aufgaben selbst eine Zeile darüber. Sie verschwindet mit
ihrer Aufgabe (ON DELETE CASCADE) und mit der Person
(ON DELETE CASCADE), aber nicht nach einer Frist. */
schluessel: "aufgaben_zuteilung",
was: "Wer eine Aufgabe übernommen hat",
art: "bleibt",
frist: "gehen mit der Aufgabe und mit der Person",
zweck: "Nachvollziehen, wer was übernommen hat – und was daraus wurde.",
grundlage: "Art. 6 Abs. 1 lit. f DSGVO – berechtigtes Interesse",
wirkung: "Bleiben. Wird die Aufgabe gelöscht, gehen sie mit (ON DELETE CASCADE); "
+ "wird der Zugang gelöscht, ebenso. Eine eigene Frist gibt es nicht – "
+ "eine Zuteilung ohne ihre Aufgabe wäre ohnehin nicht mehr lesbar.",
offen: (d) => zaehle(d,
"SELECT COUNT(*) AS n FROM aufgaben_zuteilung WHERE erledigt_am IS NOT NULL"),
},
{
schluessel: "personen_geloescht",
was: "Daten einer gelöschten Person",
+366
View File
@@ -0,0 +1,366 @@
/* =====================================================================
UNTERSTÜTZEN — die Wege (24.09.2026)
Filipe: „jeder soll diese kachel sehen aber nur dogfather soll sie
verändern können oder vieles mehr sehen. alle anderen, rechte und
linke hand, modis und community sollen supporten können."
Warum die Wege in der Datenbank stehen und warum die Zählung keine
Personenspalte hat, steht im Kopf von unterstuetzung-tabellen.js.
Hier sind die Wege — und die vier Stellen, an denen etwas
schiefgehen kann:
1. EINE ADRESSE IST EIN VERSPRECHEN. Auf dieser Seite steht ein
Zahlungslink. Wer ihn ändern darf, kann Geld umleiten — deshalb
hängt das Schreiben an `istDogFather` und nicht an `istLeitung`.
Die rechte Hand führt dieses Team mit, und sie soll trotzdem
nicht an diesen Link können. Das ist kein Misstrauen, sondern
dieselbe Regel wie bei der Rechtetafel: Genau eine Tür bleibt
bei ihm.
2. UND SIE WIRD GEPRÜFT, BEVOR SIE IN DER DATENBANK STEHT. Erlaubt
sind `https://` und eine Seite dieses Hauses. `javascript:` wäre
ein Skript, das jeder Besucher ausführt, `http://` ein
Zahlungsweg ohne Verschlüsselung, `data:` eine ganze Seite im
Link. Geprüft wird beim SCHREIBEN: Was erst beim Anzeigen
auffällt, steht vorher schon in der Datenbank.
3. DER LESEWEG IST FÜR ALLE UND ZEIGT NICHT ALLES. Unsichtbare Wege
und die Zählung gehen nur an DogFather. Beides entscheidet EINE
Stelle (`darfAlles`), damit nicht zwei Antworten auf dieselbe
Frage entstehen.
4. EIN STRICH IST KEIN PROTOKOLL. Gezählt wird je Weg und Tag, ohne
Person. Dass derselbe Mensch nicht zehnmal zählt, merkt sich der
Server NUR im Arbeitsspeicher — nach einem Neustart ist es
vergessen, und auf der Platte steht es nie.
===================================================================== */
import express from "express";
import {
db, protokolliere, echteIp, istDogFather, heuteLokal,
} from "./workspace.js";
import { UNTERSTUETZUNG_ARTEN, ZAEHLUNG_TAGE } from "./unterstuetzung-tabellen.js";
export const unterstuetzungRouter = express.Router();
const TITEL_MAX = 60;
const TEXT_MAX = 400;
const KNOPF_MAX = 40;
const ZIEL_MAX = 500;
const WEGE_MAX = 12;
/** Wer ändern darf UND wer mehr sieht — dieselbe Frage, eine Antwort.
*
* Zwei getrennte Prüfungen wären zwei Gelegenheiten, eine davon zu
* lockern. Und die beiden gehören ohnehin zusammen: Wer einen Weg
* bearbeitet, muss ihn auch sehen, solange er noch unsichtbar ist. */
const darfAlles = (person) => istDogFather(person);
/* Gleiche Herkunft — dasselbe Muster wie überall im Haus. Ohne
Origin-Kopf (Server zu Server, Prüfungen) geht es durch; mit einem
fremden nicht. */
function gleicheHerkunft(req, res, next) {
const herkunft = req.get("origin");
if (!herkunft) return next();
try {
if (new URL(herkunft).host === req.get("host")) return next();
} catch { /* unlesbar -> abweisen */ }
return res.status(403).json({ fehler: "fremde_herkunft" });
}
/** Nur DogFather. 404 und nicht 403: Wer nicht ändern darf, muss nicht
* erfahren, dass es die Möglichkeit gibt — dasselbe Vorgehen wie bei
* der Aufbewahrung und den Kanälen. */
function nurDogFather(req, res, next) {
if (!darfAlles(req.person)) return res.status(404).json({ fehler: "nicht_gefunden" });
next();
}
/* =====================================================================
DIE ADRESSE
=====================================================================
Drei Formen sind erlaubt, alles andere nicht:
"" kein Ziel (ein Weg ohne Knopf)
https://… nach draußen, verschlüsselt
seite.html[?frage] eine Seite dieses Hauses
AUSDRÜCKLICH NICHT: `javascript:` (ein Skript, das jeder Besucher
ausführt), `data:` (eine ganze Seite im Link), `http://` (ein
Zahlungsweg im Klartext) und alles mit `//` am Anfang (das übernimmt
das Schema der Seite und sieht aus wie ein eigener Pfad).
Geprüft wird mit `new URL()` und nicht mit einem Muster: Ein
selbstgeschriebener Ausdruck übersieht `HtTpS:` oder ein
eingeschobenes Leerzeichen, der Parser des Browsers nicht. */
const HAUS_SEITE = /^[a-z0-9][a-z0-9-]{0,40}\.html(\?[A-Za-z0-9=&_%.-]{0,80})?$/;
function zielPruefen(roh) {
const z = String(roh ?? "").trim();
if (!z) return { ok: true, wert: "" };
if (z.length > ZIEL_MAX) return { ok: false, grund: "Die Adresse ist zu lang." };
if (HAUS_SEITE.test(z)) return { ok: true, wert: z };
let u;
try { u = new URL(z); } catch {
return { ok: false, grund: "Das ist keine gültige Adresse. Sie muss mit https:// anfangen." };
}
if (u.protocol !== "https:") {
return { ok: false, grund: "Nur https:// – ein Zahlungsweg ohne Verschlüsselung kommt hier nicht hin." };
}
return { ok: true, wert: u.href };
}
const text = (roh, max) => String(roh ?? "").replace(/\s+/g, " ").trim().slice(0, max);
/* =====================================================================
LESEN — für alle
===================================================================== */
unterstuetzungRouter.get("/workspace/api/unterstuetzung", (req, res) => {
try {
const alles = darfAlles(req.person);
const d = db();
const wege = d.prepare(
`SELECT id, schluessel, art, titel, text, knopf, ziel, sichtbar, platz
FROM unterstuetzung_wege
${alles ? "" : "WHERE sichtbar = 1"}
ORDER BY platz, id`).all();
/* WAS DOGFATHER MEHR SIEHT (und sonst niemand):
die unsichtbaren Wege — und wie oft geöffnet wurde.
DREI ZEITRÄUME, weil eine einzelne Zahl nichts sagt: „48" kann
„läuft gut" oder „lief mal" heißen. Mit 7 und 30 Tagen daneben
sieht man den Unterschied. */
let zahlen = null;
if (alles) {
const tagVor = (n) => {
const t = new Date(); t.setHours(12, 0, 0, 0); t.setDate(t.getDate() - n);
return `${t.getFullYear()}-${String(t.getMonth() + 1).padStart(2, "0")}`
+ `-${String(t.getDate()).padStart(2, "0")}`;
};
const roh = d.prepare(
`SELECT weg_id,
SUM(anzahl) AS gesamt,
SUM(CASE WHEN tag >= ? THEN anzahl ELSE 0 END) AS tage30,
SUM(CASE WHEN tag >= ? THEN anzahl ELSE 0 END) AS tage7
FROM unterstuetzung_striche
GROUP BY weg_id`).all(tagVor(29), tagVor(6));
zahlen = Object.fromEntries(roh.map((z) => [z.weg_id, {
gesamt: z.gesamt || 0, tage30: z.tage30 || 0, tage7: z.tage7 || 0,
}]));
}
res.json({
wege: wege.map((w) => ({ ...w, sichtbar: !!w.sichtbar })),
arten: UNTERSTUETZUNG_ARTEN,
darf_aendern: alles,
zahlen,
});
} catch (fehler) {
console.error("[unterstuetzung] Lesen:", fehler?.message);
res.status(503).json({ fehler: "nicht_verfuegbar" });
}
});
/* =====================================================================
EINEN STRICH MACHEN — für alle
=====================================================================
Wer einen Weg öffnet, macht einen Strich. Kein Name, keine Nummer,
keine Kennung: nur Weg und Tag.
DIE SPERRE GEGEN MEHRFACHZÄHLUNG LIEGT IM ARBEITSSPEICHER. Sie
müsste sonst eine Kennung je Mensch auf die Platte schreiben — und
damit wäre die Zusage dieser Seite gebrochen, für eine Zahl, die nur
ungefähr stimmen muss. Nach einem Neustart zählt jeder wieder einmal;
das ist der Preis, und er ist richtig herum bezahlt. */
const heuteSchon = new Map(); // "sitzung|weg" -> Tag
unterstuetzungRouter.post("/workspace/api/unterstuetzung/:id(\\d+)/strich",
gleicheHerkunft, (req, res) => {
try {
const id = Number(req.params.id);
const weg = db().prepare(
"SELECT id, sichtbar FROM unterstuetzung_wege WHERE id = ?").get(id);
/* Ein unsichtbarer Weg wird nicht gezählt. Sonst stünde eine Zahl
an einem Weg, den außer DogFather niemand sehen kann — und die
käme dann von ihm selbst. */
if (!weg || (!weg.sichtbar && !darfAlles(req.person))) {
return res.status(404).json({ fehler: "nicht_gefunden" });
}
const tag = heuteLokal();
const schluessel = `${req.person?.id ?? "?"}|${id}`;
if (heuteSchon.get(schluessel) === tag) return res.json({ gezaehlt: false });
heuteSchon.set(schluessel, tag);
/* Der Speicher wächst sonst mit jedem Menschen und jedem Weg. Bei
mehr als tausend Einträgen fängt er von vorn an — eine Zahl,
die nur ungefähr stimmen muss, verträgt das. */
if (heuteSchon.size > 1000) heuteSchon.clear();
db().prepare(
`INSERT INTO unterstuetzung_striche (weg_id, tag, anzahl) VALUES (?,?,1)
ON CONFLICT(weg_id, tag) DO UPDATE SET anzahl = anzahl + 1`).run(id, tag);
res.json({ gezaehlt: true });
} catch (fehler) {
console.error("[unterstuetzung] Strich:", fehler?.message);
/* Ein Zähler darf den Weg nach draußen nie blockieren: Der Browser
öffnet den Link ohnehin, egal was hier steht. */
res.status(503).json({ fehler: "nicht_verfuegbar" });
}
});
/* =====================================================================
ÄNDERN — nur DogFather
===================================================================== */
unterstuetzungRouter.put("/workspace/api/unterstuetzung/:id(\\d+)",
gleicheHerkunft, nurDogFather, express.json({ limit: "8kb" }), (req, res) => {
try {
const id = Number(req.params.id);
const alt = db().prepare("SELECT * FROM unterstuetzung_wege WHERE id = ?").get(id);
if (!alt) return res.status(404).json({ fehler: "nicht_gefunden" });
const titel = text(req.body?.titel ?? alt.titel, TITEL_MAX);
if (titel.length < 2) return res.status(400).json({ fehler: "Der Weg braucht einen Namen." });
const art = String(req.body?.art ?? alt.art);
if (!UNTERSTUETZUNG_ARTEN[art]) return res.status(400).json({ fehler: "Diese Art gibt es nicht." });
const z = zielPruefen(req.body?.ziel ?? alt.ziel);
if (!z.ok) return res.status(400).json({ fehler: z.grund });
const knopf = text(req.body?.knopf ?? alt.knopf, KNOPF_MAX);
/* EIN KNOPF OHNE ZIEL IST EIN VERSPRECHEN INS LEERE. Er sieht aus
wie ein Weg und tut nichts — und weil nichts passiert, hält man
die Seite für kaputt, nicht die Eingabe für unvollständig. */
if (knopf && !z.wert) {
return res.status(400).json({
fehler: "Ein Knopf braucht eine Adresse – sonst passiert beim Drücken nichts.",
});
}
/* UND EIN SICHTBARER WEG MIT ZIEL BRAUCHT EINEN KNOPF, sonst
steht die Adresse da und niemand kommt hin. */
const sichtbar = req.body?.sichtbar === undefined
? !!alt.sichtbar : !!req.body.sichtbar;
if (sichtbar && z.wert && !knopf) {
return res.status(400).json({
fehler: "Zu einer Adresse gehört eine Aufschrift für den Knopf.",
});
}
db().prepare(`UPDATE unterstuetzung_wege
SET titel = ?, text = ?, knopf = ?, ziel = ?, art = ?, sichtbar = ?,
platz = ?, geaendert = ?, geaendert_von = ?
WHERE id = ?`)
.run(titel, text(req.body?.text ?? alt.text, TEXT_MAX), knopf, z.wert, art,
sichtbar ? 1 : 0,
Number.isInteger(Number(req.body?.platz)) ? Number(req.body.platz) : alt.platz,
new Date().toISOString(), req.person.id, id);
/* PROTOKOLLIERT WIRD, DASS DIE ADRESSE SICH GEÄNDERT HAT — nicht,
wie sie lautet. Ein Zahlungslink gehört nicht in ein Protokoll,
das andere lesen können; dass er gewechselt hat, schon. */
protokolliere("unterstuetzung_geaendert", {
personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req),
detail: `${alt.schluessel}${alt.ziel !== z.wert ? " (Adresse gewechselt)" : ""}`,
});
res.json({ ok: true });
} catch (fehler) {
console.error("[unterstuetzung] Aendern:", fehler?.message);
res.status(503).json({ fehler: "nicht_verfuegbar" });
}
});
unterstuetzungRouter.post("/workspace/api/unterstuetzung",
gleicheHerkunft, nurDogFather, express.json({ limit: "8kb" }), (req, res) => {
try {
const d = db();
const { n } = d.prepare("SELECT COUNT(*) AS n FROM unterstuetzung_wege").get();
/* EINE OBERGRENZE, und zwar eine niedrige. Zwanzig Wege sind
keine Auswahl mehr, sondern eine Wand — und die Seite soll
leicht sein. */
if (n >= WEGE_MAX) {
return res.status(400).json({
fehler: `Mehr als ${WEGE_MAX} Wege werden unübersichtlich. Räum erst einen weg.`,
});
}
const titel = text(req.body?.titel, TITEL_MAX);
if (titel.length < 2) return res.status(400).json({ fehler: "Der Weg braucht einen Namen." });
const art = String(req.body?.art ?? "frei");
if (!UNTERSTUETZUNG_ARTEN[art]) return res.status(400).json({ fehler: "Diese Art gibt es nicht." });
const z = zielPruefen(req.body?.ziel);
if (!z.ok) return res.status(400).json({ fehler: z.grund });
const knopf = text(req.body?.knopf, KNOPF_MAX);
if (knopf && !z.wert) {
return res.status(400).json({
fehler: "Ein Knopf braucht eine Adresse – sonst passiert beim Drücken nichts.",
});
}
/* Der Schlüssel wird ABGELEITET und nicht getippt. Er hält die
Striche der letzten Monate am Weg fest, auch wenn der Titel
sich ändert — und ein „eigen-" davor kann nie mit einem der
fünf Startschlüssel zusammenfallen. */
const rein = titel.toLowerCase()
.replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss")
.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 30) || "weg";
let schluessel = "eigen-" + rein;
if (d.prepare("SELECT 1 FROM unterstuetzung_wege WHERE schluessel = ?").get(schluessel)) {
schluessel += "-" + Date.now().toString(36).slice(-4);
}
const { platz } = d.prepare(
"SELECT COALESCE(MAX(platz), 0) + 10 AS platz FROM unterstuetzung_wege").get();
d.prepare(`INSERT INTO unterstuetzung_wege
(schluessel, art, titel, text, knopf, ziel, sichtbar, platz, geaendert, geaendert_von)
VALUES (?,?,?,?,?,?,1,?,?,?)`)
.run(schluessel, art, titel, text(req.body?.text, TEXT_MAX), knopf, z.wert,
platz, new Date().toISOString(), req.person.id);
protokolliere("unterstuetzung_angelegt", {
personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req),
detail: schluessel,
});
res.json({ id: d.prepare("SELECT last_insert_rowid() AS id").get().id });
} catch (fehler) {
console.error("[unterstuetzung] Anlegen:", fehler?.message);
res.status(503).json({ fehler: "nicht_verfuegbar" });
}
});
unterstuetzungRouter.delete("/workspace/api/unterstuetzung/:id(\\d+)",
gleicheHerkunft, nurDogFather, (req, res) => {
try {
const id = Number(req.params.id);
const weg = db().prepare(
"SELECT schluessel FROM unterstuetzung_wege WHERE id = ?").get(id);
if (!weg) return res.status(404).json({ fehler: "nicht_gefunden" });
/* Die Striche gehen mit (ON DELETE CASCADE). Sie ohne ihren Weg
stehen zu lassen hieße, eine Zahl aufzubewahren, zu der niemand
mehr sagen kann, wozu sie gehört. */
db().prepare("DELETE FROM unterstuetzung_wege WHERE id = ?").run(id);
protokolliere("unterstuetzung_entfernt", {
personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req),
detail: weg.schluessel,
});
res.json({ ok: true });
} catch (fehler) {
console.error("[unterstuetzung] Entfernen:", fehler?.message);
res.status(503).json({ fehler: "nicht_verfuegbar" });
}
});
/** Alte Tagesstriche wegräumen — aus dem Rundgang, nicht beim Lesen.
*
* Hinge es am Öffnen der Seite, bliebe alles liegen, solange niemand
* hinsieht. */
export function unterstuetzungAufraeumen() {
try {
const grenze = new Date(Date.now() - ZAEHLUNG_TAGE * 86400000)
.toISOString().slice(0, 10);
const r = db().prepare("DELETE FROM unterstuetzung_striche WHERE tag < ?").run(grenze);
return r.changes || 0;
} catch { return 0; }
}
+30
View File
@@ -34,6 +34,7 @@ import { darfSeite, OHNE_ANMELDUNG, seitenFuer } from "./rechte.js";
import { treffTabellen, treffSperreLesen, MINDESTALTER } from "./treff-tabellen.js";
import { hilfeTabellen } from "./hilfe-tabellen.js";
import { supportTabellen } from "./support-tabellen.js";
import { unterstuetzungTabellen } from "./unterstuetzung-tabellen.js";
import { sitzungPasstZurAdresse, istOhneModiAdresse, istCrewAdresse, istPruefAdresse, AUSSEN_ROLLEN,
TEAM_DOGI_ROLLEN, CREW_ADRESSE } from "./crew-adresse.js";
/* Weitergereicht, damit die Fachmodule sie wie alles andere aus
@@ -1410,6 +1411,31 @@ const TREFF_BEREICHE = [
zeichen: "hinaus", ton: 39, ziel: "unsere-seiten.html", szene: "portal" },
{ ...TREFF_GRUPPE, name: "Mitmachen", unter: "Wie du dazugeh\u00f6ren kannst",
zeichen: "personen", ton: 32, ziel: "bereich.html?b=mitmachen", szene: "garage" },
/* UNTERSTÜTZEN (24.09.2026).
Filipe: "ich brauch auch noch eine kachel im community bereich. wo
mein paypal und meine amazon liste sein wird. wo die leute alle
supporten können auf andere art anstatt nur tiktok."
SIE STEHT VOR "REGELN & HILFE" UND NACH "MITMACHEN", und das ist
die Reihenfolge des Weges: erst dazugehoeren, dann etwas
beitragen. Ganz vorne waere sie eine hohle Hand am Eingang.
WARUM SIE KEIN BRETT IST: Die Bretter (bereich.html?b=...) sind
Listen, auf die jemand schreibt. Hier schreibt niemand — hier
stehen Wege, und genau einer darf sie aendern. Deshalb eine eigene
Seite, wie bei "Draussen" und "Regeln & Hilfe".
TON 45 ist GERECHNET, nicht gegriffen: tools/kachel-farbe-
einzeln.mjs gegen alle 44 vorhandenen Toene. Die Zahl steht
darunter im Kommentar der CSS-Zeile.
RASTERRECHNUNG (drei Spalten, "Willkommen" belegt zwei): Mit
dieser Kachel sind es fuer die Community 11 + 1 = 12 Plaetze,
also genau vier volle Reihen. Vorher waren es elf und damit eine
Luecke am Ende. */
{ ...TREFF_GRUPPE, name: "Unterst\u00fctzen", unter: "Dogi etwas zur\u00fcckgeben \u2013 auch ohne Geld",
zeichen: "unterstuetzen", ton: 45, ziel: "unterstuetzen.html", szene: "lounge" },
/* UNSERE SEITEN (19.09.2026) -- die einzige Kachel, die HINAUSFUEHRT.
Filipe: "ich brauch im community bereich noch eine kachel, die wenn
@@ -5309,6 +5335,10 @@ export function db() {
kommt deshalb hierher, nicht in den Bauplan darueber. */
hilfeTabellen(d);
supportTabellen(d);
/* Die Wege zum Unterstützen (24.09.2026). Sie hängen an personen
(geaendert_von) und kommen deshalb hierher, nicht in den Bauplan
darüber. */
unterstuetzungTabellen(d);
umstellungen(d);
_db = d;
return d;
+5 -5
View File
@@ -16,15 +16,15 @@
verdeckt. Gefunden hat es pruef-struktur.
Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. -->
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
<!-- Ohne diese drei nimmt ein iPhone einen Bildschirmausschnitt als
Symbol, Android zeichnet die Leiste weiss, und die Seite oeffnet
ausserhalb der App -- auf einer Seite, die genau erklaeren soll,
WARUM das Telefon nicht klingelt, ist das besonders unpassend. -->
<link rel="manifest" href="/workspace/crew.webmanifest" />
<meta name="theme-color" content="#06090f" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<style>
/* Bewusst in der Seite und nicht in einer eigenen Datei: Diese Seite
soll auch dann noch etwas anzeigen, wenn genau das schiefgeht,
@@ -126,7 +126,7 @@
<script src="assets/js/anruf-probe.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/anruf-probe.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
</body>
</html>
+4 -4
View File
@@ -23,25 +23,25 @@
},
"icons": [
{
"src": "/assets/img/app-symbole/workspace-192.png?v=202609241329",
"src": "/assets/img/app-symbole/workspace-192.png?v=202609241413",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/img/app-symbole/workspace-512.png?v=202609241329",
"src": "/assets/img/app-symbole/workspace-512.png?v=202609241413",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609241329",
"src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609241413",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609241329",
"src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609241413",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
+251 -48
View File
@@ -152,6 +152,29 @@
font-size: .78rem; color: var(--text-still);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* WIE VIELE WARTEN AUF MICH (24.09.2026).
Die Summe aller ungelesenen Nachrichten, direkt neben der
Überschrift. Sie steht nur da, wenn es etwas zu zählen gibt — eine
Null wäre jeden Tag im Weg für den einen Tag, an dem sie etwas sagt
(das Verstecken macht das Skript).
DIESELBE FORM WIE DIE ZAHL AN DER ZEILE, absichtlich: Wer sie oben
sieht und unten wiederfindet, muss nicht lernen, dass es dasselbe
ist. */
.chat__zaehler {
flex: none;
min-width: 22px; height: 22px; padding: 0 7px;
display: inline-grid; place-items: center;
border-radius: 999px;
font-size: .72rem; font-weight: 750;
font-variant-numeric: tabular-nums;
color: #0b0f16;
background: linear-gradient(160deg,
color-mix(in srgb, var(--akzent) 92%, #ffffff),
var(--akzent));
}
.chat__kopf .knopf { margin-left: auto; flex: none; }
/* Der Zurück-Pfeil gehört dem Handy. Am Rechner stehen beide Spalten
@@ -173,49 +196,194 @@
.chat__zurueck svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8; }
.chat__zurueck:hover { color: var(--text); border-color: var(--rand-hell); }
.chat__suche { padding: 10px 14px 4px; flex: none; }
.chat__suche input { width: 100%; }
/* ---- Das Suchfeld ---------------------------------------------------
Vorher ein nackter Kasten in der Hausform. Er tat, was er sollte,
sah aber aus wie ein Eingabefeld in einem Formular — und dies ist
kein Formular, sondern eine Suche über einer Liste.
DIE LUPE STECKT IM HINTERGRUND und nicht als eigenes Element im
HTML: Ein Zeichen, das nichts tut und nichts sagt, braucht keinen
Platz im Dokument; manche Vorleseprogramme lesen es sonst als
„Grafik" mit. Das Feld selbst trägt seinen Namen über aria-label.
PILLENFORM, weil darunter Zeilen mit 14px-Ecken stehen — ein
scharfkantiger Kasten darüber wirkt wie von einer anderen Seite. */
.chat__suche { padding: 10px 12px 6px; flex: none; }
.chat__suche input {
width: 100%;
padding-left: 36px;
border-radius: 999px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2394a5bb' stroke-width='1.9' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='6.2'/%3E%3Cpath d='m15.8 15.8 4 4'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: 12px 50%;
background-size: 16px 16px;
}
.chat__leer {
margin: 0; padding: 20px 18px;
font-size: .86rem; color: var(--text-still); line-height: 1.55;
}
/* ---------- Links: die Gespräche ----------------------------------------- */
/* ---- Der leere Raum rechts (24.09.2026) -----------------------------
Vorher stand der Satz oben links in einer Fläche von rund tausend
mal siebenhundert Pixeln. Das sah nicht ruhig aus, sondern unfertig
— als wäre die Seite nicht zu Ende geladen.
Der Satz ist Wort für Wort derselbe geblieben: Er sagt, was zu tun
ist, und das war nie das Problem. Er steht nur nicht mehr in der
Ecke.
DAS ZEICHEN IST SEHR LEISE: Es soll den Blick auffangen, nicht ihn
halten. Wer hierher sieht, will ein Gespräch öffnen — kein Bild
ansehen. */
.chat__leer--mitte {
flex: 1; min-height: 0;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
gap: 16px; padding: 30px;
text-align: center;
}
.chat__leer-text { max-width: 30ch; }
.chat__leer-zeichen {
display: grid; place-items: center;
width: 82px; height: 82px;
border-radius: 50%;
border: 1px solid color-mix(in srgb, var(--rand-hell) 55%, transparent);
background:
radial-gradient(90% 90% at 50% 20%,
color-mix(in srgb, var(--akzent) 9%, transparent), transparent 70%),
rgba(255, 255, 255, .02);
}
.chat__leer-zeichen svg {
width: 34px; height: 34px;
fill: none; stroke: color-mix(in srgb, var(--akzent) 55%, var(--text-still));
stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round;
}
/* =====================================================================
DIE GESPRÄCHSLISTE (neu gebaut am 24.09.2026)
=====================================================================
Filipe, mit zwei Bildschirmfotos: „ich will dass die komplette
kachel viel krasser und geiler aussieht. der hintergrund der kachel
soll gleich bleiben aber ansonsten soll alles viel krasser schöner
und geiler sein, viel moderner. hau richtig rein und mach was
spezielles."
DER HINTERGRUND BLEIBT unangetastet – ausdrücklicher Wunsch, und er
ist ohnehin gemessen (siehe der Block weiter oben über die Deckkraft
der Fläche). Geändert wird alles, was darauf liegt.
WAS AUF SEINEM BILD NICHT STIMMTE, und zwar nicht als Geschmack:
1. Bei „Das Rudel" stand das „@" rechts oben und die orange „6"
eine ZEILE TIEFER, links unter dem Zeichen. Das Raster hatte
drei Spalten und bekam vier Kinder – das vierte fiel um.
Ausgerechnet die wichtigste Auskunft der Liste („hier warten
sechs") landete damit an der unauffälligsten Stelle, und die
Zeile war höher als alle anderen.
2. Die Zeilen hatten keine Kanten. Sieben Gespräche standen als
eine einzige graue Fläche da; was gerade offen ist, sah fast
aus wie der Rest.
3. Alles war gleich laut: Name, Uhrzeit und Vorschau in drei
Grautönen, die sich kaum unterscheiden.
WAS JETZT ANDERS IST
ZEILEN SIND KARTEN. Jede hat ihre eigene Fläche, die beim
Überfahren aufwacht. Das offene Gespräch trägt eine Schiene an der
linken Kante – man sieht auch nach dem Scrollen, wo man ist.
DREI ARTEN, DREI FORMEN. Ein Mensch ist rund, eine Gruppe ist ein
abgerundetes Quadrat, ein Kanal trägt die Raute. Auch ohne Farbe
auseinanderzuhalten – wer Farben schlecht unterscheidet, sieht die
Form.
UNGELESENES IST DAS LAUTESTE auf der Zeile, und sonst nichts: die
Zahl in der Akzentfarbe, der Name kräftiger, die Fläche eine Spur
heller. Alles andere tritt zurück.
AUGENSCHONEND (Hausregel): kein Leuchten, kein Blinken, keine
Bewegung außer einem kurzen Farbwechsel beim Überfahren. Ein Chat
wird stundenlang angesehen – was einmal auffällt, nervt beim
zwanzigsten Mal.
------------------------------------------------------------------- */
.chat__liste {
flex: 1; min-height: 0;
overflow-y: auto;
margin: 0; padding: 8px; list-style: none;
display: grid; gap: 3px; align-content: start;
margin: 0; padding: 8px 8px 14px; list-style: none;
display: grid; gap: 4px; align-content: start;
/* Ein schmaler, ruhiger Balken statt des breiten Systemstreifens —
die Liste ist nur rund 300 px breit, da nimmt der Standardbalken
ein Zwanzigstel davon weg. */
scrollbar-width: thin;
scrollbar-color: color-mix(in srgb, var(--rand-hell) 70%, transparent) transparent;
}
.chat-raum__knopf {
--r: var(--akzent);
position: relative;
width: 100%;
display: grid; grid-template-columns: auto 1fr auto;
/* DREI SPALTEN, UND ES BLEIBEN DREI. Alles, was rechts steht, sitzt
in `.chat-raum__rail` — käme morgen eine vierte Marke dazu, bricht
das Raster nicht wieder um (siehe Punkt 1 oben). */
display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
align-items: center; gap: 11px;
padding: 10px 11px;
border: 1px solid transparent; border-radius: 12px;
border: 1px solid transparent;
border-radius: 14px;
background: transparent;
color: inherit; font: inherit; text-align: left; cursor: pointer;
transition: background .16s ease, border-color .16s ease;
}
.chat-raum__knopf:hover { background: rgba(255, 255, 255, .04); }
.chat-raum__knopf:hover {
background: rgba(255, 255, 255, .045);
border-color: color-mix(in srgb, var(--rand-hell) 55%, transparent);
}
.chat-raum__knopf:focus-visible { outline: 2px solid var(--akzent); outline-offset: -2px; }
/* Das offene Gespräch bleibt sichtbar markiert -- sonst weiß man nach
dem Scrollen nicht mehr, wo man ist. */
/* DIE SCHIENE AM OFFENEN GESPRÄCH.
Hier stand bis zum 24.09.2026 eine zweite, schönere Fassung dieser
Regel weiter unten in derselben Datei — mit dem Selektor
`.chat-raum__knopf[data-an="ja"]`. Den hat nie etwas gesetzt: Das
Skript schreibt `data-offen` an das <li>, nicht `data-an` an den
Knopf. Die Regel war also monatelang tote Gestaltung, und das
offene Gespräch bekam nur die blassere erste Fassung.
Jetzt gibt es eine Regel, und sie greift. */
.chat-raum[data-offen="ja"] .chat-raum__knopf {
background: color-mix(in srgb, var(--akzent) 11%, transparent);
border-color: color-mix(in srgb, var(--akzent) 32%, transparent);
background: linear-gradient(90deg,
color-mix(in srgb, var(--akzent) 17%, transparent),
color-mix(in srgb, var(--akzent) 5%, transparent) 62%,
rgba(255, 255, 255, .03));
border-color: color-mix(in srgb, var(--akzent) 34%, transparent);
}
.chat-raum[data-offen="ja"] .chat-raum__knopf::before {
content: "";
position: absolute; left: -1px; top: 10px; bottom: 10px;
width: 3px; border-radius: 0 3px 3px 0;
background: var(--akzent);
}
/* Ungelesenes ist der Grund, warum man hier ist — die Zeile hebt sich
eine Spur, aber nur eine. */
.chat-raum[data-neu="ja"] .chat-raum__knopf {
background: rgba(255, 255, 255, .05);
border-color: color-mix(in srgb, var(--rand) 80%, transparent);
}
.chat-raum[data-neu="ja"][data-offen="ja"] .chat-raum__knopf {
border-color: color-mix(in srgb, var(--akzent) 34%, transparent);
}
/* ---- Das Zeichen: Mensch, Gruppe, Kanal ----------------------------- */
.chat-raum__zeichen {
flex: none;
width: 38px; height: 38px; border-radius: 50%;
width: 44px; height: 44px; border-radius: 50%;
display: grid; place-items: center;
font-size: .82rem; font-weight: 700;
font-size: .86rem; font-weight: 700; letter-spacing: .02em;
color: var(--r);
background: color-mix(in srgb, var(--r) 13%, transparent);
border: 1px solid color-mix(in srgb, var(--r) 30%, transparent);
@@ -225,46 +393,69 @@
.chat-raum__zeichen[data-rolle="manager"] { --r: var(--manager-haupt); }
.chat-raum__zeichen[data-rolle="scout"] { --r: var(--scout-haupt); }
.chat-raum__zeichen[data-rolle="creator"] { --r: var(--creator-haupt); }
/* Eine Gruppe zeigt ihre Größe statt einer Initiale -- der Name der
Gruppe steht ohnehin daneben. */
/* Eine Gruppe zeigt ihre Größe statt einer Initiale — der Name steht
ohnehin daneben. Eckiger als ein Mensch, runder als ein Kanal. */
.chat-raum__zeichen[data-gruppe="ja"] {
--r: #a0aec4;
border-radius: 12px;
border-radius: 14px;
font-size: .95rem;
font-variant-numeric: tabular-nums;
}
/* ---- Die Mitte ------------------------------------------------------ */
.chat-raum__mitte { min-width: 0; }
.chat-raum__oben { display: flex; align-items: baseline; gap: 8px; }
.chat-raum__name {
flex: 1; min-width: 0;
font-size: .89rem; font-weight: 600; color: var(--text-leise);
font-size: .92rem; font-weight: 620; color: var(--text-leise);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-raum__zeit {
flex: none;
font-size: .75rem; color: var(--text-still);
font-size: .73rem; color: var(--text-still);
font-variant-numeric: tabular-nums;
}
.chat-raum__vorschau {
display: block; margin-top: 2px;
font-size: .78rem; color: var(--text-still);
display: block; margin-top: 3px;
font-size: .79rem; line-height: 1.35; color: var(--text-still);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-raum__vorschau[data-weg="ja"] { font-style: italic; opacity: .8; }
/* WER GESCHRIEBEN HAT, eine Spur heller als der Satz. Beim Überfliegen
einer Gruppe sucht man den Namen, nicht den Text — vorher war beides
derselbe Grauton und man musste lesen. */
.chat-raum__wer {
color: color-mix(in srgb, var(--text-leise) 82%, transparent);
font-weight: 600;
}
.chat-raum__vorschau[data-weg="ja"] { font-style: italic; opacity: .78; }
/* Ungelesen: die Zahl UND ein kräftigerer Name. Nur eine Zahl übersieht
man beim Überfliegen. */
.chat-raum[data-neu="ja"] .chat-raum__name { color: var(--text); font-weight: 700; }
.chat-raum[data-neu="ja"] .chat-raum__name { color: var(--text); font-weight: 720; }
.chat-raum[data-neu="ja"] .chat-raum__vorschau { color: var(--text-leise); }
.chat-raum[data-neu="ja"] .chat-raum__zeit { color: var(--text-leise); }
/* ---- Die rechte Spalte ---------------------------------------------- */
/* Sie steht IMMER da, auch leer — sonst wäre die Mitte bei Zeilen ohne
Marke breiter als bei Zeilen mit, und die Namen stünden nicht mehr
untereinander. Eine Liste, deren Kanten wandern, wirkt unruhig, ohne
dass man sagen kann, warum. */
.chat-raum__rail {
flex: none;
display: flex; align-items: center; gap: 6px;
min-width: 22px; justify-content: flex-end;
}
.chat-raum__zahl {
flex: none;
min-width: 21px; height: 21px; padding: 0 6px;
min-width: 22px; height: 22px; padding: 0 7px;
display: grid; place-items: center;
border-radius: 999px;
font-size: .72rem; font-weight: 700;
font-size: .72rem; font-weight: 750;
font-variant-numeric: tabular-nums;
color: #0b0f16;
background: var(--akzent);
background: linear-gradient(160deg,
color-mix(in srgb, var(--akzent) 92%, #ffffff),
var(--akzent));
}
/* ---------- Rechts: der Verlauf ------------------------------------------ */
@@ -914,24 +1105,22 @@
}
/* ---------- Die Gesprächsliste --------------------------------------- */
.chat-raum__knopf {
position: relative;
border-radius: 12px;
margin: 0 8px 4px;
transition: background var(--tempo), box-shadow var(--tempo);
}
/* Das offene Gespräch trägt eine Schiene -- man sieht auch beim Scrollen
der Liste, wo man gerade ist. */
.chat-raum__knopf[data-an="ja"] {
background: linear-gradient(90deg,
color-mix(in srgb, var(--akzent) 16%, transparent),
rgba(255, 255, 255, .03) 55%);
box-shadow: inset 3px 0 0 var(--akzent);
}
/* Ungelesenes ist der Grund, warum man hier ist. */
.chat-raum__knopf:has(.chat-raum__zahl) {
background: rgba(255, 255, 255, .045);
}
/* HIER STAND EINE ZWEITE FASSUNG DERSELBEN REGELN (weg am 24.09.2026).
Sie hat `.chat-raum__knopf` ein zweites Mal beschrieben — Rundung,
Rand und Übergang — und dabei die erste Fassung weiter oben still
überschrieben. Zwei Beschreibungen derselben Sache in derselben
Datei: Wer eine davon ändert, ändert am Ergebnis nichts und sucht
danach den Fehler an der falschen Stelle.
Und die schönere der beiden griff gar nicht: Ihr Selektor hieß
`.chat-raum__knopf[data-an="ja"]`, gesetzt wird aber `data-offen`
am <li>. Das offene Gespräch trug deshalb monatelang die blasse
erste Fassung, während daneben eine Schiene beschrieben stand,
die nie jemand gesehen hat.
Alles steht jetzt EINMAL, weiter oben unter „Die Gesprächsliste
(neu gebaut am 24.09.2026)" — samt Schiene, und die greift. */
/* ---------- Die Nachrichten ------------------------------------------ */
/* HIER STAND DIE ZWEITE FASSUNG DERSELBEN BLASE — sie ist am
@@ -2659,16 +2848,27 @@
ist — ein Knopf mit der Aufschrift „Kachel" müsste erst geöffnet
werden, damit man sieht, was gerade gilt.
===================================================================== */
/* RUND STATT ECKIG (24.09.2026).
Auf Filipes Bildschirmfoto stand hier ein blaues Quadrat mit
weichen Ecken — es sah aus wie ein Bauteil, das noch niemand
gestaltet hat, und nicht wie „so sehen deine Nachrichten aus".
Ein Kreis sagt es besser: Es ist die Farbe EINER PERSON, und
Personen sind in diesem Haus überall rund (Gesicht in der Liste, im
Verlauf, in der Kopfleiste). Der Ring darum ist derselbe wie am
Gesicht — man erkennt die Verwandtschaft, ohne sie zu lernen. */
.chat__kachelknopf {
flex: 0 0 auto;
margin-left: auto;
width: 30px; height: 30px;
width: 32px; height: 32px;
padding: 3px;
border: 1px solid var(--rand-hell);
border-radius: 9px;
border-radius: 50%;
background: transparent;
cursor: pointer;
display: inline-flex; align-items: center; justify-content: center;
transition: border-color .16s ease, box-shadow .16s ease;
}
/* AM FINGER GROESSER (20.09.2026). Gemeldet von pruef-tippziele:
30x30 ist mit der Maus bequem und mit dem Daumen zu klein -- die
@@ -2702,11 +2902,14 @@
content: ""; position: absolute; inset: -8px;
}
}
.chat__kachelknopf:hover { border-color: var(--akzent); }
.chat__kachelknopf:hover {
border-color: var(--akzent);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--akzent) 14%, transparent);
}
.chat__kachelknopf:focus-visible { outline: 2px solid var(--akzent); outline-offset: 2px; }
.chat__kachelknopf-probe {
display: block; width: 100%; height: 100%;
border-radius: 6px;
border-radius: 50%;
/* Eine feine Innenkante, damit ein sehr dunkler Ton nicht aussieht
wie ein Loch im Knopf. */
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .10);
+25 -1
View File
@@ -30,13 +30,37 @@
Gemeinsam
------------------------------------------------------------------- */
/* DIE ÜBERSCHRIFT STEHT AUF EINER FLÄCHE, NICHT AUF DEM FOTO
(24.09.2026).
Gemessen von pruef-buehne: „Wen gehst du durch?" kam auf 1,79:1 --
nötig sind 4,5:1. Gemessen wurde auf rgb(123,95,67), einer hellen
Stelle des Hintergrundbildes. Die Überschrift war dort praktisch
unsichtbar, und zwar genau über der Personenliste, die Filipe heute
umbauen ließ.
Die Prüfung war seit Tagen rot und ist mir erst beim Umbau
aufgefallen -- ein roter Befund, den niemand mehr liest, sichert
nichts. (Der zweite ihrer Befunde, treff-regeln.html, steht noch
offen.)
DIE ANTWORT IST KEINE HELLERE SCHRIFT, SONDERN EINE DECKENDE
FLÄCHE. Über einem Foto lässt sich Lesbarkeit nicht ausrechnen --
das Dahinter wechselt von Motiv zu Motiv, und beim nächsten Bild
wäre auch Weiß zu wenig. Dieselbe Entscheidung wie bei der Karte
weiter unten („Die Karte bekommt eine eigene Fläche", 15.09.2026)
und bei den Gruppenköpfen auf unterstuetzen.css. */
.e-abschnitt {
display: inline-flex; align-items: baseline;
margin: 26px 0 12px;
padding: 6px 13px;
border: 1px solid var(--rand); border-radius: 999px;
background: var(--flaeche);
font-size: .82rem;
font-weight: 650;
letter-spacing: .09em;
text-transform: uppercase;
color: var(--text-still);
color: var(--text-leise);
}
/* ---------------------------------------------------------------------
+16
View File
@@ -1150,6 +1150,22 @@ body.start > main { position: relative; z-index: 1; }
Sorge, eine fast weisse Kachel koennte blenden, war unbegruendet --
gemessen statt vermutet. */
[data-ton="44"] { --ton: #fd6401; } /* Support (gerechnet 24.09.2026) */
/* GERECHNET, NICHT GEWAEHLT (24.09.2026) -- mit
`node tools/kachel-farbe-einzeln.mjs 45 --schreiben`, das die Farbe
sucht, die von den 44 vorhandenen am weitesten entfernt liegt, ohne
eine einzige davon anzufassen.
Abstand zur naechsten vorhandenen Farbe 0,0862 -- das engste
vorhandene Paar liegt bei 0,0154 (Ton 20 <-> Ton 35), sie ist also
rund funfeinhalbmal weiter weg als das schwaechste Glied.
KONTRAST 4,59:1 -- knapp ueber der Hausgrenze von 4,5, und das ist
hier die richtige Entscheidung und nicht die bequeme: Das Werkzeug
prueft seit dem 22.09.2026 ERST die Regeln und DANN den Abstand
(vorher hielt es eine zu blasse Farbe, weil ihr Abstand gut war).
Wer diesen Ton spaeter aufhellen will, muss den Abstand neu rechnen
-- nicht nach Gefuehl nachbessern. */
[data-ton="45"] { --ton: #7567fe; } /* Unterstützen (gerechnet 24.09.2026) */
/* 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
+295
View File
@@ -0,0 +1,295 @@
/* =====================================================================
UNTERSTÜTZEN (24.09.2026)
=====================================================================
Filipe: „ich brauch auch noch eine kachel im community bereich. wo
mein paypal und meine amazon liste sein wird. wo die leute alle
supporten können auf andere art anstatt nur tiktok. … mach das
richtig geil, übertrieben geil."
---------------------------------------------------------------------
DIE GESTALTUNG HAT EINE AUFGABE, UND DIE IST NICHT „SCHÖN"
Auf dieser Seite steht eine Bitte um Geld. Solche Seiten sehen
üblicherweise laut aus: große Knöpfe, Ausrufezeichen, ein Balken mit
einem Ziel. Das funktioniert einmal und fühlt sich danach unangenehm
an — für beide Seiten.
Deshalb hier das Gegenteil, und zwar konsequent:
DIE DREI ARTEN SIND GLEICH GROSS. „Kostet nichts" bekommt dieselbe
Kartenform wie PayPal. Wären die freien Wege kleiner oder weiter
unten, wäre die Aussage „das ist die zweite Wahl" — und die
Mehrheit derer, die hier lesen, wäre damit zweite Wahl.
DIE FARBE KOMMT VON DER KANTE, nicht von der Fläche. Ein Streifen
oben und ein Hauch Verlauf darunter; der Rest bleibt die ruhige
Hausfläche. Eine vollflächig eingefärbte Karte schreit, und diese
Seite soll nicht schreien.
KEIN ZÄHLER FÜR DIE COMMUNITY. Wie oft ein Weg benutzt wurde,
sieht nur DogFather. Eine öffentliche Zahl wäre Druck („erst 3 von
100") oder Beschämung — je nachdem, wie sie ausfällt.
AUGENSCHONEND (Hausregel): keine Leuchtkanten, kein Puls, keine
Bewegung außer einem kurzen Farbwechsel beim Überfahren.
===================================================================== */
/* ---- Die Gruppen ---------------------------------------------------- */
.u-gruppe { margin: 30px 0 0; }
/* DIE ÜBERSCHRIFT STEHT AUF EINER FLÄCHE, NICHT AUF DEM FOTO.
Gemessen am ersten Bildschirmfoto: Der Text lag direkt auf der Bühne
— und über einem Bild lässt sich Lesbarkeit nicht ausrechnen, weil
das Dahinter von Motiv zu Motiv wechselt. Das ist genau der Fall,
für den die Kontrastmessung des Hauses ihren dritten Ausgang hat
(„konnte nicht nachsehen").
Die Antwort darauf ist keine hellere Schrift, sondern eine deckende
Fläche. `--flaeche` ist die des Hauses und mehrfach nachgemessen. */
.u-gruppe__kopf {
display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 4px 11px;
margin: 0 0 13px;
padding: 7px 14px;
border: 1px solid var(--rand); border-radius: 999px;
background: var(--flaeche);
}
.u-gruppe__titel {
margin: 0;
font-size: .78rem; font-weight: 700; letter-spacing: .09em;
text-transform: uppercase; color: var(--text-leise);
}
.u-gruppe__satz { margin: 0; font-size: .82rem; color: var(--text-still); }
.u-gitter {
display: grid; gap: 14px;
/* Dieselbe Rechenweise wie bei den Personenkacheln: `auto-fill` legt
feste Spalten an, damit eine allein stehende Karte in der letzten
Zeile nicht auf die volle Breite wächst und dadurch wichtiger
aussieht als die anderen. */
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
}
/* ---- Eine Karte ----------------------------------------------------- */
.u-weg {
--wton: var(--akzent);
position: relative; overflow: hidden;
display: flex; flex-direction: column; gap: 9px;
min-width: 0;
padding: 20px 20px 18px;
border: 1px solid var(--rand);
border-radius: var(--radius, 18px);
background:
linear-gradient(180deg,
color-mix(in srgb, var(--wton) 9%, transparent), transparent 48%),
color-mix(in srgb, var(--tinte) 92%, #ffffff 8%);
transition: border-color .16s ease;
}
.u-weg:hover { border-color: color-mix(in srgb, var(--wton) 38%, var(--rand)); }
/* DIE KANTE IST DIE FARBE. Oben ein Streifen, der nach rechts
ausläuft — so bleibt die Fläche darunter ruhig lesbar. */
.u-weg::before {
content: ""; position: absolute; inset: 0 0 auto; height: 3px;
background: linear-gradient(90deg,
var(--wton), color-mix(in srgb, var(--wton) 30%, transparent) 55%, transparent);
}
/* Drei Arten, drei Töne. Sie stehen HIER und nicht in der Datenbank:
Eine Farbe ist Gestaltung, kein Inhalt — und aus der Datenbank
käme sie ungeprüft in ein Stilblatt. */
.u-weg[data-art="geld"] { --wton: #f0b25a; }
.u-weg[data-art="geschenk"] { --wton: #79d1c4; }
.u-weg[data-art="frei"] { --wton: var(--akzent); }
.u-weg__kopf { display: flex; align-items: center; gap: 13px; }
.u-weg__zeichen {
flex: none;
width: 46px; height: 46px; border-radius: 14px;
display: grid; place-items: center;
color: var(--wton);
background: color-mix(in srgb, var(--wton) 12%, transparent);
border: 1px solid color-mix(in srgb, var(--wton) 28%, transparent);
}
.u-weg__zeichen svg {
width: 24px; height: 24px;
fill: none; stroke: currentColor;
stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}
.u-weg__zeichen svg .voll { fill: currentColor; stroke: none; }
.u-weg__namen { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.u-weg__titel {
margin: 0;
font-size: 1.06rem; font-weight: 650; color: var(--text);
overflow-wrap: anywhere;
}
.u-weg__marke {
align-self: flex-start;
font-size: .68rem; font-weight: 700; letter-spacing: .07em;
text-transform: uppercase;
padding: 2px 8px; border-radius: 999px;
color: color-mix(in srgb, var(--wton) 72%, #ffffff);
border: 1px solid color-mix(in srgb, var(--wton) 32%, transparent);
background: color-mix(in srgb, var(--wton) 8%, transparent);
}
.u-weg__text {
margin: 0;
font-size: .87rem; line-height: 1.62; color: var(--text-still);
}
/* Der Knopf steht UNTEN und auf einer Linie mit den Nachbarkarten —
`margin-top: auto` schiebt ihn ans Ende, egal wie lang der Text
darüber ist. Ohne das säße er bei jeder Karte auf einer anderen
Höhe, und das Auge müsste ihn jedes Mal neu suchen. */
.u-weg__knopf {
margin-top: auto;
align-self: flex-start;
display: inline-flex; align-items: center; gap: 8px;
/* 44px Höhe — die Fingergrenze des Hauses. Dieser Knopf führt zu
einer Zahlung; ihn zu verfehlen ist ärgerlicher als anderswo. */
min-height: 44px; padding: 10px 17px;
border-radius: 999px;
border: 1px solid color-mix(in srgb, var(--wton) 42%, transparent);
background: color-mix(in srgb, var(--wton) 14%, transparent);
color: color-mix(in srgb, var(--wton) 78%, #ffffff);
font: inherit; font-size: .88rem; font-weight: 620;
text-decoration: none; cursor: pointer;
transition: background .16s ease, border-color .16s ease;
}
.u-weg__knopf:hover {
background: color-mix(in srgb, var(--wton) 22%, transparent);
border-color: color-mix(in srgb, var(--wton) 62%, transparent);
}
.u-weg__knopf svg {
width: 15px; height: 15px; flex: none;
fill: none; stroke: currentColor; stroke-width: 1.8;
stroke-linecap: round; stroke-linejoin: round;
}
/* ---- Was nur DogFather sieht ---------------------------------------- */
.u-leitung {
margin: 22px 0 0; padding: 15px 17px;
border: 1px solid color-mix(in srgb, var(--akzent) 26%, var(--rand));
border-radius: var(--radius-klein, 12px);
background:
linear-gradient(180deg,
color-mix(in srgb, var(--akzent) 7%, transparent), transparent 60%),
color-mix(in srgb, var(--tinte) 90%, #ffffff 10%);
}
.u-leitung__kopf {
display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
}
.u-leitung__titel {
margin: 0; flex: 1 1 12rem;
font-size: .95rem; font-weight: 650; color: var(--text);
}
.u-leitung__satz {
margin: 7px 0 0; font-size: .83rem; line-height: 1.6; color: var(--text-still);
}
.u-leitung__satz[data-warnung="ja"] {
color: color-mix(in srgb, var(--warn) 50%, #ffffff); font-weight: 600;
}
.u-zahlen {
display: grid; gap: 8px; margin-top: 13px;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.u-zahl {
padding: 9px 12px;
border: 1px solid var(--rand); border-radius: 10px;
background: rgba(255, 255, 255, .03);
}
.u-zahl__n {
display: block;
font-size: 1.3rem; font-weight: 700; color: var(--text);
font-variant-numeric: tabular-nums;
}
.u-zahl__text { display: block; font-size: .75rem; color: var(--text-leise); }
/* Die Zeile an der Karte — nur im Änderungsmodus, nur für ihn. */
.u-weg__zahl {
font-size: .75rem; color: var(--text-leise);
font-variant-numeric: tabular-nums;
}
.u-weg[data-aus="ja"] { opacity: .62; }
.u-weg__aus {
align-self: flex-start;
font-size: .7rem; font-weight: 650;
padding: 2px 9px; border-radius: 999px;
color: var(--text-still);
border: 1px dashed var(--rand-hell);
}
/* ---- Der Änderungsmodus --------------------------------------------- */
/* Die Felder stehen IN der Karte, nicht in einem eigenen Fenster: Man
sieht beim Tippen, was dabei herauskommt. Ein Dialog über der Seite
hätte genau das verdeckt, was man gerade ändert. */
.u-form { display: flex; flex-direction: column; gap: 9px; }
.u-form label {
display: flex; flex-direction: column; gap: 4px;
font-size: .76rem; color: var(--text-leise);
}
.u-form input[type="text"],
.u-form input[type="url"],
.u-form textarea,
.u-form select { width: 100%; }
.u-form textarea { min-height: 76px; resize: vertical; }
.u-form__reihe { display: flex; flex-wrap: wrap; gap: 9px; align-items: center; }
.u-form__an {
display: inline-flex; align-items: center; gap: 7px;
font-size: .82rem; color: var(--text-still);
}
.u-form__knoepfe {
display: flex; flex-wrap: wrap; gap: 8px; margin-top: 2px;
}
.u-form__weg {
margin-left: auto;
color: color-mix(in srgb, var(--warn) 55%, #ffffff);
border-color: color-mix(in srgb, var(--warn) 32%, transparent);
}
.u-neu {
display: grid; place-items: center; gap: 8px;
min-height: 150px; padding: 20px;
border: 1px dashed var(--rand-hell); border-radius: var(--radius, 18px);
background: transparent;
color: var(--text-leise); font: inherit; font-size: .88rem;
cursor: pointer;
transition: border-color .16s ease, color .16s ease;
}
.u-neu:hover { border-color: var(--akzent); color: var(--text); }
.u-neu__plus { font-size: 1.6rem; line-height: 1; }
/* ---- Rand und Ränder ------------------------------------------------- */
.u-laedt { margin: 26px 0; font-size: .88rem; color: var(--text-still); }
/* Auch dieser Satz stand auf dem Foto — und er ist der wichtigste der
Seite: Er nimmt den Druck weg. Deshalb ebenfalls auf eine deckende
Flaeche, und nicht nur eine Trennlinie darueber. */
.u-fuss {
margin: 34px 0 6px; padding: 14px 17px;
border: 1px solid var(--rand); border-radius: var(--radius-klein, 12px);
background: var(--flaeche);
font-size: .84rem; line-height: 1.65; color: var(--text-still);
max-width: 68ch;
}
/* Am Handy eine Karte je Zeile — zwei nebeneinander wären hier keine
Übersicht, sondern zwei halbe Texte. Der Unterschied zur
Personenliste ist Absicht: Dort steht eine Zahl, hier ein Satz. */
@media (max-width: 620px) {
.u-gitter { grid-template-columns: 1fr; }
.u-weg { padding: 16px 16px 15px; }
.u-weg__titel { font-size: 1rem; }
}
@media (prefers-reduced-motion: reduce) {
.u-weg, .u-weg__knopf, .u-neu { transition: none; }
}
/* Im Kontrastmodus fällt `color-mix` weg — eine Markierung, die nur aus
einer Tönung besteht, verschwindet dort komplett. Deshalb echte
Rahmen, die jeden Farbmodus überleben. */
@media (forced-colors: active) {
.u-weg, .u-weg__zeichen, .u-weg__knopf, .u-weg__marke, .u-zahl {
border: 1px solid CanvasText;
}
}
+21
View File
@@ -327,6 +327,27 @@
linien: ['M8 7.6h11.2a1.2 1.2 0 0 1 1.2 1.2v8.4a1.2 1.2 0 0 1-1.2 1.2H8a1.2 1.2 0 0 1-1.2-1.2V8.8A1.2 1.2 0 0 1 8 7.6z',
'M4.6 16.6V5.6A1.2 1.2 0 0 1 5.8 4.4h10.6'],
},
/* UNTERSTUETZEN (24.09.2026) -- ein Herz ueber zwei offenen Haenden.
Filipe: "wo die leute alle supporten koennen auf andere art
anstatt nur tiktok."
DAS HERZ IST FLAECHE, die Haende sind Linie. Auf 19 Pixeln
verschwindet eine reine Strichzeichnung, und ein ganz
gefuelltes Zeichen sieht aus wie ein Klecks -- dieselbe
Aufteilung wie bei `galerie` darueber.
WARUM NICHT NUR EIN HERZ: Ein Herz allein heisst "gefaellt
mir" -- das gibt es im Haus schon an den Beitraegen. Die
Schale darunter macht daraus "etwas geben", ohne ein
Geldstueck zu zeichnen. Ein Muenzsymbol waere falsch: Die
Haelfte der Wege auf dieser Seite kostet nichts. */
unterstuetzen: {
fuellung: ['M12 12.2 8.62 9.02a2.2 2.2 0 0 1 3.03-3.18l.35.33.35-.33a2.2 2.2 0 0 1 3.03 3.18z'],
/* Eine Schale, kein Kreis: Sie ist oben offen -- man haelt etwas
hin, man schliesst es nicht ein. */
linien: ['M4.9 14.4c0 2.9 3.18 4.9 7.1 4.9s7.1-2 7.1-4.9'],
},
hinaus: {
/* Nur der Pfeilkopf ist Flaeche -- er ist die Aussage. */
fuellung: ['M13.8 4H20v6.2z'],
+75 -13
View File
@@ -790,6 +790,25 @@
ziel.textContent = '';
$('liste-leer').hidden = raeume.length > 0;
/* WIE VIELE WARTEN AUF MICH (24.09.2026) -- die Summe, nicht die
Zahl der Gespraeche. „3 Gespraeche mit Neuem" und „17 neue
Nachrichten" sind zwei verschiedene Auskuenfte, und die zweite
ist die, nach der man fragt, bevor man anfaengt.
GERECHNET UEBER `raeume` UND NICHT UEBER `zeigen`: Wer im
Suchfeld tippt, filtert die Liste -- die Summe darueber wuerde
dann mitwandern und behaupten, es warte weniger. */
{
const zaehler = $('liste-zaehler');
if (zaehler) {
const summe = raeume.reduce((n, r) => n + (Number(r.ungelesen) || 0), 0);
zaehler.textContent = summe > 99 ? '99+' : String(summe);
zaehler.setAttribute('aria-label',
summe === 1 ? '1 ungelesene Nachricht' : summe + ' ungelesene Nachrichten');
zaehler.hidden = summe === 0;
}
}
/* Das Suchfeld erst, wenn die Liste lang genug ist, um darin zu
suchen. Vorher ist es ein Feld ohne Zweck. */
/* IMMER SICHTBAR SEIT DEM 09.09.2026.
@@ -804,6 +823,11 @@
for (const r of zeigen) {
const li = el('li', 'chat-raum');
li.dataset.id = String(r.id);
/* DIE ART GEHOERT AN DIE ZEILE (24.09.2026). Kanal, Gruppe und
Zweiergespraech sehen unterschiedlich aus; bisher stand die
Art nur am runden Zeichen, und das Stilblatt konnte deshalb
nur DAS einfaerben -- nicht die Zeile drumherum. */
li.dataset.art = r.art || 'direkt';
if (offen && r.id === offen.id) li.dataset.offen = 'ja';
if (r.ungelesen) li.dataset.neu = 'ja';
@@ -838,12 +862,18 @@
Sinnvolles statt eines kaputten Bildsymbols. Dieselbe
Bauweise wie im Verlauf, in der Kopfleiste und in der
Personenauswahl -- vier Stellen, ein Verhalten. */
if (gegen.bild) {
const b = document.createElement('img');
b.src = gegen.bild; b.alt = '';
b.addEventListener('error', () => b.remove());
zeichen.append(b);
}
/* EINMAL, NICHT ZWEIMAL (behoben 24.09.2026).
Dieser Block stand hier doppelt -- Zeichen fuer Zeichen
gleich, einmal vor und einmal nach der Rollenzeile. Jedes
Gespraech mit Profilbild lud das Bild also zweimal und
haengte zwei <img> uebereinander.
Zu SEHEN war davon nichts: Beide liegen deckend
uebereinander und zeigen dasselbe Bild. Gekostet hat es
trotzdem -- doppelte Ladelast bei jedem Neuzeichnen der
Liste, und die wird bei jeder eintreffenden Nachricht neu
gezeichnet. */
zeichen.dataset.rolle = gegen.rolle;
if (gegen.bild) {
const b = document.createElement('img');
@@ -867,6 +897,11 @@
zwei Leute schreiben koennen -- in einer Gruppe wie in einem
Kanal. Hier stand `=== 'gruppe'`, und ein Kanal waere damit
die einzige Stelle ohne Absender in der Vorschau gewesen. */
/* WER GESCHRIEBEN HAT, steht seit dem 24.09.2026 in einem
eigenen Stueck Text statt mitten im Satz. Damit kann das
Stilblatt den Namen leicht absetzen -- beim Ueberfliegen
sucht man „wer", nicht „was", und ein durchgehend grauer
Satz zwingt zum Lesen. */
const wer = r.letzte.von === ich.name ? 'Du: '
: (r.art !== 'direkt' ? `${r.letzte.von}: ` : '');
/* KEIN „Nachricht zurueckgenommen" MEHR (22.09.2026). Hier
@@ -876,10 +911,13 @@
nicht mehr als letzte, weil es die Zeile nicht mehr gibt.
Sollte doch einmal eine alte, zurueckgenommene durchkommen,
steht hier lieber nichts als ein Hinweis auf sie. */
vorschau.textContent = r.letzte.zurueckgenommen
? 'Noch nichts geschrieben'
: wer + r.letzte.text;
if (r.letzte.zurueckgenommen) vorschau.dataset.weg = 'ja';
if (r.letzte.zurueckgenommen) {
vorschau.textContent = 'Noch nichts geschrieben';
vorschau.dataset.weg = 'ja';
} else {
if (wer) vorschau.append(el('span', 'chat-raum__wer', wer));
vorschau.append(document.createTextNode(r.letzte.text));
}
} else {
vorschau.textContent = 'Noch nichts geschrieben';
vorschau.dataset.weg = 'ja';
@@ -898,17 +936,41 @@
ist, und es traegt einen eigenen Satz fuer Vorleseprogramme:
Ein "@" allein wird je nach Programm als "at", "Klammeraffe"
oder gar nicht vorgelesen. */
/* ==== DIE RECHTE SPALTE (24.09.2026) =========================
HIER LAG EIN ECHTER FEHLER, und man sah ihn auf Filipes
Bildschirmfoto: Bei „Das Rudel" stand das „@" rechts oben und
die orange „6" eine ZEILE TIEFER, links unter dem Zeichen.
URSACHE: Der Knopf ist ein Raster mit DREI Spalten
(Zeichen | Mitte | Rest). Mit „@" UND Zahl sind es aber VIER
Kinder -- und das vierte faellt in die naechste Rasterzeile.
Die Zeile wurde dadurch hoeher als alle anderen, und die
wichtigste Auskunft der ganzen Liste („hier warten sechs
ungelesene") stand an der unauffaelligsten Stelle.
Beide sitzen jetzt in EINER Spalte nebeneinander. Damit bleibt
das Raster dreispaltig, egal wie viele Marken dazukommen --
und eine vierte Marke morgen bricht es nicht wieder auf. */
const rail = el('span', 'chat-raum__rail');
if (r.erwaehnt) {
const marke = el('span', 'chat-raum__erwaehnt', '@');
marke.title = 'Du wurdest hier erwähnt';
marke.setAttribute('aria-label', 'Du wurdest hier erwähnt');
knopf.append(marke);
rail.append(marke);
}
if (r.ungelesen) {
const zahl = el('span', 'chat-raum__zahl', String(r.ungelesen));
const zahl = el('span', 'chat-raum__zahl',
r.ungelesen > 99 ? '99+' : String(r.ungelesen));
zahl.setAttribute('aria-label', `${r.ungelesen} ungelesen`);
knopf.append(zahl);
rail.append(zahl);
}
/* Die Spalte steht IMMER da, auch leer. Ohne sie waere die Mitte
bei Zeilen ohne Marke breiter als bei Zeilen mit -- und die
Namen staenden nicht mehr untereinander. Eine Liste, in der
die Kanten wandern, wirkt unruhig, ohne dass man sagen kann,
warum. */
knopf.append(rail);
knopf.addEventListener('click', () => raumOeffnen(r.id));
li.append(knopf);
+476
View File
@@ -0,0 +1,476 @@
/* =====================================================================
UNTERSTÜTZEN (24.09.2026)
=====================================================================
Filipe: „jeder soll diese kachel sehen aber nur dogfather soll sie
verändern können oder vieles mehr sehen. alle anderen, rechte und
linke hand, modis und community sollen supporten können."
---------------------------------------------------------------------
DREI DINGE, DIE HIER ANDERS SIND ALS AUF DEN ÜBRIGEN SEITEN
1. DIE OBERFLÄCHE ENTSCHEIDET NICHTS. `darf_aendern` kommt vom
Server und wird hier nur GELESEN. Wer das Feld im Browser auf
„true" setzt, bekommt trotzdem 404 zurück — die Regel steht in
workspace-unterstuetzung.js. Ein verstecktes Feld ist eine
Bitte, keine Sperre.
2. DER ZÄHLER DARF DEN WEG NIE AUFHALTEN. Ein Klick auf „Bei PayPal
öffnen" muss PayPal öffnen, auch wenn der Server gerade nicht
antwortet. Deshalb `sendBeacon`: Er schickt den Strich los und
wartet auf nichts. Ein `await fetch()` vor der Weiterleitung
wäre ein Zähler, der im Zweifel eine Zahlung verhindert.
3. EINE ADRESSE WIRD NICHT AUS TEXT ZUSAMMENGESETZT. Das Ziel geht
über `href` in ein `<a>`, nie über `innerHTML` — und jeder Weg
nach draußen bekommt `rel="noopener noreferrer"`. Ohne
`noopener` kann die geöffnete Seite über `window.opener` auf
diese hier zurückgreifen.
===================================================================== */
(() => {
const $ = (id) => document.getElementById(id);
const el = (t, k, text) => {
const n = document.createElement(t);
if (k) n.className = k;
if (text !== undefined) n.textContent = text;
return n;
};
const hole = (pfad, optionen) => window.holen(pfad, optionen);
const melde = (text, gut) => {
const f = $('fehler');
if (!f) return;
f.textContent = text || '';
f.hidden = !text;
f.dataset.gut = gut ? 'ja' : 'nein';
};
let stand = null; // die Antwort des Servers
let aendern = false; // steht der Änderungsmodus an?
/* =====================================================================
DIE ZEICHEN
Eines je Weg, mit einem Rückfall je Art. Ein Weg, den DogFather
neu anlegt, hat keinen eigenen Schlüssel in dieser Liste — und
bekommt trotzdem ein Zeichen, statt ein Loch zu lassen.
`voll` markiert die Teile, die Fläche sind statt Linie. Auf 24
Pixeln verschwindet eine reine Strichzeichnung, und ein ganz
gefülltes Zeichen sieht aus wie ein Klecks — dieselbe Aufteilung
wie bei den Kachelzeichen in bereiche.js.
===================================================================== */
const ZEICHEN = {
paypal: [
['M3.6 8.2h16.8a1.4 1.4 0 0 1 1.4 1.4v8.2a1.4 1.4 0 0 1-1.4 1.4H3.6a1.4 1.4 0 0 1-1.4-1.4V9.6a1.4 1.4 0 0 1 1.4-1.4z'],
['M2.2 12.4h19.6'],
['M5.6 8.2 7.2 5a1.4 1.4 0 0 1 1.9-.6l6.9 3.8', 'voll-nein'],
],
amazon: [
['M4 11.2h16v7.2a1.4 1.4 0 0 1-1.4 1.4H5.4A1.4 1.4 0 0 1 4 18.4z'],
['M3 8.1h18v3.1H3z'],
['M12 8.1v11.7'],
['M12 8.1S10.9 4.2 8.8 4.2a2 2 0 0 0 0 3.9z'],
['M12 8.1s1.1-3.9 3.2-3.9a2 2 0 0 1 0 3.9z'],
],
dabei: [
['M9.2 11.4a3.1 3.1 0 1 0 0-6.2 3.1 3.1 0 0 0 0 6.2z'],
['M2.8 19.6c0-3.3 2.9-5.6 6.4-5.6s6.4 2.3 6.4 5.6'],
['M16.4 6a3 3 0 0 1 0 5.6'],
['M17.6 14.6c2.2.6 3.6 2.4 3.6 5'],
],
weitersagen: [
['M17.8 8.4a2.6 2.6 0 1 0 0-5.2 2.6 2.6 0 0 0 0 5.2z'],
['M5.6 14.6a2.6 2.6 0 1 0 0-5.2 2.6 2.6 0 0 0 0 5.2z'],
['M17.8 20.8a2.6 2.6 0 1 0 0-5.2 2.6 2.6 0 0 0 0 5.2z'],
['M7.9 10.7 15.5 7', 'M7.9 13.3l7.6 3.7'],
],
draussen: [
['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'],
],
/* Rückfall je Art. */
geld: [['M3.6 8.2h16.8a1.4 1.4 0 0 1 1.4 1.4v8.2a1.4 1.4 0 0 1-1.4 1.4H3.6a1.4 1.4 0 0 1-1.4-1.4V9.6a1.4 1.4 0 0 1 1.4-1.4z'], ['M2.2 12.4h19.6']],
geschenk: [['M4 11.2h16v7.2a1.4 1.4 0 0 1-1.4 1.4H5.4A1.4 1.4 0 0 1 4 18.4z'], ['M3 8.1h18v3.1H3z'], ['M12 8.1v11.7']],
frei: [['M12 20.2 4.4 12.6a4.6 4.6 0 0 1 6.3-6.7l1.3 1.2 1.3-1.2a4.6 4.6 0 0 1 6.3 6.7z']],
};
function zeichenBauen(w) {
const pfade = ZEICHEN[w.schluessel] || ZEICHEN[w.art] || ZEICHEN.frei;
const NS = 'http://www.w3.org/2000/svg';
const svg = document.createElementNS(NS, 'svg');
svg.setAttribute('viewBox', '0 0 24 24');
svg.setAttribute('aria-hidden', 'true');
for (const gruppe of pfade) {
for (const d of gruppe) {
if (!d.startsWith('M')) continue;
const p = document.createElementNS(NS, 'path');
p.setAttribute('d', d);
svg.append(p);
}
}
return svg;
}
const PFEIL_RAUS = ['M13.8 4H20v6.2', 'M19.4 4.6 12 12',
'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'];
function pfeilBauen() {
const NS = 'http://www.w3.org/2000/svg';
const svg = document.createElementNS(NS, 'svg');
svg.setAttribute('viewBox', '0 0 24 24');
svg.setAttribute('aria-hidden', 'true');
for (const d of PFEIL_RAUS) {
const p = document.createElementNS(NS, 'path');
p.setAttribute('d', d);
svg.append(p);
}
return svg;
}
/* =====================================================================
EINE KARTE
===================================================================== */
/** Führt die Adresse aus dem Haus hinaus?
*
* Entscheidet über `target` und `rel` — und darüber, ob überhaupt
* ein `rel` nötig ist. Eine Seite dieses Hauses öffnet im selben
* Fenster: Ein neuer Tab für „Draußen" wäre ein Fenster mehr, das
* niemand zumacht. */
const nachDraussen = (ziel) => /^https:\/\//i.test(ziel);
function karte(w) {
const k = el('article', 'u-weg');
k.dataset.art = w.art;
k.dataset.schluessel = w.schluessel;
if (!w.sichtbar) k.dataset.aus = 'ja';
const kopf = el('div', 'u-weg__kopf');
const zeichen = el('span', 'u-weg__zeichen');
zeichen.setAttribute('aria-hidden', 'true');
zeichen.append(zeichenBauen(w));
kopf.append(zeichen);
const namen = el('div', 'u-weg__namen');
namen.append(el('h3', 'u-weg__titel', w.titel));
namen.append(el('span', 'u-weg__marke', stand.arten[w.art] || w.art));
kopf.append(namen);
k.append(kopf);
if (w.text) k.append(el('p', 'u-weg__text', w.text));
if (aendern && stand.darf_aendern) {
k.append(formular(w));
return k;
}
/* NUR EIN KNOPF, WENN ES EINEN WEG GIBT. „Einfach da sein" hat
keinen — und ein Knopf, der nichts tut, lässt die Seite kaputt
aussehen statt den Weg selbsterklärend. */
if (w.ziel && w.knopf) {
const a = el('a', 'u-weg__knopf');
a.href = w.ziel;
a.append(document.createTextNode(w.knopf));
if (nachDraussen(w.ziel)) {
a.target = '_blank';
/* `noopener` ist die Sicherung: Ohne ihn kann die geöffnete
Seite über `window.opener` auf diese hier zugreifen. */
a.rel = 'noopener noreferrer';
a.append(pfeilBauen());
}
a.addEventListener('click', () => strich(w.id));
k.append(a);
}
/* WAS NUR DOGFATHER SIEHT. Ohne Änderungsmodus, damit er im Alltag
dieselbe Seite sieht wie alle anderen — und nur dann mehr, wenn
er danach fragt. */
if (stand.darf_aendern) {
if (!w.sichtbar) k.append(el('span', 'u-weg__aus', 'steht für niemanden da'));
const z = stand.zahlen?.[w.id];
k.append(el('span', 'u-weg__zahl', z
? `${z.gesamt}× geöffnet · ${z.tage30} in 30 Tagen · ${z.tage7} in 7 Tagen`
: 'noch nicht geöffnet'));
}
return k;
}
/* =====================================================================
DER STRICH
Kein Name, keine Nummer — der Server zählt Weg und Tag (siehe
unterstuetzung-tabellen.js).
`sendBeacon` und nicht `fetch`: Der Browser folgt dem Link im
selben Augenblick, und eine laufende Anfrage würde dabei
abgebrochen. Ein Strich, der nur gezählt wird, wenn die Seite noch
offen ist, zählt ausgerechnet die erfolgreichen Klicks nicht.
===================================================================== */
function strich(id) {
const weg = `/workspace/api/unterstuetzung/${id}/strich`;
try {
if (navigator.sendBeacon && navigator.sendBeacon(weg, new Blob())) return;
} catch { /* dann eben der Rückfall darunter */ }
/* RÜCKFALL, und er darf ebenfalls nichts aufhalten: `keepalive`
lässt die Anfrage weiterlaufen, während die Seite wechselt. */
try {
fetch(weg, { method: 'POST', credentials: 'same-origin', keepalive: true })
.catch(() => {});
} catch { /* der Weg nach draußen zählt, nicht der Strich */ }
}
/* =====================================================================
DAS FORMULAR — nur im Änderungsmodus
===================================================================== */
function feld(art, name, wert, hinweis) {
const l = el('label');
l.append(el('span', null, hinweis));
const i = document.createElement(art === 'lang' ? 'textarea' : 'input');
if (art !== 'lang') i.type = 'text';
i.value = wert ?? '';
i.dataset.feld = name;
l.append(i);
return l;
}
function formular(w) {
const f = el('div', 'u-form');
f.append(feld('kurz', 'titel', w.titel, 'Name'));
f.append(feld('lang', 'text', w.text, 'Was dahintersteht'));
const reihe = el('div', 'u-form__reihe');
const artFeld = el('label');
artFeld.append(el('span', null, 'Art'));
const auswahl = document.createElement('select');
auswahl.dataset.feld = 'art';
for (const [wert, name] of Object.entries(stand.arten)) {
const o = document.createElement('option');
o.value = wert; o.textContent = name;
if (wert === w.art) o.selected = true;
auswahl.append(o);
}
artFeld.append(auswahl);
reihe.append(artFeld);
const an = el('label', 'u-form__an');
const haken = document.createElement('input');
haken.type = 'checkbox';
haken.dataset.feld = 'sichtbar';
haken.checked = !!w.sichtbar;
an.append(haken, document.createTextNode('steht für alle da'));
reihe.append(an);
f.append(reihe);
f.append(feld('kurz', 'knopf', w.knopf, 'Aufschrift des Knopfes (leer = kein Knopf)'));
const zielFeld = feld('kurz', 'ziel', w.ziel, 'Adresse (https://… oder seite.html)');
f.append(zielFeld);
const knoepfe = el('div', 'u-form__knoepfe');
const sichern = el('button', 'knopf knopf--klein', 'Speichern');
sichern.type = 'button';
sichern.addEventListener('click', () => sichere(w.id, f));
knoepfe.append(sichern);
const weg = el('button', 'abmelden u-form__weg', 'Entfernen');
weg.type = 'button';
weg.addEventListener('click', () => entferne(w));
knoepfe.append(weg);
f.append(knoepfe);
return f;
}
const lies = (f, name) => {
const e = f.querySelector(`[data-feld="${name}"]`);
if (!e) return undefined;
return e.type === 'checkbox' ? e.checked : e.value;
};
async function sichere(id, f) {
melde('');
const koerper = {
titel: lies(f, 'titel'), text: lies(f, 'text'),
knopf: lies(f, 'knopf'), ziel: lies(f, 'ziel'),
art: lies(f, 'art'), sichtbar: lies(f, 'sichtbar'),
};
try {
const a = await hole(`/workspace/api/unterstuetzung/${id}`, {
method: 'PUT', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(koerper),
});
if (!a.ok) {
const r = await a.json().catch(() => ({}));
melde(r.fehler || 'Das ließ sich nicht speichern.');
return;
}
melde('Gespeichert.', true);
await laden();
} catch { melde('Das ließ sich nicht speichern.'); }
}
async function entferne(w) {
/* GEFRAGT WIRD ÜBER DIE HAUS-NACHFRAGE und nicht über `confirm`.
Sie sieht aus wie der Rest des Hauses und lässt sich mit der
Tastatur bedienen — `confirm` sperrt die ganze Seite. */
const ja = window.bestaetige
? await window.bestaetige({
titel: `„${w.titel}" entfernen?`,
text: 'Der Weg verschwindet für alle, und die Striche der letzten '
+ 'Monate gehen mit. Das lässt sich nicht zurückholen.',
ja: 'Entfernen', nein: 'Behalten',
})
: true;
if (!ja) return;
try {
const a = await hole(`/workspace/api/unterstuetzung/${w.id}`, { method: 'DELETE' });
if (!a.ok) { melde('Das ließ sich nicht entfernen.'); return; }
melde('Entfernt.', true);
await laden();
} catch { melde('Das ließ sich nicht entfernen.'); }
}
async function neuerWeg() {
melde('');
try {
const a = await hole('/workspace/api/unterstuetzung', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ titel: 'Neuer Weg', art: 'frei', text: '' }),
});
if (!a.ok) {
const r = await a.json().catch(() => ({}));
melde(r.fehler || 'Das ließ sich nicht anlegen.');
return;
}
await laden();
} catch { melde('Das ließ sich nicht anlegen.'); }
}
/* =====================================================================
ZEICHNEN
===================================================================== */
function gruppe(titel, satz, wege) {
if (!wege.length) return null;
const s = el('section', 'u-gruppe');
const kopf = el('div', 'u-gruppe__kopf');
kopf.append(el('h2', 'u-gruppe__titel', titel));
if (satz) kopf.append(el('p', 'u-gruppe__satz', satz));
s.append(kopf);
const gitter = el('div', 'u-gitter');
for (const w of wege) gitter.append(karte(w));
s.append(gitter);
return s;
}
function zeichnen() {
const ziel = $('wege');
ziel.textContent = '';
const geben = stand.wege.filter((w) => w.art !== 'frei');
const frei = stand.wege.filter((w) => w.art === 'frei');
/* ZWEI GRUPPEN, UND DIE ZWEITE IST NICHT DIE ABTEILUNG FÜR ARME.
Sie trägt dieselbe Kartenform und dieselbe Größe; nur die
Überschrift sortiert. Ohne Überschriften stünden fünf Karten
nebeneinander und man müsste jede lesen, um zu wissen, was
Geld kostet. */
const a = gruppe('Direkt etwas geben',
'Geht an Dogi selbst – ohne Umweg über TikTok.', geben);
const b = gruppe('Kostet nichts',
'Genauso viel wert. Wirklich.', frei);
if (a) ziel.append(a);
if (b) ziel.append(b);
if (aendern && stand.darf_aendern) {
const neu = el('button', 'u-neu');
neu.type = 'button';
neu.append(el('span', 'u-neu__plus', '+'));
neu.append(el('span', null, 'Noch einen Weg dazunehmen'));
neu.addEventListener('click', neuerWeg);
const gitter = el('div', 'u-gitter');
gitter.append(neu);
const s = el('section', 'u-gruppe');
s.append(gitter);
ziel.append(s);
}
if (!stand.wege.length) {
ziel.append(el('p', 'u-laedt', stand.darf_aendern
? 'Noch kein Weg eingetragen. Leg oben einen an.'
: 'Hier steht gerade nichts. Schau später noch einmal vorbei.'));
}
}
function leitungZeichnen() {
const block = $('leitung');
if (!stand.darf_aendern) { block.hidden = true; return; }
block.hidden = false;
/* DER HINWEIS AUF DEN FEHLENDEN PAYPAL-LINK.
Er steht hier und nicht als Platzhalter in der Datenbank: Eine
geratene Zahlungsadresse wäre der gefährlichste Fehler dieser
ganzen Seite — Geld an einen Fremden, und niemand merkt es.
Also steht dort nichts, der Weg ist unsichtbar, und genau EINE
Person erfährt, dass er fehlt. */
const ohne = stand.wege.filter((w) => w.knopf === '' && w.ziel === '' && w.art !== 'frei');
const unsichtbar = stand.wege.filter((w) => !w.sichtbar);
const satz = $('leitung-satz');
if (ohne.length || unsichtbar.length) {
const namen = [...new Set([...ohne, ...unsichtbar].map((w) => w.titel))].join(', ');
satz.textContent = `Noch ohne Adresse und deshalb für niemanden sichtbar: ${namen}. `
+ 'Drück auf „Ändern", trag die Adresse ein und setz den Haken bei '
+ '„steht für alle da".';
satz.dataset.warnung = 'ja';
} else {
satz.textContent = 'Du kannst hier jeden Weg umschreiben, ausblenden oder '
+ 'einen neuen dazunehmen. Alle anderen sehen nur, was sichtbar ist.';
delete satz.dataset.warnung;
}
/* DIE SUMME ÜBER ALLE WEGE. Eine Zahl je Karte steht unten an der
Karte; hier oben steht, ob die Seite überhaupt benutzt wird. */
const kasten = $('zahlen');
kasten.textContent = '';
const z = Object.values(stand.zahlen || {});
if (!z.length) { kasten.hidden = true; return; }
kasten.hidden = false;
const summe = (feld) => z.reduce((n, x) => n + (x[feld] || 0), 0);
for (const [wert, text] of [
[summe('tage7'), 'geöffnet in 7 Tagen'],
[summe('tage30'), 'in 30 Tagen'],
[summe('gesamt'), 'insgesamt'],
]) {
const kachel = el('div', 'u-zahl');
kachel.append(el('span', 'u-zahl__n', String(wert)));
kachel.append(el('span', 'u-zahl__text', text));
kasten.append(kachel);
}
/* KEIN NAME, NIRGENDS. Die Tabelle hat gar keine Spalte dafür —
das steht so im Schema und ist nicht nur eine Abfrage, die
jemand später ändern könnte. */
}
async function laden() {
try {
const a = await hole('/workspace/api/unterstuetzung');
if (!a.ok) { melde('Die Seite ließ sich nicht laden.'); return; }
stand = await a.json();
$('laedt')?.remove();
leitungZeichnen();
zeichnen();
} catch {
window.ladefehler?.();
}
}
$('aendern-an')?.addEventListener('click', () => {
aendern = !aendern;
$('aendern-an').textContent = aendern ? 'Fertig' : 'Ändern';
melde('');
zeichnen();
});
laden();
})();
+19 -19
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -391,22 +391,22 @@
<!-- TEXTFORM ZUERST (21.09.2026): Die Seite fragt beim Zeichnen nach
`window.Textform`. Kaeme sie spaeter, waere der erste Eintrag ohne
Gestaltung. Kein `defer`, damit sie wirklich vorher dasteht. -->
<script src="assets/js/textform.js?v=202609241329"></script>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/hochladen.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/textform.js?v=202609241413"></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/hochladen.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/zuteilung.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/zuteilung.js?v=202609241413" defer></script>
<!-- Der Vorlagenkatalog stand bis zum 23.09.2026 mitten in
aufgaben.js. Er bedient jetzt zwei Seiten und hat deshalb eine
eigene Datei; hier zeigt er den Creator-Katalog.
Vor aufgaben.js, weil die Seite ihn beim Start einrichtet. -->
<script src="assets/js/vorlagenbrett.js?v=202609241329" defer></script>
<script src="assets/js/aufgaben.js?v=202609241329" defer></script>
<script src="assets/js/vorlagenbrett.js?v=202609241413" defer></script>
<script src="assets/js/aufgaben.js?v=202609241413" defer></script>
</body>
</html>
+16 -16
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/automation.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/automation.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -168,15 +168,15 @@
</main>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/automation.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/automation.js?v=202609241413" defer></script>
</body>
</html>
+17 -17
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -197,16 +197,16 @@
</main>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/holen.js?v=202609241329" defer></script>
<script src="assets/js/befinden.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/holen.js?v=202609241413" defer></script>
<script src="assets/js/befinden.js?v=202609241413" defer></script>
</body>
</html>
+22 -22
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -304,19 +304,19 @@
<!-- TEXTFORM ZUERST (21.09.2026): Die Seite fragt beim Zeichnen nach
`window.Textform`. Kaeme sie spaeter, waere der erste Eintrag ohne
Gestaltung. Kein `defer`, damit sie wirklich vorher dasteht. -->
<script src="assets/js/textform.js?v=202609241329"></script>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/textform.js?v=202609241413"></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/checkliste.js?v=202609241329" defer></script>
<script src="assets/js/ampel.js?v=202609241329" defer></script>
<script src="assets/js/schulung.js?v=202609241329" defer></script>
<script src="assets/js/holen.js?v=202609241329" defer></script>
<script src="assets/js/bereich.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/checkliste.js?v=202609241413" defer></script>
<script src="assets/js/ampel.js?v=202609241413" defer></script>
<script src="assets/js/schulung.js?v=202609241413" defer></script>
<script src="assets/js/holen.js?v=202609241413" defer></script>
<script src="assets/js/bereich.js?v=202609241413" defer></script>
</body>
</html>
+19 -19
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- haus.css MUSS die letzte Stilvorlage sein - siehe treff-regeln.html. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -108,15 +108,15 @@
</main>
<!-- Der Fuß gehört zu jeder Seite - siehe pruef-css-klassen. -->
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/auskunft.js?v=202609241329" defer></script>
<script src="assets/js/bewerben.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/auskunft.js?v=202609241413" defer></script>
<script src="assets/js/bewerben.js?v=202609241413" defer></script>
</body>
</html>
+18 -18
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- haus.css MUSS die letzte Stilvorlage sein - siehe treff-regeln.html. -->
<link rel="stylesheet" href="assets/css/haus.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -80,16 +80,16 @@
</main>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/auskunft.js?v=202609241329" defer></script>
<script src="assets/js/bewerbungen.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/auskunft.js?v=202609241413" defer></script>
<script src="assets/js/bewerbungen.js?v=202609241413" defer></script>
</body>
</html>
+17 -17
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/calls.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/calls.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -89,15 +89,15 @@
</main>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/ki.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/calls.js?v=202609241329" defer></script>
<script src="assets/js/ki.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/calls.js?v=202609241413" defer></script>
</body>
</html>
+48 -21
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/chat.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/chat.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -70,6 +70,17 @@
<section class="chat__spalte chat__spalte--liste" aria-labelledby="liste-titel">
<div class="chat__kopf">
<h1 class="chat__titel" id="liste-titel">Gespräche</h1>
<!-- WIE VIELE WARTEN AUF MICH? (24.09.2026)
Die Zahl stand bisher nur an jeder einzelnen Zeile. Wer
morgens hereinkommt, will aber zuerst wissen, ob es
ÜBERHAUPT etwas gibt – und am Handy sieht man von der
Liste nur die ersten drei Zeilen.
Sie steht NUR da, wenn es etwas zu zählen gibt. Eine „0"
neben der Überschrift wäre jeden Tag im Weg für den einen
Tag, an dem sie etwas sagt. -->
<span class="chat__zaehler" id="liste-zaehler" hidden></span>
<!-- MEINE KACHEL (20.09.2026).
Filipe: "jeder account soll seine kachel personalisieren
koennen."
@@ -265,8 +276,24 @@
Weg für den Fall, dass irgendwann etwas darin steht. -->
<div class="chat-pins" id="pins" hidden aria-label="Angeheftet"></div>
<div class="chat__verlauf" id="verlauf" role="log" aria-live="polite" aria-relevant="additions">
<p class="chat__leer" id="verlauf-leer">
Links ein Gespräch auswählen – oder mit <strong>Neu</strong> eines anfangen.
<!-- DER LEERE RAUM RECHTS (24.09.2026).
Vorher stand hier ein einzelner grauer Satz, oben links
in einer Fläche von tausend mal siebenhundert Pixeln. Das
sah nicht ruhig aus, sondern unfertig – als hätte die
Seite nicht zu Ende geladen.
Jetzt steht er in der Mitte, mit einem Zeichen darüber.
Der Satz bleibt Wort für Wort derselbe: Er sagt, was zu
tun ist, und das war nie das Problem. -->
<p class="chat__leer chat__leer--mitte" id="verlauf-leer">
<span class="chat__leer-zeichen" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path d="M20 12a8 8 0 0 1-8 8H7l-3.2 2.4A.5.5 0 0 1 3 22v-4.6A8 8 0 1 1 20 12Z" />
<path d="M8.5 11.5h7M8.5 14.5h4" />
</svg>
</span>
<span class="chat__leer-text">Links ein Gespräch auswählen – oder mit <strong>Neu</strong> eines anfangen.</span>
</p>
</div>
@@ -592,25 +619,25 @@
</form>
</dialog>
<script src="assets/js/bereiche.js?v=202609241329"></script>
<script src="assets/js/bereiche.js?v=202609241413"></script>
<!-- TEXTFORM VOR ALLEM ANDEREN (21.09.2026): chat.js und bereich.js
fragen beim Zeichnen nach `window.Textform`. Kaeme sie spaeter,
waere die erste gezeichnete Nachricht ohne Gestaltung -- und das
faellt nur dem auf, der genau hinsieht. KEIN `defer`, damit sie
wirklich vor den anderen dasteht. -->
<script src="assets/js/textform.js?v=202609241329"></script>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/hochladen.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329"></script>
<script src="assets/js/glocke.js?v=202609241329"></script>
<script src="assets/js/textform.js?v=202609241413"></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/hochladen.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413"></script>
<script src="assets/js/glocke.js?v=202609241413"></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=202609241329"></script>
<script src="assets/js/chat.js?v=202609241329"></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/anruf.js?v=202609241413"></script>
<script src="assets/js/chat.js?v=202609241413"></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
</body>
</html>
+17 -17
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/content.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/content.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -179,15 +179,15 @@
</main>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/checkliste.js?v=202609241329" defer></script>
<script src="assets/js/content.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/checkliste.js?v=202609241413" defer></script>
<script src="assets/js/content.js?v=202609241413" defer></script>
</body>
</html>
+6 -6
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/crew-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/crew-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/crew-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/crew-haus.css?v=202609241413" />
</head>
<body class="gate gate--crew">
@@ -459,7 +459,7 @@
<script src="assets/js/gate.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/gate.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
</body>
</html>
+4 -4
View File
@@ -23,25 +23,25 @@
},
"icons": [
{
"src": "/assets/img/app-symbole/crew-192.png?v=202609241329",
"src": "/assets/img/app-symbole/crew-192.png?v=202609241413",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/img/app-symbole/crew-512.png?v=202609241329",
"src": "/assets/img/app-symbole/crew-512.png?v=202609241413",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609241329",
"src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609241413",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609241329",
"src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609241413",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
+17 -17
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/dateien.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/dateien.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -108,15 +108,15 @@
</main>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/hochladen.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/hochladen.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/dateien.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/dateien.js?v=202609241413" defer></script>
</body>
</html>
+18 -18
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -297,20 +297,20 @@
</main>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/holen.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/holen.js?v=202609241413" defer></script>
<!-- VOR entwicklung.js: Die Seite richtet den Katalog beim Start ein,
also muss es ihn zu dem Zeitpunkt geben. Mit "defer" laufen beide
in Dokumentreihenfolge. -->
<script src="assets/js/vorlagenbrett.js?v=202609241329" defer></script>
<script src="assets/js/entwicklung.js?v=202609241329" defer></script>
<script src="assets/js/vorlagenbrett.js?v=202609241413" defer></script>
<script src="assets/js/entwicklung.js?v=202609241413" defer></script>
</body>
</html>
+15 -15
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/hilfe.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/hilfe.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -146,14 +146,14 @@
</main>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/hilfe.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/hilfe.js?v=202609241413" defer></script>
</body>
</html>
+5 -5
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
</head>
<body class="gate">
@@ -427,7 +427,7 @@
<script src="assets/js/gate.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/gate.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
</body>
</html>
+17 -17
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/kalender.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/kalender.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -383,15 +383,15 @@
</main>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/kalender.js?v=202609241329" defer></script>
<script src="assets/js/kalender-abo.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/kalender.js?v=202609241413" defer></script>
<script src="assets/js/kalender-abo.js?v=202609241413" defer></script>
</body>
</html>
+16 -16
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/leistung.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/leistung.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -444,13 +444,13 @@
</form>
</dialog>
<script src="assets/js/meldung.js?v=202609241329"></script>
<script src="assets/js/nachfrage.js?v=202609241329"></script>
<script src="assets/js/wahl.js?v=202609241329"></script>
<script src="assets/js/bereiche.js?v=202609241329"></script>
<script src="assets/js/kopf.js?v=202609241329"></script>
<script src="assets/js/glocke.js?v=202609241329"></script>
<script src="assets/js/leistung.js?v=202609241329"></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413"></script>
<script src="assets/js/nachfrage.js?v=202609241413"></script>
<script src="assets/js/wahl.js?v=202609241413"></script>
<script src="assets/js/bereiche.js?v=202609241413"></script>
<script src="assets/js/kopf.js?v=202609241413"></script>
<script src="assets/js/glocke.js?v=202609241413"></script>
<script src="assets/js/leistung.js?v=202609241413"></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
</body>
</html>
+16 -16
View File
@@ -12,13 +12,13 @@
vergessen wird. -->
<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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/material.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/material.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start" data-ton="42" data-buehne="studio">
@@ -138,14 +138,14 @@
<p class="m-laedt" id="laedt">Einen Moment …</p>
</main>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/hochladen.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/material.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/hochladen.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/material.js?v=202609241413" defer></script>
</body>
</html>
+16 -16
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/personen.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/personen.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -453,14 +453,14 @@
</main>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/personen.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/personen.js?v=202609241413" defer></script>
</body>
</html>
+18 -18
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -214,16 +214,16 @@
</main>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/hochladen.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/hochladen.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/steckbrief.js?v=202609241329" defer></script>
<script src="assets/js/profil.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/steckbrief.js?v=202609241413" defer></script>
<script src="assets/js/profil.js?v=202609241413" defer></script>
</body>
</html>
+17 -17
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -134,14 +134,14 @@
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/meldung.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/holen.js?v=202609241329" defer></script>
<script src="assets/js/rechte.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/holen.js?v=202609241413" defer></script>
<script src="assets/js/rechte.js?v=202609241413" defer></script>
</body>
</html>
+18 -18
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/report.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/report.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -106,17 +106,17 @@
</main>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/ki.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/holen.js?v=202609241329" defer></script>
<script src="assets/js/report.js?v=202609241329" defer></script>
<script src="assets/js/ki.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/holen.js?v=202609241413" defer></script>
<script src="assets/js/report.js?v=202609241413" defer></script>
</body>
</html>
+16 -16
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/scouting.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/scouting.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -173,14 +173,14 @@
</div>
</div>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/scouting.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/scouting.js?v=202609241413" defer></script>
</body>
</html>
+16 -16
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/heim.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -684,16 +684,16 @@
</p>
</main>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/live.js?v=202609241329" defer></script>
<script src="assets/js/start.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/live.js?v=202609241413" defer></script>
<script src="assets/js/start.js?v=202609241413" defer></script>
</body>
</html>
+17 -17
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/startcheck.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/startcheck.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -85,16 +85,16 @@
</main>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/ki.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/startcheck.js?v=202609241329" defer></script>
<script src="assets/js/ki.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/startcheck.js?v=202609241413" defer></script>
</body>
</html>
+18 -18
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -150,16 +150,16 @@
</main>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/hochladen.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/hochladen.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/auskunft.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/steckbrief.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/auskunft.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/steckbrief.js?v=202609241413" defer></script>
</body>
</html>
+14 -14
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/support.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/support.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -123,16 +123,16 @@
(reading 'LEITUNG')" ab -- gemessen am 24.09.2026, als diese
Zeile beim Anlegen der Seite fehlte. Die Kopfleiste kam
trotzdem, deshalb sah man es nur in der Browserkonsole. -->
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<!-- wahl.js gehoert zu kopf.js: Der Sicht-Umschalter in der
Kopfleiste baut daraus seine Liste. pruef-css-klassen setzt das
seit dem 07.09.2026 durch -- damals fehlte es auf chat.html und
uebersicht.html, und heute hier. -->
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/support.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/support.js?v=202609241413" defer></script>
</body>
</html>
+18 -18
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/bewerbung.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -111,15 +111,15 @@
</main>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/holen.js?v=202609241329" defer></script>
<script src="assets/js/talente.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/holen.js?v=202609241413" defer></script>
<script src="assets/js/talente.js?v=202609241413" defer></script>
</body>
</html>
+16 -16
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/team.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/team.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -110,15 +110,15 @@
</main>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/holen.js?v=202609241329" defer></script>
<script src="assets/js/team.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/holen.js?v=202609241413" defer></script>
<script src="assets/js/team.js?v=202609241413" defer></script>
</body>
</html>
+17 -17
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/team.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/team.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<!-- `data-buehne` steht hier fest und nicht im Skript: Die Kachel zu
@@ -84,15 +84,15 @@
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/meldung.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/holen.js?v=202609241329" defer></script>
<script src="assets/js/teamlage.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/holen.js?v=202609241413" defer></script>
<script src="assets/js/teamlage.js?v=202609241413" defer></script>
</body>
</html>
+15 -15
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -112,15 +112,15 @@
</main>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/teilen.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/teilen.js?v=202609241413" defer></script>
</body>
</html>
+16 -16
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -126,14 +126,14 @@
</main>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/treff-moderation.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/treff-moderation.js?v=202609241413" defer></script>
</body>
</html>
+16 -16
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/treff.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- haus.css MUSS die letzte Stilvorlage sein. Auf der Adresse des
Rudels 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=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -248,14 +248,14 @@
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/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/auskunft.js?v=202609241329" defer></script>
<script src="assets/js/treff-regeln.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/auskunft.js?v=202609241413" defer></script>
<script src="assets/js/treff-regeln.js?v=202609241413" defer></script>
</body>
</html>
+16 -16
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<!-- Formular- und Dialogstile: seit dem 07.09.2026 steht hier der
Dialog "Creator anlegen". -->
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/uebersicht.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/uebersicht.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -149,16 +149,16 @@
</main>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/uebersicht.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/uebersicht.js?v=202609241413" defer></script>
</body>
</html>
+15 -15
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/unsere-seiten.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/unsere-seiten.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -335,14 +335,14 @@
</main>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/live.js?v=202609241329" defer></script>
<script src="assets/js/unsere-seiten.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/live.js?v=202609241413" defer></script>
<script src="assets/js/unsere-seiten.js?v=202609241413" defer></script>
</body>
</html>
+118
View File
@@ -0,0 +1,118 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>Unterstützen · Spicy &amp; Dogi</title>
<meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/unterstuetzen.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</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">Creator&nbsp;Workspace</a> · Unterstützen</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">
<!-- ==================================================================
DER KOPF SAGT ZUERST, DASS NIEMAND MUSS (24.09.2026)
Filipe: „wo die leute alle supporten können auf andere art
anstatt nur tiktok."
Eine Seite, die mit „unterstütze mich" anfängt, liest sich wie
eine hohle Hand. Der erste Satz sagt deshalb das Gegenteil: Es
gibt Wege, die nichts kosten, und keiner davon ist zweite Wahl.
Erst danach kommen die, bei denen Geld fließt.
Wer nichts geben kann, soll diese Seite nicht mit einem
schlechten Gefühl schließen — das ist die ganze Überlegung
hinter dem Aufbau.
============================================================== -->
<section class="kopf-zeile">
<div>
<p class="marke">Community</p>
<h1 class="titel" id="seiten-titel">Dogi etwas zurückgeben</h1>
<p class="unterzeile" id="seiten-unter">
Es geht auch ohne TikTok – und es geht auch ohne Geld.
Such dir aus, was zu dir passt. Niemand muss etwas geben, um dazuzugehören.
</p>
</div>
</section>
<p class="fehler" id="fehler" role="alert" aria-live="polite" hidden></p>
<!-- ==================================================================
NUR DOGFATHER: die Leiste zum Ändern und die Zahlen
„nur dogfather soll sie verändern können oder vieles mehr sehen."
Sie steht im Dokument und wird von unterstuetzen.js eingeblendet
— ausgeliefert bekommt sie jeder, das ist nur HTML. Die
ENTSCHEIDUNG trifft der Server: Er liefert `darf_aendern` und
weist jedes Schreiben ohne dieses Recht mit 404 ab. Ein
verstecktes Feld ist eine Bitte, keine Sperre.
============================================================== -->
<section class="u-leitung" id="leitung" hidden aria-labelledby="leitung-titel">
<div class="u-leitung__kopf">
<h2 class="u-leitung__titel" id="leitung-titel">Nur du siehst das hier</h2>
<button type="button" class="knopf knopf--klein" id="aendern-an">Ändern</button>
</div>
<p class="u-leitung__satz" id="leitung-satz"></p>
<div class="u-zahlen" id="zahlen" hidden></div>
</section>
<!-- Die Wege. unterstuetzen.js baut sie aus der Antwort des Servers —
hier steht nur, wo sie hingehören. -->
<div id="wege" aria-live="polite">
<p class="u-laedt" id="laedt">Wird geladen …</p>
</div>
<!-- ==================================================================
DER LETZTE SATZ IST KEIN SCHMUCK
Er steht fest im Dokument und nicht in der Datenbank: Was hier
zugesagt wird, soll nicht aus Versehen wegzuklicken sein.
============================================================== -->
<p class="u-fuss">
Nichts davon ist Pflicht, und niemand sieht, wer etwas gegeben hat.
Wer nur zuschaut, gehört genauso dazu.
</p>
</main>
<!-- bereiche.js VOR kopf.js: Dort steht `window.Bereiche`, und kopf.js
liest daraus die Leitungsrollen. Ohne sie stürzt jede Seite mit
„Cannot read properties of undefined (reading 'LEITUNG')" ab. -->
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/holen.js?v=202609241413" defer></script>
<script src="assets/js/unterstuetzen.js?v=202609241413" defer></script>
</body>
</html>
+16 -16
View File
@@ -7,15 +7,15 @@
<meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/werdegang.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/entwicklung.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/werdegang.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -144,14 +144,14 @@
</main>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/werdegang.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/werdegang.js?v=202609241413" defer></script>
</body>
</html>
+15 -15
View File
@@ -12,13 +12,13 @@
vergessen wird. -->
<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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/willkommen.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/willkommen.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<!-- KOERPERKLASSE "start", NICHT "gate" (berichtigt 21.09.2026).
`body.gate` ist die Anmeldewand: `display:flex; align-items:center`,
@@ -75,13 +75,13 @@
<p class="fehler" id="fehler" hidden></p>
</main>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/willkommen.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/willkommen.js?v=202609241413" defer></script>
</body>
</html>
+17 -17
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=202609241329" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241329" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/wissen.css?v=202609241329" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241329" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609241413" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609241413" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/start.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/wissen.css?v=202609241413" />
<link rel="stylesheet" href="assets/css/module.css?v=202609241413" />
<!-- 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=202609241329" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609241413" />
</head>
<body class="start">
@@ -190,15 +190,15 @@
</main>
<script src="assets/js/meldung.js?v=202609241329" defer></script>
<script src="assets/js/hochladen.js?v=202609241329" defer></script>
<script src="assets/js/nachfrage.js?v=202609241329" defer></script>
<script src="assets/js/meldung.js?v=202609241413" defer></script>
<script src="assets/js/hochladen.js?v=202609241413" defer></script>
<script src="assets/js/nachfrage.js?v=202609241413" defer></script>
<script src="assets/js/wahl.js?v=202609241329" defer></script>
<script src="assets/js/bereiche.js?v=202609241329" defer></script>
<script src="assets/js/installieren.js?v=202609241329" defer></script>
<script src="assets/js/kopf.js?v=202609241329" defer></script>
<script src="assets/js/glocke.js?v=202609241329" defer></script>
<script src="assets/js/wissen.js?v=202609241329" defer></script>
<script src="assets/js/wahl.js?v=202609241413" defer></script>
<script src="assets/js/bereiche.js?v=202609241413" defer></script>
<script src="assets/js/installieren.js?v=202609241413" defer></script>
<script src="assets/js/kopf.js?v=202609241413" defer></script>
<script src="assets/js/glocke.js?v=202609241413" defer></script>
<script src="assets/js/wissen.js?v=202609241413" defer></script>
</body>
</html>