Der Weg ueber die TikTok-Datei des Creators ist entfernt

Filipe, mit den drei Knoepfen im Bild: "ich will nur dass wir manager
scouts, dogfather oder spicy sachen eintragen koennen und nicht die
creator. ich will keine daten von denen kriegen, nur wir."

Gebaut am Vormittag, am Nachmittag wieder ausgebaut. Der Weg war
technisch in Ordnung -- und er war der einzige, bei dem ein CREATOR uns
etwas gibt: seine eigene Datenkopie aus der TikTok-App. Genau das soll
nicht sein. Es bleiben die beiden Wir-Wege: die eigene Datei und die
Backstage-Tabelle der Agentur.

VOLLSTAENDIG ENTFERNT, NICHT VERSTECKT:
- beide Schnittstellen (/tiktok-datei und /tiktok-datei/vorschau)
- der Leser workspace-tiktok-datei.js (263 Zeilen)
- Knopf, Fenster, Dateiauswahl, Vorschau, Stilregeln
- der fertige Text, der einen Creator um seine Daten bittet
- der Erklaersatz unter der Knopfreihe

Ein Weg, der nur unsichtbar ist, ist weiterhin ein Weg -- wer die
Adresse kennt, benutzt ihn. Und eine Bitte an einen Creator um seine
Daten soll in diesem Haus nirgends mehr stehen, auch nicht in einem
Fenster, das niemand oeffnet.

GEPRUEFT WIRD JETZT DIE ABWESENHEIT, an drei Stellen: Knopf weg,
Fenster weg, und die Schnittstelle antwortet DOGFATHER mit 404 -- dem
staerksten Zugang, den es gibt. Bekommt er 404, bekommt ihn jeder.
Daneben die Gegenprobe, dass der Backstage-Weg weiterhin mit 200
antwortet; sonst bewiese das 404 nur einen Tippfehler.

pruef-tiktok-datei.mjs (47 Pruefungen) faellt mit dem Weg weg. Die
Zahl sinkt dadurch, und das ist hier richtig: Sie pruefte etwas, das
es nicht mehr gibt. Was bleibt, sind sechs Pruefungen, die das
Fehlen sichern -- pruef-backstage-import 63 -> 66.

NEBENBEI GEDREHT, NICHT GELOESCHT: pruef-leistung-optik behauptete
noch die Regel vom 07.09. ("beim Manager fehlt die Kachel", "die Seite
weist den Creator ab"). Beide Aussagen sind jetzt umgekehrt und messen
zusaetzlich, was vorher niemand gemessen hat: dass die Creatorin auf
der Zahlen-Seite ihre EIGENEN Zahlen sieht und trotzdem keinen
einzigen Knopf zum Eintragen hat -- beides zusammen, denn "keine
Knoepfe" waere auch auf einer leeren Seite wahr. 56 -> 59.

Nicht von mir, nachgemessen gegen den Stand ohne diese Aenderungen:
pruef-struktur meldet dieselben 6 Fehler (crew-index/teamlage nicht
verlinkt, start.css 348 KB, totes CSS .nase, UTC-Datum).

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-11 15:02:21 +02:00
co-authored by Claude Opus 5
parent b34586be91
commit 14ed467928
31 changed files with 427 additions and 1262 deletions
+57 -25
View File
@@ -286,6 +286,30 @@ console.log("\n=== Gegenproben ===");
ok(status === 200, `heute als Tag geht dagegen durch (HTTP ${status})`);
}
/* DIE SCHNITTSTELLE ZUR TIKTOK-DATEI GIBT ES NICHT MEHR (11.09.2026).
Filipe: "ich will keine daten von denen kriegen, nur wir."
Ein ausgebauter Knopf ist keine Entfernung -- die Schnittstelle
waere weiterhin erreichbar, und wer die Adresse kennt, koennte
weiter eine Creator-Datei hochladen. Gemessen wird deshalb hier,
mit DogFathers Zugang, also dem staerksten, den es gibt: Bekommt
ER 404, bekommt ihn jeder.
GEGENPROBE DANEBEN: Der Backstage-Weg antwortet weiterhin. Sonst
bewiese das 404 nur, dass ich mich vertippt habe. */
for (const weg of ["/workspace/api/leistung/1/tiktok-datei",
"/workspace/api/leistung/1/tiktok-datei/vorschau"]) {
const { status } = await ruf(weg, keksDogi, { inhalt: {} });
ok(status === 404, `${weg.split("/").slice(-2).join("/")} gibt es nicht mehr (HTTP ${status})`);
}
{
const { status } = await ruf("/workspace/api/leistung/netzwerk-import/vorschau",
keksDogi, { text: TABELLE, tag: gestern });
ok(status === 200,
`Gegenprobe: der Backstage-Weg antwortet weiterhin (HTTP ${status})`);
}
/* WER SIEHT WEN -- direkt an der Schnittstelle gemessen, bevor der
Browser ins Spiel kommt. Wird der Auswahlliste auf der Seite etwas
fehlen, steht hier, ob es schon hier fehlte. */
@@ -370,21 +394,29 @@ console.log("\n=== Die Seite ===");
const satz = (id) => seite.$eval(id, (e) => (e.hidden ? "" : e.textContent))
.catch(() => "");
const netzSatz = await satz("#quelle-netz");
const ttSatz = await satz("#quelle-tt");
ok(/Backstage/.test(netzSatz) && /TikTok-Datei/.test(ttSatz),
"unter den Knöpfen steht dauerhaft, was die beiden Quellen sind");
ok(/Backstage/.test(netzSatz),
"unter den Knöpfen steht dauerhaft, woher die Zahlen kommen");
/* Und der fertige Text für den Creator -- mit den zwei Angaben, an
denen es sonst scheitert: JSON und "alle Daten". */
await seite.click("#tt-oeffnen");
await seite.waitForTimeout(300);
const fuerCreator = await seite.$eval("#tt-text", (e) => e.textContent);
ok(/JSON/.test(fuerCreator) && /Alle Daten/i.test(fuerCreator),
"der Text für den Creator nennt JSON und „Alle Daten“");
ok(/Einstellungen und Datenschutz/.test(fuerCreator) && /Deine Daten herunterladen/.test(fuerCreator),
"und den Weg in der App");
ok(/nicht gespeichert/.test(fuerCreator),
"und was mit der Datei passiert -- das gehört dem Creator gesagt");
/* ---- DER WEG UEBER DIE TIKTOK-DATEI IST WEG (11.09.2026) --------
Filipe: "ich will keine daten von denen kriegen, nur wir."
GEPRUEFT WIRD DIE ABWESENHEIT, und zwar an DREI Stellen -- sonst
hiesse "der Knopf ist weg" nur, dass er versteckt ist. Ein
versteckter Weg ist ein Weg: Wer die Adresse kennt, benutzt ihn.
Deshalb muss auch die Schnittstelle verschwunden sein.
Und der Text, der einen Creator um seine Daten bittet, darf
nirgends mehr auf der Seite stehen -- auch nicht in einem
Fenster, das niemand oeffnet. */
const spuren = await seite.evaluate(() => ({
knopf: !!document.getElementById("tt-oeffnen"),
fenster: !!document.getElementById("tt-dialog"),
text: document.body.textContent.includes("Deine Daten herunterladen"),
}));
ok(spuren.knopf === false, "der Knopf „TikTok-Datei des Creators\" ist weg");
ok(spuren.fenster === false, "und sein Fenster auch");
ok(spuren.text === false,
"und die Bitte an den Creator steht nirgends mehr auf der Seite");
/* ---- DIESELBE SEITE, ANDERE ROLLE (11.09.2026) -----------------
Filipe, mit der Kachel im Bild: "jetzt soll jede rolle diese
@@ -434,9 +466,9 @@ console.log("\n=== Die Seite ===");
return {
weg: location.pathname,
kacheln: 0,
import: h("import-oeffnen"), netz: h("netz-oeffnen"), tt: h("tt-oeffnen"),
import: h("import-oeffnen"), netz: h("netz-oeffnen"),
ziele: h("ziele-aendern"),
block: h("quellen-hinweis"), sNetz: h("quelle-netz"), sTt: h("quelle-tt"),
block: h("quellen-hinweis"), sNetz: h("quelle-netz"),
/* Wie viele Creator stehen zur Auswahl? Ein Creator darf hier
kein Auswahlfeld haben -- es haette nur ihn selbst drin. */
wahl: document.getElementById("creator-wahl")?.hidden === false
@@ -461,12 +493,12 @@ console.log("\n=== Die Seite ===");
ok(scoutBild.kachel === true && /leistung\.html$/.test(scoutBild.weg),
`der Scout findet die Kachel und kommt auf die Seite (${scoutBild.weg})`);
ok(scoutBild.fehler === "", `kein Skriptfehler beim Scout (${scoutBild.fehler || "keiner"})`);
ok(scoutBild.tt === false && scoutBild.import === false && scoutBild.ziele === false,
"der Scout darf eintragen: Datei, TikTok-Datei und Ziele sind da");
ok(scoutBild.import === false && scoutBild.ziele === false,
"der Scout darf eintragen: eigene Datei und Ziele sind da");
ok(scoutBild.netz === true,
"die Backstage-Tabelle bleibt ihm zu -- sie gilt für ALLE Creator");
ok(scoutBild.sTt === false && scoutBild.sNetz === true,
"und er liest nur die Erklärung zu dem Weg, den er auch hat");
ok(scoutBild.sNetz === true && scoutBild.block === true,
"die Backstage-Erklärung bekommt er nicht -- den Weg hat er nicht");
ok(scoutBild.aendern > 0,
`er kann einen Tag eintragen (${scoutBild.aendern} Knöpfe in der Liste)`);
@@ -486,10 +518,10 @@ console.log("\n=== Die Seite ===");
ok(b.kachel === true && /leistung\.html$/.test(b.weg),
`${rolle}: findet die Kachel und kommt auf die Seite`);
ok(b.fehler === "", `${rolle}: kein Skriptfehler (${b.fehler || "keiner"})`);
ok(b.netz === false && b.tt === false && b.import === false && b.ziele === false,
`${rolle}: alle vier Wege sind da -- auch die Backstage-Tabelle`);
ok(b.sNetz === false && b.sTt === false,
`${rolle}: und beide Erklärungen dazu`);
ok(b.netz === false && b.import === false && b.ziele === false,
`${rolle}: alle drei Wege sind da -- auch die Backstage-Tabelle`);
ok(b.sNetz === false,
`${rolle}: und die Erklärung dazu`);
ok(b.wahl >= mindestens,
`${rolle}: sieht ${b.wahl} Creator zur Auswahl (mindestens ${mindestens})`);
}
@@ -508,7 +540,7 @@ console.log("\n=== Die Seite ===");
`der Creator findet die Kachel und kommt auf die Seite (${creatorBild.weg})`);
ok(creatorBild.fehler === "", `kein Skriptfehler beim Creator (${creatorBild.fehler || "keiner"})`);
ok(creatorBild.import === true && creatorBild.netz === true
&& creatorBild.tt === true && creatorBild.ziele === true,
&& creatorBild.ziele === true,
"der Creator sieht KEINEN einzigen Eintrag-Knopf");
ok(creatorBild.aendern === 0,
`auch nicht an den einzelnen Tagen (${creatorBild.aendern} Knöpfe)`);
+56 -18
View File
@@ -167,19 +167,31 @@ const chef = await alsRolle("admin", "CODE-DOGI-0001", "start.html");
k.some((x) => x.querySelector(".kachel__name")?.textContent.trim() === "Zahlenzauber"));
ok(!erfunden, "eine erfundene Kachel wird nicht gefunden");
/* GEGENPROBE ZUR REGEL SELBST: Der Manager darf sie NICHT sehen und
NICHT hineinkommen. Ohne diese beiden Zeilen bewiese der Block
oben nur, dass DogFather etwas sieht -- nicht, dass sonst niemand
es sieht. Und genau das war die Anweisung. */
/* GEDREHT AM 11.09.2026, NICHT GELOESCHT.
Hier stand das Gegenteil: "beim Manager fehlt sie" und "die Seite
laesst ihn nicht herein". Das war richtig nach dem Wunsch vom
07.09. ("nimm die kategorie zahlen bei jedem weg"). Filipe hat ihn
am 11.09. aufgehoben: "jetzt soll jede rolle diese kachel sehen.
spicy, dogfather, manager, scout und creator."
Eine geloeschte Pruefung senkt die Zahl und beweist nichts. Eine
gedrehte haelt fest, dass die Oeffnung eine ENTSCHEIDUNG war --
und schlaegt an, wenn jemand die Sperre versehentlich wieder
einbaut.
BEIDE SCHICHTEN, wie vorher: die Kachel (der Weg) UND die Seite
(die Schranke). Nur eine von beiden zu messen, hat am 10.09. schon
einmal eine dritte, unbemerkte Schicht stehen lassen. */
const mgr = await alsRolle("manager", "CODE-MANA-0001", "start.html");
const beiIhm = await mgr.seite.$$eval(".kachel", (k) =>
k.map((x) => x.querySelector(".kachel__name")?.textContent.trim()));
ok(beiIhm.length > 3 && !beiIhm.includes("Zahlen"),
`beim Manager fehlt sie (${beiIhm.length} Kacheln, keine davon "Zahlen")`);
ok(beiIhm.length > 3 && beiIhm.includes("Zahlen"),
`der Manager sieht die Kachel wieder (${beiIhm.length} Kacheln)`);
const versuch = await mgr.seite.evaluate(async (b) =>
(await fetch(b + "/workspace/leistung.html", { redirect: "manual" })).status, BASIS);
ok(versuch !== 200, `und die Seite selbst laesst ihn nicht herein (HTTP ${versuch})`);
ok(versuch === 200, `und die Seite laesst ihn herein (HTTP ${versuch})`);
await mgr.ctx.close();
await chef.seite.goto(`${BASIS}/workspace/leistung.html`, { waitUntil: "networkidle" });
@@ -421,21 +433,47 @@ melde("\n=== Aus Datei einlesen ===");
melde("\n=== Was der Creator sieht ===");
const luna = await alsRolle("creator", "CODE-CREA-0001", "start.html");
{
/* HIER STAND DAS GEGENTEIL, UND ES WAR RICHTIG -- bis Filipe die
Regel geändert hat: "nimm die kategorie zahlen bei jedem weg, nur
die rolle dogfather soll die sehen." Vorher sah Luna ihre eigenen
Zahlen (nur ohne Eintragen-Knopf), jetzt kommt sie gar nicht mehr
hinein. Die Prüfung misst deshalb nicht mehr, WAS sie dort sieht,
sondern DASS sie nicht hineinkommt -- und dass die Schranke am
Server sitzt, nicht bloß in der Oberfläche. */
const keineKachel = await luna.seite.$$eval(".kachel", (k) =>
/* ZWEIMAL GEDREHT, UND BEIDE MALE AUF ANSAGE.
Ursprünglich sah Luna ihre eigenen Zahlen (ohne Eintragen-Knopf).
Am 07.09. kam sie gar nicht mehr hinein ("nimm die kategorie
zahlen bei jedem weg"). Seit dem 11.09. gilt wieder die erste
Fassung, und zwar wörtlich: "die creator kriegen dan quasi nur
ihre daten zu sehen und der manager scout dogfather oder spicy
können eintragen."
Gemessen wird deshalb BEIDES -- dass sie hineinkommt UND dass sie
dort nichts eintragen kann. Nur das erste zu prüfen hieße, eine
offene Tür für eine Erlaubnis zu halten. */
const kacheln = await luna.seite.$$eval(".kachel", (k) =>
k.map((x) => x.querySelector(".kachel__name")?.textContent.trim()));
ok(keineKachel.length > 2 && !keineKachel.includes("Zahlen"),
`bei Luna fehlt die Kachel "Zahlen" (${keineKachel.length} Kacheln)`);
ok(kacheln.length > 2 && kacheln.includes("Zahlen"),
`Luna sieht die Kachel "Zahlen" wieder (${kacheln.length} Kacheln)`);
const rein = await luna.seite.evaluate(async () =>
(await fetch("/workspace/leistung.html", { credentials: "same-origin" })).redirected);
ok(rein === true, "und die Seite selbst weist sie ab (Umleitung)");
ok(rein === false, "und die Seite laesst sie herein (keine Umleitung)");
/* UND DORT: ihre Zahlen, kein Knopf. Beides zusammen, sonst sagt es
nichts -- "keine Knoepfe" waere auch bei einer leeren Seite wahr. */
await luna.seite.goto(`${BASIS}/workspace/leistung.html`, { waitUntil: "networkidle" });
await luna.seite.waitForTimeout(1600);
const lage = await luna.seite.evaluate(() => {
const h = (id) => document.getElementById(id)?.hidden !== false;
return {
knoepfe: ["import-oeffnen", "netz-oeffnen", "ziele-aendern"].filter((i) => !h(i)),
tagKnoepfe: document.querySelectorAll(".tagzeile__knopf").length,
wahl: document.getElementById("creator-wahl")?.hidden === false,
inhalt: (document.getElementById("tagetabelle")?.textContent || "").length,
};
});
ok(lage.knoepfe.length === 0 && lage.tagKnoepfe === 0,
`sie kann dort nichts eintragen (${lage.knoepfe.join(", ") || "kein Knopf"}, `
+ `${lage.tagKnoepfe} Tag-Knöpfe)`);
ok(lage.wahl === false,
"und hat kein Auswahlfeld für fremde Creator -- sie sieht nur sich");
ok(lage.inhalt > 20,
`ihre eigenen Zahlen stehen aber da (${lage.inhalt} Zeichen)`);
/* DIE SCHRANKE SITZT AM SERVER, nicht in der Oberfläche. Ein
versteckter Knopf ist keine Sicherung. */
-295
View File
@@ -1,295 +0,0 @@
/* =====================================================================
DIE TIKTOK-DATEI EINES CREATORS — und warum sie ohne Beispieldatei
trotzdem prüfbar ist.
Filipe, 11.09.2026: "tiktok entwicklerkonto hab ich nicht und werd
ich nicht bekommen." Damit sind Login Kit und Datenportabilitäts-API
erledigt. Übrig bleibt der Weg, der keines braucht: Der Creator lädt
seine eigenen Daten herunter und gibt die Datei her.
---------------------------------------------------------------------
DAS PRÜFPROBLEM
Ich habe keine echte Datei. TikTok dokumentiert, DASS es einen
Go-LIVE-Verlauf gibt, nicht wie die Schlüssel heissen. Eine Prüfung
gegen EINE ausgedachte Form würde nur beweisen, dass mein Leser meine
eigene Erfindung liest.
DESHALB DREI FORMEN, absichtlich verschieden:
A englisch, flach, Dauer in Sekunden
B deutsch, verschachtelt, Dauer als "01:23:45"
C Zeitstempel als Sekundenzahl, Dauer in Minuten, andere Namen
Besteht der Leser alle drei, hat er nicht eine Form gelernt, sondern
das Prinzip: Listen mit Datum finden, Felder vorschlagen, den
Menschen bestätigen lassen. Und dazu eine vierte Datei, die KEINEN
Verlauf enthält -- sie muss abgelehnt werden, sonst hiesse "es
funktioniert" nur "es sagt immer ja".
Aufruf: node server/pruef-tiktok-datei.mjs
===================================================================== */
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { spawn } from "node:child_process";
import { DatabaseSync } from "node:sqlite";
import { scryptSync, randomBytes } from "node:crypto";
import { notbremse } from "./helfer-notbremse.mjs";
import { tagAus, minutenAus, kandidatenFinden, listeAmPfad, zuTagen } from "./workspace-tiktok-datei.js";
notbremse(240_000, "pruef-tiktok-datei");
const HIER = dirname(fileURLToPath(import.meta.url));
const PORT = 4391;
const BASIS = `http://127.0.0.1:${PORT}`;
const ordner = mkdtempSync(join(tmpdir(), "ws-ttdatei-"));
const DB = join(ordner, "workspace.db");
const jetzt = new Date().toISOString();
let gemacht = 0, fehler = 0;
const ok = (b, text) => {
gemacht++;
if (b) console.log(` ok ${text}`);
else { fehler++; console.log(` FEHL ${text}`); }
};
/* =======================================================================
TEIL 1: Die Bausteine allein -- ohne Server, ohne Browser
======================================================================= */
console.log("=== Datum lesen ===");
ok(tagAus("2026-09-10 21:14:03") === "2026-09-10", "Text mit Uhrzeit");
ok(tagAus("2026-09-10T19:14:03.000Z") === "2026-09-10", "ISO");
ok(tagAus("10.09.2026") === "2026-09-10", "deutsches Datum");
ok(tagAus(1757538843) === new Date(1757538843000).toISOString().slice(0, 10),
"Sekundenstempel");
ok(tagAus(1757538843000) === new Date(1757538843000).toISOString().slice(0, 10),
"Millisekundenstempel");
/* GEGENPROBEN: Was KEIN Datum ist, darf auch keines werden. */
ok(tagAus("Just Chatting") === null, "ein Text ist kein Datum");
ok(tagAus(42) === null, "eine kleine Zahl ist kein Datum (Jahr 1970)");
ok(tagAus("1999-01-01") === null, "vor 2016 gab es TikTok nicht");
ok(tagAus(null) === null, "und nichts ist nichts");
console.log("\n=== Dauer lesen -- die Einheit ist die Falle ===");
ok(minutenAus("01:23:45") === 83.75, `"01:23:45" sind 83,75 Minuten (${minutenAus("01:23:45")})`);
ok(minutenAus("23:45") === 23.75, `"23:45" sind 23,75 Minuten (${minutenAus("23:45")})`);
ok(minutenAus(5025, "duration_seconds") === 83.75, "5025 Sekunden sind 83,75 Minuten");
ok(minutenAus(84, "dauer_minuten") === 84, "84 Minuten bleiben 84");
ok(minutenAus(2, "hours_live") === 120, "2 Stunden sind 120 Minuten");
ok(minutenAus("2h 15m") === 135, `"2h 15m" sind 135 Minuten (${minutenAus("2h 15m")})`);
/* DIE WICHTIGSTE GEGENPROBE: Sagt der Name nichts und die Form nichts,
wird NICHTS geraten. Eine Dauer, die um Faktor 60 danebenliegt, sieht
richtig aus und ist es nicht. */
ok(minutenAus(83, "wert") === null, "eine nackte Zahl ohne Einheit ergibt null, nicht 83");
/* =======================================================================
TEIL 2: Drei Dateiformen
======================================================================= */
const FORM_A = { // englisch, flach, Sekunden
Profile: { username: "nova" },
"TikTok Live": {
"Go Live History": [
{ start_time: "2026-09-08 20:00:00", duration_seconds: 5400, total_diamonds: "1200", peak_viewers: 310 },
{ start_time: "2026-09-08 23:30:00", duration_seconds: 1800, total_diamonds: "300", peak_viewers: 120 },
{ start_time: "2026-09-09 21:00:00", duration_seconds: 7200, total_diamonds: "2450", peak_viewers: 512 },
],
},
/* DREI Nachrichten, nicht eine. Beim ersten Anlauf stand hier EINE,
und damit fiel der Verlauf unter die Mindestgrösse von zwei
Einträgen -- die Prüfung "der LIVE-Verlauf steht oben" hatte danach
nur einen Kandidaten und bewies gar nichts. Eine Rangfolge lässt
sich nur an mindestens zwei Dingen zeigen. */
"Direct Messages": {
ChatHistory: [
{ Date: "2026-09-01 10:00:00", Content: "hi" },
{ Date: "2026-09-02 11:00:00", Content: "und?" },
{ Date: "2026-09-03 12:00:00", Content: "bis dann" },
],
},
};
const FORM_B = { // deutsch, verschachtelt, hh:mm:ss
Konto: { Nutzername: "kiro" },
Aktivität: {
LIVE: {
Verlauf: [
{ Datum: "08.09.2026", Dauer: "01:30:00", Diamanten: "1.200", "Höchste Zuschauer": "310" },
{ Datum: "09.09.2026", Dauer: "02:00:00", Diamanten: "2.450", "Höchste Zuschauer": "512" },
],
},
},
};
const FORM_C = { // Stempel + Minuten, andere Namen
live_sessions: [
{ started_at: 1757361600, live_minutes: 90, gift_count: 1200, audience_peak: 310 },
{ started_at: 1757448000, live_minutes: 120, gift_count: 2450, audience_peak: 512 },
],
};
const FORM_LEER = { // kein Verlauf
Profile: { username: "lumi" },
Settings: { language: "de", theme: "dark" },
};
console.log("\n=== Drei verschiedene Dateiformen -- und eine ohne Verlauf ===");
for (const [name, datei, erwarteteTage] of [["A englisch/Sekunden", FORM_A, 2],
["B deutsch/hh:mm:ss", FORM_B, 2], ["C Stempel/Minuten", FORM_C, 2]]) {
const k = kandidatenFinden(datei);
ok(k.length >= 1, `${name}: eine Liste gefunden (${k.length})`);
const beste = k[0];
const liste = listeAmPfad(datei, beste.pfad);
ok(Array.isArray(liste) && liste.length >= 2,
`${name}: die Liste ist wieder auffindbar (${liste?.length} Einträge)`);
const { tage } = zuTagen(liste, beste.vorschlag);
ok(tage.length === erwarteteTage,
`${name}: ${tage.length} Tage (erwartet ${erwarteteTage})`);
/* Form A hat ZWEI Lives am 8. September -- sie müssen zu EINEM Tag
mit 120 Minuten zusammenfallen. Das ist der Fall, an dem sich
zeigt, ob addiert statt überschrieben wird. */
if (name.startsWith("A")) {
const achter = tage.find((t) => t.tag === "2026-09-08");
ok(achter?.lives === 2, `A: der 8. hat zwei Lives (${achter?.lives})`);
ok(achter?.dauer_min === 120, `A: und zusammen 120 Minuten (${achter?.dauer_min})`);
ok(achter?.diamanten === 1500, `A: und 1500 Diamanten (${achter?.diamanten})`);
ok(achter?.zuschauer_max === 310, `A: Spitze ist die höhere der beiden (${achter?.zuschauer_max})`);
}
if (name.startsWith("B")) {
const t = tage.find((x) => x.tag === "2026-09-09");
ok(t?.dauer_min === 120, `B: "02:00:00" wurde zu 120 Minuten (${t?.dauer_min})`);
ok(t?.diamanten === 2450, `B: "2.450" wurde zu 2450 (${t?.diamanten})`);
}
}
{
const k = kandidatenFinden(FORM_LEER);
ok(k.length === 0, `eine Datei ohne Verlauf ergibt keine Kandidaten (${k.length})`);
}
{
/* UND DER VERLAUF MUSS OBEN STEHEN. Form A enthält auch einen
Nachrichtenverlauf mit Datum -- der ist ebenfalls ein gültiger
Kandidat, darf aber nicht der erste Vorschlag sein. */
const k = kandidatenFinden(FORM_A);
ok(k.length >= 2, `A: auch die Nachrichten werden als Liste erkannt (${k.length} Kandidaten)`);
ok(/live/i.test(k[0].pfad), `A: vorgeschlagen wird aber der LIVE-Verlauf ("${k[0].pfad}")`);
}
/* =======================================================================
TEIL 3: Der ganze Weg über den Server
======================================================================= */
let kind = null;
async function starten() {
kind = spawn(process.execPath, [join(HIER, "index.js")], {
env: { ...process.env, WORKSPACE_DB: DB, PORT: String(PORT),
SITE_ACCESS_SECRET: "lokaler-test",
SITE_PUBLIC_LAUNCH_AT: "2020-01-01T00:00:00+01:00" },
stdio: ["ignore", "pipe", "pipe"] });
let aus = "";
kind.stdout.on("data", (d) => { aus += d; });
kind.stderr.on("data", (d) => { aus += d; });
for (let i = 0; i < 80; i++) {
await new Promise((r) => setTimeout(r, 250));
if (kind.exitCode !== null) break;
try { const a = await fetch(BASIS + "/workspace/", { redirect: "manual" });
if (a.status < 500) return; } catch { /* noch nicht oben */ }
}
throw new Error("Server startete nicht:\n" + aus.slice(-1500));
}
await starten();
kind.kill();
await new Promise((r) => setTimeout(r, 600));
let idNova;
{
const d = new DatabaseSync(DB);
const anlegen = (name, rolle, code) => {
const salz = randomBytes(16).toString("hex");
const hash = scryptSync(code, salz, 64,
{ N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, aktiv, erstellt) VALUES (?,?,?,?,?,1,?)")
.run(name, rolle, hash, salz, 32768, jetzt);
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
};
anlegen("Filipe", "admin", "CODE-DOGI-0001");
idNova = anlegen("Nova", "creator", "CODE-NOVA-0001");
d.close();
}
await starten();
const anmelden = async (rolle, code) => {
const a = await fetch(BASIS + "/workspace/api/anmelden", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ rolle, code }) });
const keks = (a.headers.getSetCookie?.() || []).map((k) => k.split(";")[0]).join("; ");
if (!keks) throw new Error(`Anmeldung ${rolle} fehlgeschlagen (${a.status})`);
return keks;
};
const keksDogi = await anmelden("admin", "CODE-DOGI-0001");
const keksNova = await anmelden("creator", "CODE-NOVA-0001");
const ruf = async (weg, keks, koerper) => {
const a = await fetch(BASIS + weg, { method: "POST",
headers: { Cookie: keks, "Content-Type": "application/json", Origin: BASIS },
body: JSON.stringify(koerper) });
let daten = null; try { daten = await a.json(); } catch { /* leer erlaubt */ }
return { status: a.status, daten };
};
console.log("\n=== Über den Server ===");
let vorschlag = null;
{
const { status, daten } = await ruf(`/workspace/api/leistung/${idNova}/tiktok-datei/vorschau`,
keksDogi, { inhalt: FORM_A });
ok(status === 200, `die Vorschau liest die Datei (HTTP ${status})`);
ok(daten?.kandidaten?.length >= 1, `${daten?.kandidaten?.length} Kandidaten`);
vorschlag = daten?.kandidaten?.[0];
ok(vorschlag?.tage_gesamt === 2, `der beste ergäbe 2 Tage (${vorschlag?.tage_gesamt})`);
ok(!!vorschlag?.vorschlag?.dauer, `mit erkannter Dauerspalte (${vorschlag?.vorschlag?.dauer})`);
}
{
const { status, daten } = await ruf(`/workspace/api/leistung/${idNova}/tiktok-datei`,
keksDogi, { inhalt: FORM_A, pfad: vorschlag.pfad, zuordnung: vorschlag.vorschlag });
ok(status === 200, `die Übernahme läuft (HTTP ${status})`);
ok(daten?.geschrieben === 2, `zwei Tage geschrieben (${daten?.geschrieben})`);
const d = new DatabaseSync(DB);
const z = d.prepare("SELECT tag, dauer_min, diamanten, gueltiger_tag, quelle FROM leistung WHERE creator_id = ? ORDER BY tag").all(idNova);
ok(z.length === 2, `zwei Zeilen in der Datenbank (${z.length})`);
ok(z[0].dauer_min === 120 && z[0].diamanten === 1500,
`der 8. trägt beide Lives zusammen (${z[0].dauer_min} Min, ${z[0].diamanten} Dia)`);
ok(z.every((x) => x.gueltiger_tag === 1), "beide als gültiger Tag gezählt");
ok(z.every((x) => x.quelle === "import"), "Quelle 'import'");
d.close();
}
{
/* DIE ENTSCHEIDENDE REGEL: Eine vorhandene Zahl wird NICHT
überschrieben. Die Datei kommt vom Creator und ist die zweite
Quelle -- Backstage und die Hand gelten mehr. */
const d = new DatabaseSync(DB);
d.prepare("UPDATE leistung SET diamanten = 9999 WHERE creator_id = ? AND tag = '2026-09-08'").run(idNova);
d.close();
await ruf(`/workspace/api/leistung/${idNova}/tiktok-datei`, keksDogi,
{ inhalt: FORM_A, pfad: vorschlag.pfad, zuordnung: vorschlag.vorschlag });
const d2 = new DatabaseSync(DB);
const z = d2.prepare("SELECT diamanten FROM leistung WHERE creator_id = ? AND tag = '2026-09-08'").get(idNova);
ok(z?.diamanten === 9999, `eine vorhandene Zahl bleibt stehen (${z?.diamanten})`);
d2.close();
}
{
const { status, daten } = await ruf(`/workspace/api/leistung/${idNova}/tiktok-datei/vorschau`,
keksDogi, { inhalt: FORM_LEER });
ok(status === 400 && /JSON|Liste/i.test(daten?.fehler || ""),
`eine Datei ohne Verlauf wird abgelehnt ("${(daten?.fehler || "").slice(0, 50)}…")`);
}
{
const { status } = await ruf(`/workspace/api/leistung/${idNova}/tiktok-datei`,
keksNova, { inhalt: FORM_A, pfad: vorschlag.pfad, zuordnung: vorschlag.vorschlag });
ok(status === 403,
`ein Creator kann sich seine Zahlen nicht selbst eintragen (HTTP ${status})`);
}
kind.kill();
await new Promise((r) => setTimeout(r, 400));
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Zugabe */ }
console.log(`\n${gemacht} Pruefungen`);
console.log(fehler === 0 ? "ALLES IN ORDNUNG" : `${fehler} FEHLER`);
process.exit(fehler ? 1 : 0);
+17 -127
View File
@@ -47,7 +47,6 @@ import {
db, protokolliere, echteIp, sitzungLesen, istLeitung,
sichtbareCreatorIds, ROLLEN_SORTIERUNG, heuteLokal,
} from "./workspace.js";
import { kandidatenFinden, listeAmPfad, zuTagen } from "./workspace-tiktok-datei.js";
export const leistungRouter = express.Router();
@@ -870,137 +869,28 @@ leistungRouter.post("/workspace/api/leistung/netzwerk-import", gleicheHerkunft,
});
/* =====================================================================
DIE EIGENE TIKTOK-DATEI EINES CREATORS (11.09.2026)
DER WEG UEBER DIE TIKTOK-DATEI IST ENTFERNT (11.09.2026)
Der letzte verbliebene Weg aus dem Plan vom 10.09.: Login Kit und
Datenportabilitäts-API hängen beide an einem TikTok-Entwicklerkonto,
und das wird es nicht geben ("hab ich nicht und werd ich nicht
bekommen"). Dieser hier braucht keines -- der Creator lädt seine
eigenen Daten bei TikTok herunter und gibt die Datei her.
Gebaut am 11.09. vormittags, am selben Tag wieder ausgebaut. Filipe,
mit den drei Knoepfen im Bild: *"ich will nur dass wir manager
scouts, dogfather oder spicy sachen eintragen koennen und nicht die
creator. ich will keine daten von denen kriegen, nur wir."*
Das Suchen und Umrechnen steht in workspace-tiktok-datei.js; hier
stehen nur die beiden Wege und die Rechte.
Der Weg war technisch einwandfrei -- und er war der einzige, bei dem
ein CREATOR uns etwas gibt: seine eigene Datenkopie aus der
TikTok-App. Genau das soll nicht sein. Es bleiben die beiden
Wir-Wege: die eigene Datei und die Backstage-Tabelle der Agentur.
DIE DATEI WIRD NICHT GESPEICHERT. Sie wird gelesen, ausgewertet und
fällt danach weg. In ihr stehen auch Direktnachrichten und der
Ansehverlauf -- das ist nichts, was auf einem Server liegen soll,
der es nicht braucht.
VOLLSTAENDIG ENTFERNT, nicht nur versteckt: Schnittstellen, Leser
(workspace-tiktok-datei.js), Knopf, Fenster und der fertige Text an
den Creator. Ein Weg, der nur unsichtbar ist, ist weiterhin da --
wer die Adresse kennt, benutzt ihn. Und ein Text, der einen Creator
um seine Daten bittet, soll in diesem Haus nirgends mehr stehen.
WAS WIR DAMIT NICHT MEHR WISSEN: nichts, was Backstage nicht auch
sagt. Die Datei war die zweite Quelle (COALESCE), nie die erste.
===================================================================== */
leistungRouter.post("/workspace/api/leistung/:creatorId/tiktok-datei/vorschau",
gleicheHerkunft, express.json({ limit: "60mb" }), (req, res) => {
try {
const creatorId = Number(req.params.creatorId);
if (!Number.isInteger(creatorId)) return res.status(400).json({ fehler: "ungueltig" });
if (!darfEintragen(req.person)) return res.status(403).json({ fehler: "nicht_erlaubt" });
if (!darfSehen(req.person, creatorId)) return res.status(404).json({ fehler: "nicht_gefunden" });
const inhalt = req.body?.inhalt;
if (!inhalt || typeof inhalt !== "object") {
return res.status(400).json({ fehler: "Das ist keine gelesene JSON-Datei." });
}
const kandidaten = kandidatenFinden(inhalt);
if (!kandidaten.length) {
return res.status(400).json({
fehler: "In dieser Datei steht keine Liste mit Datumsangaben. "
+ "Wurde beim Herunterladen wirklich JSON gewählt (nicht TXT) und "
+ "„Alles auswählen“ angehakt?",
});
}
/* Zu jedem Vorschlag gleich zeigen, was dabei herauskäme -- sonst
muss der Mensch aus Schlüsselnamen raten, welcher der richtige
ist. */
const mitVorschau = kandidaten.map((k) => {
const { tage } = zuTagen(k.beispiele.length ? listeAmPfad(inhalt, k.pfad) || [] : [], k.vorschlag);
return {
...k,
tage_gesamt: tage.length,
tage_beispiel: tage.slice(-3),
/* Die Beispielzeilen gekürzt: In einem Vollarchiv hängt an
einem Eintrag manchmal ein ganzer Kommentartext. */
beispiele: k.beispiele.map((b) => {
const klein = {};
for (const [s, w] of Object.entries(b)) klein[s] = String(w ?? "").slice(0, 80);
return klein;
}),
};
});
res.json({ kandidaten: mitVorschau });
} catch (fehler) {
console.error("[leistung] TikTok-Datei Vorschau:", fehler?.message);
res.status(503).json({ fehler: "nicht_verfuegbar" });
}
});
leistungRouter.post("/workspace/api/leistung/:creatorId/tiktok-datei",
gleicheHerkunft, express.json({ limit: "60mb" }), (req, res) => {
try {
const creatorId = Number(req.params.creatorId);
if (!Number.isInteger(creatorId)) return res.status(400).json({ fehler: "ungueltig" });
if (!darfEintragen(req.person)) return res.status(403).json({ fehler: "nicht_erlaubt" });
if (!darfSehen(req.person, creatorId)) return res.status(404).json({ fehler: "nicht_gefunden" });
const inhalt = req.body?.inhalt;
const pfad = String(req.body?.pfad || "");
const zuordnung = req.body?.zuordnung;
if (!inhalt || !pfad || !zuordnung?.tag) {
return res.status(400).json({ fehler: "Es fehlt die Auswahl, was gelesen werden soll." });
}
const liste = listeAmPfad(inhalt, pfad);
if (!liste) return res.status(400).json({ fehler: "Diese Liste gibt es in der Datei nicht." });
const { tage, uebersprungen } = zuTagen(liste, zuordnung);
const heute = heuteLokal();
const brauchbar = tage.filter((t) => t.tag <= heute
/* Ein Tag ohne eine einzige Zahl ist keine Messung. Dieselbe
Regel wie beim Backstage-Import. */
&& (t.dauer_min !== null || t.diamanten !== null || t.zuschauer_max !== null));
if (!brauchbar.length) return res.status(400).json({ fehler: "keine_brauchbaren_tage" });
const d = db();
const schreiben = d.prepare(`
INSERT INTO leistung
(creator_id, tag, diamanten, dauer_min, gueltiger_tag, zuschauer_max,
erfasst, erfasst_von, quelle)
VALUES (?,?,?,?,?,?,?,?,'import')
ON CONFLICT(creator_id, tag) DO UPDATE SET
/* NUR FÜLLEN, NICHT ÜBERSCHREIBEN.
Anders als beim Backstage-Import, der die Wahrheit der
Agentur bringt: Diese Datei kommt vom Creator und ist eine
ZWEITE Quelle. Wo schon eine Zahl steht -- von Hand
eingetragen oder aus Backstage --, bleibt sie stehen.
Andersherum würde ein Dateiupload stillschweigend die
offiziellen Zahlen ersetzen, und niemand wüsste hinterher,
welche gilt. */
diamanten = COALESCE(leistung.diamanten, excluded.diamanten),
dauer_min = COALESCE(leistung.dauer_min, excluded.dauer_min),
zuschauer_max = COALESCE(leistung.zuschauer_max, excluded.zuschauer_max),
gueltiger_tag = MAX(leistung.gueltiger_tag, excluded.gueltiger_tag)`);
let geschrieben = 0, ergaenzt = 0;
const vorhanden = d.prepare("SELECT 1 FROM leistung WHERE creator_id = ? AND tag = ?");
d.exec("BEGIN");
try {
for (const t of brauchbar) {
const gabEs = !!vorhanden.get(creatorId, t.tag);
schreiben.run(creatorId, t.tag, t.diamanten, t.dauer_min,
(t.dauer_min ?? 0) > 0 ? 1 : 0, t.zuschauer_max,
new Date().toISOString(), req.person.id);
if (gabEs) ergaenzt++; else geschrieben++;
}
d.exec("COMMIT");
} catch (f) { d.exec("ROLLBACK"); throw f; }
protokolliere("leistung_tiktok_datei", {
personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req),
detail: `Creator #${creatorId}: ${geschrieben} neu, ${ergaenzt} ergaenzt`.slice(0, 120),
});
res.json({ geschrieben, ergaenzt, uebersprungen: uebersprungen.length });
} catch (fehler) {
console.error("[leistung] TikTok-Datei:", fehler?.message);
res.status(503).json({ fehler: "nicht_verfuegbar" });
}
});
/** Vorschau: Was WÜRDE passieren? Schreibt nichts. */
leistungRouter.post("/workspace/api/leistung/:creatorId/import/vorschau", gleicheHerkunft,
-263
View File
@@ -1,263 +0,0 @@
/* =====================================================================
workspace-tiktok-datei.js — die eigenen TikTok-Daten eines Creators.
Filipe, 10.09.2026: "wie sehr wir diese seite hier mit dem backstage
verbinden könnten von tiktok. ohne probleme und ganz schnell."
Am 11.09.2026: "tiktok entwicklerkonto hab ich nicht und werd ich
nicht bekommen."
---------------------------------------------------------------------
WARUM ES DIESEN WEG ÜBERHAUPT GIBT
Die Recherche vom 10.09. hat drei Wege ergeben. Zwei davon sind seit
dem 11.09. erledigt: Login Kit und die Datenportabilitäts-API hängen
beide an einem Entwicklerkonto bei TikTok, und das wird es nicht
geben.
Der dritte braucht keines. Jeder TikTok-Nutzer kann seine eigenen
Daten selbst herunterladen -- Profil → Einstellungen → Konto → Deine
Daten herunterladen, Format JSON. Die Datei enthält laut TikToks
eigener Datentypen-Liste einen Abschnitt "TikTok LIVE" mit dem
Go-LIVE-Verlauf. Der Creator lädt sie hier hoch, und zwar mit seinen
eigenen Händen -- legitimer wird es nicht.
---------------------------------------------------------------------
DAS EIGENTLICHE PROBLEM: ICH KENNE DAS FORMAT NICHT
Ich habe keine echte Datei gesehen. TikTok dokumentiert, DASS es
"Go LIVE History" gibt, nicht WIE die Schlüssel heissen. Sie können
deutsch oder englisch sein, verschachtelt oder flach, und sie können
sich ändern, ohne dass jemand Bescheid sagt.
DER NAHELIEGENDE WEG WÄRE, Schlüsselnamen zu raten und einen Leser
darauf zu bauen. Das wäre die schlechteste Lösung: Er würde bei der
ersten echten Datei auseinanderfallen, und zwar STILL -- "0 Einträge
gefunden" sieht aus wie "der Creator war nicht live".
DESHALB WIRD NICHT GERATEN, SONDERN GESUCHT. Der Leser geht die
Datei durch, sammelt alle Listen von Einträgen, die ein Datum
enthalten, und legt sie zur Auswahl vor -- mit Anzahl, Zeitraum und
Beispielzeile. Der Mensch bestätigt, was davon der LIVE-Verlauf ist.
Damit ist der Leser unabhängig davon, wie die Felder heissen. Er
schlägt etwas vor (nach Namensmustern), aber er entscheidet nicht.
Und wenn TikTok das Format ändert, ändert sich nur der Vorschlag,
nicht die Brauchbarkeit.
---------------------------------------------------------------------
WAS BEWUSST NICHT GELESEN WIRD
Das Vollarchiv enthält auch "Income and Wallet" -- Transaktionen und
Auszahlungen. Das bleibt liegen. Diamanten sind Leistung,
Auszahlungen sind Gehalt, und diese Seite ist eine Arbeitslage, keine
Gehaltsakte (dieselbe Linie wie bei der Team-Lage).
Ebenso liegen bleiben: Direktnachrichten, Suchverlauf, Ansehverlauf.
Sie stehen in derselben Datei. Der Leser rührt sie nicht an, und die
Datei selbst wird NICHT gespeichert -- gelesen, ausgewertet,
verworfen.
===================================================================== */
/** Wie tief darf gesucht werden? Ein Vollarchiv ist verschachtelt, aber
* nicht beliebig tief. Die Grenze verhindert, dass eine kaputte Datei
* den Server beschäftigt. */
const MAX_TIEFE = 8;
/** Mehr als so viele Listen werden nicht zur Auswahl gestellt -- wer
* fünfzig Vorschläge bekommt, liest keinen. */
const MAX_KANDIDATEN = 8;
/** Ein Datum aus einem beliebigen Wert. Gibt "JJJJ-MM-TT" oder null.
*
* TikTok schreibt Zeitpunkte üblicherweise als "2026-09-10 21:14:03"
* oder als ISO-Zeichenkette; Sekundenstempel kommen ebenfalls vor.
* Alle drei werden erkannt -- und alles andere ausdrücklich nicht. */
export function tagAus(wert) {
if (wert === null || wert === undefined) return null;
if (typeof wert === "number") {
/* Sekunden oder Millisekunden? Ein Stempel in Sekunden liegt 2026
bei rund 1,8 Milliarden, in Millisekunden bei 1,8 Billionen.
Die Grenze dazwischen ist eindeutig. */
const ms = wert > 1e11 ? wert : wert * 1000;
const d = new Date(ms);
if (Number.isNaN(d.getTime())) return null;
const j = d.getFullYear();
if (j < 2016 || j > 2100) return null; // TikTok gibt es seit 2016
return d.toISOString().slice(0, 10);
}
const t = String(wert).trim();
const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(t);
if (m) {
const j = Number(m[1]);
if (j < 2016 || j > 2100) return null;
return `${m[1]}-${m[2]}-${m[3]}`;
}
const d = /^(\d{1,2})\.(\d{1,2})\.(\d{4})/.exec(t);
if (d) return `${d[3]}-${String(d[2]).padStart(2, "0")}-${String(d[1]).padStart(2, "0")}`;
return null;
}
/** Eine Dauer in MINUTEN aus einem beliebigen Wert.
*
* DIE EINHEIT IST DIE FALLE. "83" kann 83 Sekunden, 83 Minuten oder
* 83 Stunden heissen. Geraten wird deshalb NICHT aus der Zahl, sondern
* aus dem SCHLÜSSELNAMEN -- und wo der nichts sagt, aus der Form des
* Wertes ("01:23:45" ist eindeutig).
*
* Bleibt beides stumm, wird null zurückgegeben statt einer plausiblen
* Zahl. Eine Dauer, die um den Faktor 60 danebenliegt, sieht richtig
* aus und ist es nicht. */
export function minutenAus(wert, schluessel = "") {
if (wert === null || wert === undefined || wert === "") return null;
const name = String(schluessel).toLowerCase();
const t = String(wert).trim();
/* "01:23:45" oder "23:45" -- eindeutig, unabhängig vom Namen. */
const uhr = /^(\d{1,3}):(\d{2})(?::(\d{2}))?$/.exec(t);
if (uhr) {
return uhr[3] !== undefined
? Number(uhr[1]) * 60 + Number(uhr[2]) + Number(uhr[3]) / 60
: Number(uhr[1]) + Number(uhr[2]) / 60;
}
/* "2h 15m", "2 Std 15 Min" */
const hm = /(\d+)\s*(?:h|std|stunde)/i.exec(t);
const mm = /(\d+)\s*(?:m(?!s)|min)/i.exec(t);
if (hm || mm) return (hm ? Number(hm[1]) * 60 : 0) + (mm ? Number(mm[1]) : 0);
const zahl = Number(String(wert).replace(/[^\d.]/g, ""));
if (!Number.isFinite(zahl)) return null;
if (/millisec|_ms\b|\bms\b/.test(name)) return zahl / 60000;
if (/sekund|second|_s\b|\bsec/.test(name)) return zahl / 60;
if (/minut|minute|_min/.test(name)) return zahl;
if (/stunde|hour/.test(name)) return zahl * 60;
return null; // lieber nichts als falsch
}
/** Wie gut passt dieser Schlüsselname zu dem, was wir suchen? */
const MUSTER = {
tag: /date|datum|zeit|time|start|begin/i,
dauer: /duration|dauer|laenge|length|minute|second|sekund|stunde|hour|watch|live.?time|time.?live/i,
diamanten: /diamond|diamant|coin|gift|geschenk|earn|einnahm/i,
zuschauer: /viewer|zuschauer|audience|watcher/i,
};
/** Findet in einem beliebigen Objekt alle Listen, die ein Datum tragen.
*
* Rückgabe je Fund: Pfad, Anzahl, Schlüssel, Vorschlag für die
* Zuordnung, Zeitraum und zwei Beispielzeilen. */
export function kandidatenFinden(wurzel) {
const gefunden = [];
const gehe = (knoten, pfad, tiefe) => {
if (tiefe > MAX_TIEFE || knoten === null || typeof knoten !== "object") return;
if (Array.isArray(knoten)) {
const objekte = knoten.filter((e) => e && typeof e === "object" && !Array.isArray(e));
/* Eine Liste mit weniger als zwei Einträgen ist kein Verlauf.
(Ein einziger LIVE wäre einer -- aber dann lohnt der ganze
Weg nicht, und die Auswahl bliebe voller Rauschen.) */
if (objekte.length >= 2) {
const schluessel = [...new Set(objekte.flatMap((e) => Object.keys(e)))];
/* Welcher Schlüssel trägt ein Datum? Nicht nach dem Namen
gesucht, sondern nach dem INHALT -- ein Feld namens "date",
in dem kein Datum steht, hilft niemandem. */
let tagSchluessel = null;
let beste = 0;
for (const s of schluessel) {
const treffer = objekte.filter((e) => tagAus(e[s])).length;
/* Mindestens die Hälfte der Einträge muss ein lesbares Datum
haben -- sonst ist es ein Feld, das zufällig manchmal wie
eines aussieht. */
if (treffer > beste && treffer >= objekte.length / 2) {
beste = treffer; tagSchluessel = s;
}
}
if (tagSchluessel) {
const passt = (art) => schluessel.find((s) => MUSTER[art].test(s) && s !== tagSchluessel) || null;
const tage = objekte.map((e) => tagAus(e[tagSchluessel])).filter(Boolean).sort();
gefunden.push({
pfad, anzahl: objekte.length, schluessel,
vorschlag: {
tag: tagSchluessel,
dauer: passt("dauer"),
diamanten: passt("diamanten"),
zuschauer: passt("zuschauer"),
},
von: tage[0] || null, bis: tage[tage.length - 1] || null,
/* LIVE IM PFAD IST EIN STARKES ZEICHEN, aber kein Beweis --
deshalb nur ein Punkt bei der Sortierung, keine
Vorentscheidung. */
punkte: (/live/i.test(pfad) ? 10 : 0)
+ (passt("dauer") ? 4 : 0) + (passt("diamanten") ? 3 : 0)
+ Math.min(3, Math.floor(objekte.length / 10)),
beispiele: objekte.slice(0, 2),
});
}
}
/* Auch IN der Liste weitersuchen -- aber nur im ersten Eintrag.
Alle durchzugehen kostet bei einem Vollarchiv Minuten und
bringt nichts Neues: Die Einträge einer Liste sind gleich
gebaut. */
if (knoten[0] && typeof knoten[0] === "object") gehe(knoten[0], `${pfad}[0]`, tiefe + 1);
return;
}
for (const [s, w] of Object.entries(knoten)) {
gehe(w, pfad ? `${pfad}.${s}` : s, tiefe + 1);
}
};
gehe(wurzel, "", 0);
gefunden.sort((a, b) => b.punkte - a.punkte || b.anzahl - a.anzahl);
return gefunden.slice(0, MAX_KANDIDATEN);
}
/** Holt die Liste an einem Pfad wieder heraus. */
export function listeAmPfad(wurzel, pfad) {
let hier = wurzel;
for (const teil of String(pfad).split(".")) {
if (hier === null || typeof hier !== "object") return null;
const m = /^(.*)\[(\d+)\]$/.exec(teil);
if (m) {
hier = m[1] ? hier[m[1]] : hier;
if (!Array.isArray(hier)) return null;
hier = hier[Number(m[2])];
} else {
hier = hier[teil];
}
}
return Array.isArray(hier) ? hier : null;
}
/** Aus den Einträgen werden TAGE.
*
* MEHRERE LIVES AN EINEM TAG SIND EIN TAG. Die Dauer wird addiert,
* die Diamanten ebenfalls, bei den Zuschauern gewinnt der höchste
* Wert -- ein Durchschnitt aus zwei Streams wäre eine Zahl, die es
* nie gegeben hat.
*
* Ein Tag mit LIVE-Zeit gilt als gültiger Tag. Das ist dieselbe Regel
* wie beim Backstage-Import; sie steht bewusst an beiden Stellen
* gleich und nicht einmal so und einmal anders. */
export function zuTagen(eintraege, zuordnung) {
const nachTag = new Map();
const uebersprungen = [];
eintraege.forEach((e, i) => {
const tag = tagAus(e[zuordnung.tag]);
if (!tag) { uebersprungen.push({ nr: i + 1, grund: "kein lesbares Datum" }); return; }
const minuten = zuordnung.dauer ? minutenAus(e[zuordnung.dauer], zuordnung.dauer) : null;
const diamanten = zuordnung.diamanten ? Number(String(e[zuordnung.diamanten]).replace(/[^\d]/g, "")) : null;
const zuschauer = zuordnung.zuschauer ? Number(String(e[zuordnung.zuschauer]).replace(/[^\d]/g, "")) : null;
const v = nachTag.get(tag) || { tag, dauer_min: null, diamanten: null, zuschauer_max: null, lives: 0 };
v.lives++;
if (minuten !== null && Number.isFinite(minuten)) v.dauer_min = (v.dauer_min ?? 0) + minuten;
if (Number.isFinite(diamanten)) v.diamanten = (v.diamanten ?? 0) + diamanten;
if (Number.isFinite(zuschauer)) v.zuschauer_max = Math.max(v.zuschauer_max ?? 0, zuschauer);
nachTag.set(tag, v);
});
const tage = [...nachTag.values()]
.map((v) => ({ ...v, dauer_min: v.dauer_min === null ? null : Math.round(v.dauer_min) }))
.sort((a, b) => a.tag.localeCompare(b.tag));
return { tage, uebersprungen };
}
+4 -4
View File
@@ -13,25 +13,25 @@
"theme_color": "#06090f",
"icons": [
{
"src": "/assets/img/app-symbole/workspace-192.png?v=202609111404",
"src": "/assets/img/app-symbole/workspace-192.png?v=202609111501",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/img/app-symbole/workspace-512.png?v=202609111404",
"src": "/assets/img/app-symbole/workspace-512.png?v=202609111501",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609111404",
"src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609111501",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609111404",
"src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609111501",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
-45
View File
@@ -317,38 +317,6 @@
color: var(--text-still); font-size: .8rem;
}
/* ---- Die Auswahl in der TikTok-Datei (11.09.2026) -------------------
Das Format der Datei ist nicht dokumentiert. Der Server schlägt vor,
der Mensch entscheidet -- und eine Entscheidung gehört als sichtbare
Auswahl auf die Seite, nicht in ein Aufklappfeld, in dem man die
erste Zeile für gesetzt hält. */
.tt-wahl {
display: flex; align-items: flex-start; gap: 10px;
margin: 6px 0;
padding: 9px 11px;
border: 1px solid var(--rand); border-radius: 11px;
background: rgba(255, 255, 255, .02);
cursor: pointer;
}
.tt-wahl:hover { border-color: var(--rand-hell); }
.tt-wahl:has(input:checked) {
border-color: color-mix(in srgb, var(--akzent) 55%, transparent);
background: color-mix(in srgb, var(--akzent) 10%, transparent);
}
.tt-wahl input { margin-top: 3px; flex: none; }
.tt-wahl__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.tt-wahl__text strong {
font-family: ui-monospace, "Cascadia Code", "Consolas", monospace;
font-size: .82rem; color: var(--text);
overflow-wrap: anywhere;
}
.tt-wahl__zahl { font-size: .8rem; color: var(--text-leise); }
.tt-wahl__felder {
font-size: .76rem; color: var(--text-still);
font-family: ui-monospace, "Cascadia Code", "Consolas", monospace;
overflow-wrap: anywhere;
}
/* =====================================================================
DIE ANLEITUNGEN (11.09.2026)
@@ -387,17 +355,4 @@
.anleitung li::marker { color: var(--akzent); font-weight: 700; }
.anleitung strong { color: var(--text); }
/* Der fertige Text zum Weiterschicken. Als <pre>, damit die Nummerierung
stehen bleibt -- und mit Umbruch, damit er nicht seitlich herausläuft. */
.anleitung-text {
margin: 4px 0 8px;
max-height: 210px; overflow-y: auto;
padding: 11px 13px;
border: 1px solid var(--rand); border-radius: 11px;
background: rgba(0, 0, 0, .28);
font-family: inherit; font-size: .81rem; line-height: 1.55;
color: var(--text-leise);
white-space: pre-wrap; overflow-wrap: anywhere;
}
.dialog__unter--still { font-size: .8rem; color: var(--text-still); }
.dialog__knoepfe--links { justify-content: flex-start; margin: 0 0 10px; }
+12 -135
View File
@@ -197,24 +197,24 @@
hier geht es darum, niemandem einen Knopf hinzustellen, der
eine Absage bringt. */
['netz-oeffnen', LEITUNG.has(ich?.rolle)],
/* Die Datei gehoert EINEM Creator -- deshalb sichtbar fuer alle,
die fuer ihn eintragen duerfen, also auch fuer seinen Scout. */
['tt-oeffnen', darfEintragen()],
];
for (const [knopf, darf] of KNOEPFE) $(knopf).hidden = !darf;
/* Jeder Erklaersatz haengt an SEINEM Knopf, nicht am Block. Ein
Scout sieht nur die TikTok-Datei -- ihm die Backstage-Tabelle zu
erklaeren, an die er gar nicht kommt, ist keine Hilfe, sondern
eine Absage, die er nicht gesucht hat. Und ein Creator sieht
keinen von beiden, also auch keinen Satz. Abgeleitet statt
zweimal aufgeschrieben: kommt ein dritter Weg dazu, stimmt es
weiter. */
for (const [satz, knopf] of [['quelle-netz', 'netz-oeffnen'],
['quelle-tt', 'tt-oeffnen']]) {
Scout, der die Backstage-Tabelle nicht hat, soll sie auch nicht
erklaert bekommen -- das waere keine Hilfe, sondern eine Absage,
die er nicht gesucht hat. Und ein Creator sieht keinen Knopf,
also auch keinen Satz.
Die Liste hat seit dem 11.09.2026 nur noch einen Eintrag (der
Weg ueber die TikTok-Datei des Creators ist entfernt). Sie
bleibt trotzdem eine Liste: Der naechste Weg haengt sich dann
daneben, statt dass jemand die Regel ein zweites Mal
aufschreibt. */
for (const [satz, knopf] of [['quelle-netz', 'netz-oeffnen']]) {
$(satz).hidden = $(knopf).hidden;
}
$('quellen-hinweis').hidden = $('quelle-netz').hidden && $('quelle-tt').hidden;
$('quellen-hinweis').hidden = $('quelle-netz').hidden;
if (!z || (!z.diamanten && !z.tage && !z.verweildauer)) {
ziel.append(el('p', 'leistung__leer',
@@ -582,109 +582,6 @@
$('tag-form').addEventListener('submit', tagSpeichern);
$('tag-zu').addEventListener('click', () => $('tag-dialog').close());
$('tag-loeschen').addEventListener('click', tagLoeschen);
/* =====================================================================
DIE TIKTOK-DATEI DES CREATORS
Das Format ist nicht dokumentiert. Deshalb wird hier nichts
geraten: Der Server durchsucht die Datei nach Listen mit Datum und
schickt sie zur Auswahl zurück -- mit Anzahl, Zeitraum und dem,
was dabei herauskäme. Bestätigt wird von Hand.
===================================================================== */
let ttInhalt = null; /* die gelesene Datei -- bleibt im Browser */
let ttWahl = null; /* der bestätigte Kandidat */
async function ttDateiGewaehlt(ereignis) {
const datei = ereignis.target.files?.[0];
$('tt-fehler').textContent = '';
$('tt-vorschau').hidden = true;
$('tt-los').disabled = true;
ttInhalt = null; ttWahl = null;
if (!datei) return;
/* Ein Vollarchiv kann zweistellige Megabyte haben. Das Lesen
passiert im Browser; auf den Server geht es erst danach -- und
nur einmal, nicht bei jedem Klick. */
$('tt-fehler').textContent = 'Datei wird gelesen …';
let text;
try { text = await datei.text(); } catch { $('tt-fehler').textContent = 'Die Datei ließ sich nicht lesen.'; return; }
try { ttInhalt = JSON.parse(text); } catch {
$('tt-fehler').textContent = 'Das ist keine JSON-Datei. Beim Herunterladen bei TikTok '
+ 'muss als Format JSON gewählt werden, nicht TXT.';
return;
}
$('tt-fehler').textContent = 'Datei wird durchsucht …';
const a = await hole(`/workspace/api/leistung/${offenerCreator}/tiktok-datei/vorschau`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ inhalt: ttInhalt }),
}).catch(() => null);
if (!a) { $('tt-fehler').textContent = 'Die Datei konnte nicht geprüft werden.'; return; }
const d = await a.json().catch(() => ({}));
if (!a.ok) { $('tt-fehler').textContent = d.fehler || 'Die Datei wurde nicht verstanden.'; return; }
$('tt-fehler').textContent = '';
const v = $('tt-vorschau');
v.textContent = ''; v.hidden = false;
const zeile = (text2, art) => {
const p = el('p', 'import-vorschau__zeile');
if (art) p.dataset.art = art;
p.textContent = text2;
v.append(p);
return p;
};
zeile(`${d.kandidaten.length} Liste(n) mit Datum gefunden. Welche ist der LIVE-Verlauf?`);
/* Die Auswahl als echte Radioknöpfe: Es ist eine Entscheidung, und
eine Entscheidung gehört nicht in ein Aufklappfeld, in dem man
die erste Zeile für gesetzt hält. */
d.kandidaten.forEach((k, i) => {
const box = el('label', 'tt-wahl');
const r = document.createElement('input');
r.type = 'radio'; r.name = 'tt-kandidat'; r.value = String(i);
if (i === 0) { r.checked = true; ttWahl = k; }
r.addEventListener('change', () => { ttWahl = k; $('tt-los').disabled = false; });
const t = el('span', 'tt-wahl__text');
t.append(el('strong', null, k.pfad || '(oberste Ebene)'));
t.append(el('span', 'tt-wahl__zahl',
` ${k.anzahl} Einträge · ${k.tage_gesamt} Tage`
+ (k.von ? ` · ${k.von} bis ${k.bis}` : '')));
const felder = ['tag', 'dauer', 'diamanten', 'zuschauer']
.filter((f) => k.vorschlag[f])
.map((f) => `${f}: ${k.vorschlag[f]}`).join(' · ');
t.append(el('span', 'tt-wahl__felder', felder || 'nur ein Datum erkannt'));
box.append(r, t);
v.append(box);
});
if (ttWahl) $('tt-los').disabled = false;
}
async function ttUebernehmen(ereignis) {
ereignis.preventDefault();
if (!ttInhalt || !ttWahl) return;
$('tt-los').disabled = true;
const a = await hole(`/workspace/api/leistung/${offenerCreator}/tiktok-datei`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ inhalt: ttInhalt, pfad: ttWahl.pfad, zuordnung: ttWahl.vorschlag }),
}).catch(() => null);
if (!a) { $('tt-los').disabled = false; return; }
const d = await a.json().catch(() => ({}));
if (!a.ok) {
$('tt-fehler').textContent = d.fehler === 'keine_brauchbaren_tage'
? 'In dieser Liste stehen keine Tage mit Zahlen.'
: (d.fehler || 'Übernehmen ging nicht.');
$('tt-los').disabled = false;
return;
}
/* Die Datei loslassen, sobald sie nicht mehr gebraucht wird. */
ttInhalt = null; ttWahl = null;
$('tt-dialog').close();
melde(`${d.geschrieben} Tage neu, ${d.ergaenzt} ergänzt`);
setTimeout(() => melde(''), 6000);
await laden();
}
/* =====================================================================
DIE BACKSTAGE-TABELLE — Vorschau und Übernahme
===================================================================== */
@@ -797,26 +694,6 @@
$('netz-text').focus();
});
$('netz-zu').addEventListener('click', () => $('netz-dialog').close());
$('tt-oeffnen').addEventListener('click', () => {
$('tt-vorschau').hidden = true; $('tt-los').disabled = true;
$('tt-fehler').textContent = ''; $('tt-datei').value = '';
$('tt-dialog').showModal();
});
$('tt-zu').addEventListener('click', () => $('tt-dialog').close());
$('tt-datei').addEventListener('change', ttDateiGewaehlt);
/* Den Text fuer den Creator in die Zwischenablage. Kein
Erfolgshaken ohne Beleg: Geht es schief (kein Recht, alter
Browser), steht das auch da -- sonst glaubt man, es sei kopiert,
und schickt eine leere Nachricht. */
$('tt-text-kopieren').addEventListener('click', async (e) => {
const knopf = e.currentTarget;
try {
await navigator.clipboard.writeText($('tt-text').textContent);
knopf.textContent = 'Kopiert';
} catch { knopf.textContent = 'Ging nicht - bitte von Hand markieren'; }
setTimeout(() => { knopf.textContent = 'Text kopieren'; }, 2600);
});
$('tt-form').addEventListener('submit', ttUebernehmen);
/* Beim EINFÜGEN sofort nachsehen -- nicht erst auf einen Knopf warten.
Wer eine Tabelle einfügt, will wissen, ob sie erkannt wurde; ein
zweiter Klick dazwischen ist nur eine Gelegenheit, es nicht zu tun. */
+12 -12
View File
@@ -12,19 +12,19 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111404" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111404" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111404" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111501" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111501" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609111501" />
</head>
<body class="start">
@@ -252,10 +252,10 @@
</form>
</dialog>
<script src="assets/js/wahl.js?v=202609111404" defer></script>
<script src="assets/js/bereiche.js?v=202609111404" defer></script>
<script src="assets/js/kopf.js?v=202609111404" defer></script>
<script src="assets/js/glocke.js?v=202609111404" defer></script>
<script src="assets/js/aufgaben.js?v=202609111404" defer></script>
<script src="assets/js/wahl.js?v=202609111501" defer></script>
<script src="assets/js/bereiche.js?v=202609111501" defer></script>
<script src="assets/js/kopf.js?v=202609111501" defer></script>
<script src="assets/js/glocke.js?v=202609111501" defer></script>
<script src="assets/js/aufgaben.js?v=202609111501" defer></script>
</body>
</html>
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111404" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111404" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/automation.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111404" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111501" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111501" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/automation.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609111501" />
</head>
<body class="start">
@@ -147,10 +147,10 @@
</main>
<script src="assets/js/wahl.js?v=202609111404" defer></script>
<script src="assets/js/bereiche.js?v=202609111404" defer></script>
<script src="assets/js/kopf.js?v=202609111404" defer></script>
<script src="assets/js/glocke.js?v=202609111404" defer></script>
<script src="assets/js/automation.js?v=202609111404" defer></script>
<script src="assets/js/wahl.js?v=202609111501" defer></script>
<script src="assets/js/bereiche.js?v=202609111501" defer></script>
<script src="assets/js/kopf.js?v=202609111501" defer></script>
<script src="assets/js/glocke.js?v=202609111501" defer></script>
<script src="assets/js/automation.js?v=202609111501" 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=202609111404" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111404" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111404" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111501" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111501" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609111501" />
</head>
<body class="start">
@@ -181,14 +181,14 @@
</main>
<script src="assets/js/wahl.js?v=202609111404" defer></script>
<script src="assets/js/bereiche.js?v=202609111404" defer></script>
<script src="assets/js/kopf.js?v=202609111404" defer></script>
<script src="assets/js/glocke.js?v=202609111404" defer></script>
<script src="assets/js/checkliste.js?v=202609111404" defer></script>
<script src="assets/js/ampel.js?v=202609111404" defer></script>
<script src="assets/js/schulung.js?v=202609111404" defer></script>
<script src="assets/js/vorlagen.js?v=202609111404" defer></script>
<script src="assets/js/bereich.js?v=202609111404" defer></script>
<script src="assets/js/wahl.js?v=202609111501" defer></script>
<script src="assets/js/bereiche.js?v=202609111501" defer></script>
<script src="assets/js/kopf.js?v=202609111501" defer></script>
<script src="assets/js/glocke.js?v=202609111501" defer></script>
<script src="assets/js/checkliste.js?v=202609111501" defer></script>
<script src="assets/js/ampel.js?v=202609111501" defer></script>
<script src="assets/js/schulung.js?v=202609111501" defer></script>
<script src="assets/js/vorlagen.js?v=202609111501" defer></script>
<script src="assets/js/bereich.js?v=202609111501" defer></script>
</body>
</html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111404" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111404" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/calls.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111404" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111501" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111501" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/calls.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609111501" />
</head>
<body class="start">
@@ -89,11 +89,11 @@
</main>
<script src="assets/js/ki.js?v=202609111404" defer></script>
<script src="assets/js/wahl.js?v=202609111404" defer></script>
<script src="assets/js/bereiche.js?v=202609111404" defer></script>
<script src="assets/js/kopf.js?v=202609111404" defer></script>
<script src="assets/js/glocke.js?v=202609111404" defer></script>
<script src="assets/js/calls.js?v=202609111404" defer></script>
<script src="assets/js/ki.js?v=202609111501" defer></script>
<script src="assets/js/wahl.js?v=202609111501" defer></script>
<script src="assets/js/bereiche.js?v=202609111501" defer></script>
<script src="assets/js/kopf.js?v=202609111501" defer></script>
<script src="assets/js/glocke.js?v=202609111501" defer></script>
<script src="assets/js/calls.js?v=202609111501" defer></script>
</body>
</html>
+13 -13
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=202609111404" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111404" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111404" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111501" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111501" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/chat.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/chat.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609111501" />
</head>
<body class="start">
@@ -289,10 +289,10 @@
</form>
</dialog>
<script src="assets/js/bereiche.js?v=202609111404"></script>
<script src="assets/js/wahl.js?v=202609111404" defer></script>
<script src="assets/js/kopf.js?v=202609111404"></script>
<script src="assets/js/glocke.js?v=202609111404"></script>
<script src="assets/js/chat.js?v=202609111404"></script>
<script src="assets/js/bereiche.js?v=202609111501"></script>
<script src="assets/js/wahl.js?v=202609111501" defer></script>
<script src="assets/js/kopf.js?v=202609111501"></script>
<script src="assets/js/glocke.js?v=202609111501"></script>
<script src="assets/js/chat.js?v=202609111501"></script>
</body>
</html>
+15 -15
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111404" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111404" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/content.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111404" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111501" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111501" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/content.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609111501" />
</head>
<body class="start">
@@ -179,12 +179,12 @@
</main>
<script src="assets/js/wahl.js?v=202609111404" defer></script>
<script src="assets/js/bereiche.js?v=202609111404" defer></script>
<script src="assets/js/kopf.js?v=202609111404" defer></script>
<script src="assets/js/glocke.js?v=202609111404" defer></script>
<script src="assets/js/checkliste.js?v=202609111404" defer></script>
<script src="assets/js/vorlagen.js?v=202609111404" defer></script>
<script src="assets/js/content.js?v=202609111404" defer></script>
<script src="assets/js/wahl.js?v=202609111501" defer></script>
<script src="assets/js/bereiche.js?v=202609111501" defer></script>
<script src="assets/js/kopf.js?v=202609111501" defer></script>
<script src="assets/js/glocke.js?v=202609111501" defer></script>
<script src="assets/js/checkliste.js?v=202609111501" defer></script>
<script src="assets/js/vorlagen.js?v=202609111501" defer></script>
<script src="assets/js/content.js?v=202609111501" defer></script>
</body>
</html>
+5 -5
View File
@@ -30,15 +30,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=202609111404" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/crew-180.png?v=202609111404" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111404" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/crew-32.png?v=202609111501" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/crew-180.png?v=202609111501" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/crew-haus.css?v=202609111501" />
</head>
<body class="gate gate--crew">
@@ -252,6 +252,6 @@
</main>
</div>
<script src="assets/js/gate.js?v=202609111404" defer></script>
<script src="assets/js/gate.js?v=202609111501" defer></script>
</body>
</html>
+4 -4
View File
@@ -13,25 +13,25 @@
"theme_color": "#06090f",
"icons": [
{
"src": "/assets/img/app-symbole/crew-192.png?v=202609111404",
"src": "/assets/img/app-symbole/crew-192.png?v=202609111501",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/img/app-symbole/crew-512.png?v=202609111404",
"src": "/assets/img/app-symbole/crew-512.png?v=202609111501",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609111404",
"src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609111501",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609111404",
"src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609111501",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111404" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111404" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/dateien.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111404" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111501" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111501" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/dateien.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609111501" />
</head>
<body class="start">
@@ -108,10 +108,10 @@
</main>
<script src="assets/js/wahl.js?v=202609111404" defer></script>
<script src="assets/js/bereiche.js?v=202609111404" defer></script>
<script src="assets/js/kopf.js?v=202609111404" defer></script>
<script src="assets/js/glocke.js?v=202609111404" defer></script>
<script src="assets/js/dateien.js?v=202609111404" defer></script>
<script src="assets/js/wahl.js?v=202609111501" defer></script>
<script src="assets/js/bereiche.js?v=202609111501" defer></script>
<script src="assets/js/kopf.js?v=202609111501" defer></script>
<script src="assets/js/glocke.js?v=202609111501" defer></script>
<script src="assets/js/dateien.js?v=202609111501" defer></script>
</body>
</html>
+4 -4
View File
@@ -30,9 +30,9 @@
<link rel="preload" as="image" href="assets/img/buehne/gate-hoch-580.webp"
type="image/webp" media="(max-width: 899px)" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111404" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111404" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111404" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111501" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111501" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111501" />
</head>
<body class="gate">
@@ -401,6 +401,6 @@
</main>
</div>
<script src="assets/js/gate.js?v=202609111404" defer></script>
<script src="assets/js/gate.js?v=202609111501" defer></script>
</body>
</html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111404" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111404" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/kalender.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111404" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111501" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111501" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/kalender.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609111501" />
</head>
<body class="start">
@@ -377,11 +377,11 @@
</main>
<script src="assets/js/wahl.js?v=202609111404" defer></script>
<script src="assets/js/bereiche.js?v=202609111404" defer></script>
<script src="assets/js/kopf.js?v=202609111404" defer></script>
<script src="assets/js/glocke.js?v=202609111404" defer></script>
<script src="assets/js/kalender.js?v=202609111404" defer></script>
<script src="assets/js/kalender-abo.js?v=202609111404" defer></script>
<script src="assets/js/wahl.js?v=202609111501" defer></script>
<script src="assets/js/bereiche.js?v=202609111501" defer></script>
<script src="assets/js/kopf.js?v=202609111501" defer></script>
<script src="assets/js/glocke.js?v=202609111501" defer></script>
<script src="assets/js/kalender.js?v=202609111501" defer></script>
<script src="assets/js/kalender-abo.js?v=202609111501" defer></script>
</body>
</html>
+13 -82
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=202609111404" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111404" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/leistung.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111404" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111501" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111501" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/leistung.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609111501" />
</head>
<body class="start">
@@ -121,12 +121,6 @@
<button type="button" class="knopf knopf--klein" id="netz-oeffnen" hidden>
Backstage-Tabelle einfügen
</button>
<!-- DIE EIGENE TIKTOK-DATEI DES CREATORS (11.09.2026).
Der einzige Weg an TikTok-Daten, der ohne Entwicklerkonto
auskommt: Der Creator lädt sie selbst herunter. -->
<button type="button" class="knopf knopf--klein" id="tt-oeffnen" hidden>
TikTok-Datei des Creators
</button>
</div>
</div>
<!-- IMMER SICHTBAR, NICHT ERST IM FENSTER (11.09.2026).
@@ -141,10 +135,6 @@
<span id="quelle-netz" hidden><strong>Backstage-Tabelle:</strong> die Liste aus TikTok LIVE
Backstage, in der alle deine Creator mit ihren Zahlen stehen –
markieren, kopieren, einfügen. Für alle auf einmal.</span>
<span id="quelle-tt" hidden><strong>TikTok-Datei:</strong> die Datendatei, die ein Creator
sich in der TikTok-App selbst herunterlädt. Für einen Creator, und
nur er kommt daran. Die Anleitung zum Weiterschicken steht im
Fenster.</span>
</p>
<div class="tagetabelle" id="tagetabelle"></div>
</section>
@@ -263,65 +253,6 @@
legt sie vor – bestätigt wird von Hand. Damit funktioniert es auch
dann noch, wenn TikTok die Felder umbenennt.
==================================================================== -->
<dialog class="dialog dialog--breit" id="tt-dialog">
<form method="dialog" id="tt-form" class="neu neu--blank">
<h2 class="dialog__titel">TikTok-Datei einlesen</h2>
<p class="dialog__unter">
Diese Datei kann <strong>nur der Creator selbst</strong> holen – an
Backstage kommt sie nicht vorbei, und ein Zugang bei TikTok für uns
existiert nicht. Du brauchst sie also von ihm.
</p>
<!-- DIE ANLEITUNG ZUM WEITERSCHICKEN (11.09.2026).
Ein Manager oder Scout soll den Creator nicht mit eigenen Worten
durch sechs Menüpunkte lotsen müssen -- dabei geht jedes Mal ein
Schritt verloren, und am Ende kommt eine TXT-Datei an, die
niemand lesen kann. Der Text steht deshalb fertig da und lässt
sich mit einem Klick kopieren. -->
<p class="feld-schild">Das schickst du dem Creator</p>
<pre class="anleitung-text" id="tt-text">Hi! Ich brauche einmal deine TikTok-Daten, damit deine LIVE-Zahlen bei uns im Workspace stehen. Das machst du selbst in der App, dauert zwei Minuten:
1. TikTok öffnen, auf dein Profil gehen
2. Oben rechts auf die drei Striche, dann "Einstellungen und Datenschutz"
3. "Konto" öffnen, dann "Deine Daten herunterladen"
4. Reiter "Daten anfordern": bei Datenumfang "Alle Daten" wählen und als Dateiformat unbedingt JSON (nicht TXT)
5. Auf "Daten anfordern" tippen
TikTok braucht dafür 1 bis 4 Tage. Wenn es fertig ist, gehst du an derselben Stelle auf den Reiter "Daten herunterladen", lädst die Datei und schickst sie mir.
Was ich damit mache: Ich lese daraus nur deinen LIVE-Verlauf (wann und wie lange du live warst). Nachrichten, Suchverlauf und Auszahlungen werden nicht gelesen, und die Datei selbst wird nicht gespeichert.</pre>
<p class="dialog__knoepfe dialog__knoepfe--links">
<button type="button" class="knopf-still" id="tt-text-kopieren">Text kopieren</button>
</p>
<p class="dialog__unter dialog__unter--still">
Die Bezeichnungen können je nach App-Version leicht abweichen – der Weg
ist immer <em>Einstellungen → Konto → Deine Daten herunterladen</em>.
<strong>JSON ist Pflicht</strong>: Eine TXT-Datei lässt sich nicht
auswerten.
</p>
<p class="dialog__unter dialog__unter--still">
Wenn die Datei da ist: hier auswählen. Der Workspace durchsucht sie
und zeigt dir, welche Liste darin der LIVE-Verlauf ist – du bestätigst
nur. Gelesen und <strong>nicht gespeichert</strong>.
</p>
<div class="neu__zeile">
<label class="feld-schild" for="tt-datei">Datei wählen</label>
<input id="tt-datei" type="file" accept=".json,application/json" />
</div>
<div class="import-vorschau" id="tt-vorschau" hidden></div>
<p class="fehler" id="tt-fehler" role="alert" aria-live="polite"></p>
<div class="dialog__knoepfe">
<button type="submit" class="knopf knopf--klein" id="tt-los" disabled>Übernehmen</button>
<button type="button" class="abmelden" id="tt-zu">Abbrechen</button>
</div>
</form>
</dialog>
<dialog class="dialog dialog--breit" id="netz-dialog">
<form method="dialog" id="netz-form" class="neu neu--blank">
<h2 class="dialog__titel">Backstage-Tabelle einfügen</h2>
@@ -422,10 +353,10 @@ Was ich damit mache: Ich lese daraus nur deinen LIVE-Verlauf (wann und wie lange
</form>
</dialog>
<script src="assets/js/wahl.js?v=202609111404"></script>
<script src="assets/js/bereiche.js?v=202609111404"></script>
<script src="assets/js/kopf.js?v=202609111404"></script>
<script src="assets/js/glocke.js?v=202609111404"></script>
<script src="assets/js/leistung.js?v=202609111404"></script>
<script src="assets/js/wahl.js?v=202609111501"></script>
<script src="assets/js/bereiche.js?v=202609111501"></script>
<script src="assets/js/kopf.js?v=202609111501"></script>
<script src="assets/js/glocke.js?v=202609111501"></script>
<script src="assets/js/leistung.js?v=202609111501"></script>
</body>
</html>
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111404" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111404" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/personen.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111404" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111501" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111501" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/personen.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609111501" />
</head>
<body class="start">
@@ -396,10 +396,10 @@
</main>
<script src="assets/js/wahl.js?v=202609111404" defer></script>
<script src="assets/js/bereiche.js?v=202609111404" defer></script>
<script src="assets/js/kopf.js?v=202609111404" defer></script>
<script src="assets/js/glocke.js?v=202609111404" defer></script>
<script src="assets/js/personen.js?v=202609111404" defer></script>
<script src="assets/js/wahl.js?v=202609111501" defer></script>
<script src="assets/js/bereiche.js?v=202609111501" defer></script>
<script src="assets/js/kopf.js?v=202609111501" defer></script>
<script src="assets/js/glocke.js?v=202609111501" defer></script>
<script src="assets/js/personen.js?v=202609111501" defer></script>
</body>
</html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111404" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111404" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111404" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111501" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111501" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609111501" />
</head>
<body class="start">
@@ -214,11 +214,11 @@
</main>
<script src="assets/js/wahl.js?v=202609111404" defer></script>
<script src="assets/js/bereiche.js?v=202609111404" defer></script>
<script src="assets/js/kopf.js?v=202609111404" defer></script>
<script src="assets/js/glocke.js?v=202609111404" defer></script>
<script src="assets/js/steckbrief.js?v=202609111404" defer></script>
<script src="assets/js/profil.js?v=202609111404" defer></script>
<script src="assets/js/wahl.js?v=202609111501" defer></script>
<script src="assets/js/bereiche.js?v=202609111501" defer></script>
<script src="assets/js/kopf.js?v=202609111501" defer></script>
<script src="assets/js/glocke.js?v=202609111501" defer></script>
<script src="assets/js/steckbrief.js?v=202609111501" defer></script>
<script src="assets/js/profil.js?v=202609111501" defer></script>
</body>
</html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111404" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111404" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/report.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111404" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111501" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111501" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/report.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609111501" />
</head>
<body class="start">
@@ -106,11 +106,11 @@
</main>
<script src="assets/js/ki.js?v=202609111404" defer></script>
<script src="assets/js/wahl.js?v=202609111404" defer></script>
<script src="assets/js/bereiche.js?v=202609111404" defer></script>
<script src="assets/js/kopf.js?v=202609111404" defer></script>
<script src="assets/js/glocke.js?v=202609111404" defer></script>
<script src="assets/js/report.js?v=202609111404" defer></script>
<script src="assets/js/ki.js?v=202609111501" defer></script>
<script src="assets/js/wahl.js?v=202609111501" defer></script>
<script src="assets/js/bereiche.js?v=202609111501" defer></script>
<script src="assets/js/kopf.js?v=202609111501" defer></script>
<script src="assets/js/glocke.js?v=202609111501" defer></script>
<script src="assets/js/report.js?v=202609111501" defer></script>
</body>
</html>
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111404" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111404" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/scouting.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111404" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111501" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111501" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/scouting.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609111501" />
</head>
<body class="start">
@@ -173,10 +173,10 @@
</div>
</div>
<script src="assets/js/wahl.js?v=202609111404" defer></script>
<script src="assets/js/bereiche.js?v=202609111404" defer></script>
<script src="assets/js/kopf.js?v=202609111404" defer></script>
<script src="assets/js/glocke.js?v=202609111404" defer></script>
<script src="assets/js/scouting.js?v=202609111404" defer></script>
<script src="assets/js/wahl.js?v=202609111501" defer></script>
<script src="assets/js/bereiche.js?v=202609111501" defer></script>
<script src="assets/js/kopf.js?v=202609111501" defer></script>
<script src="assets/js/glocke.js?v=202609111501" defer></script>
<script src="assets/js/scouting.js?v=202609111501" defer></script>
</body>
</html>
+12 -12
View File
@@ -12,21 +12,21 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111404" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111404" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111404" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111501" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111501" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/heim.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609111501" />
</head>
<body class="start">
@@ -674,10 +674,10 @@
</p>
</main>
<script src="assets/js/wahl.js?v=202609111404" defer></script>
<script src="assets/js/bereiche.js?v=202609111404" defer></script>
<script src="assets/js/kopf.js?v=202609111404" defer></script>
<script src="assets/js/glocke.js?v=202609111404" defer></script>
<script src="assets/js/start.js?v=202609111404" defer></script>
<script src="assets/js/wahl.js?v=202609111501" defer></script>
<script src="assets/js/bereiche.js?v=202609111501" defer></script>
<script src="assets/js/kopf.js?v=202609111501" defer></script>
<script src="assets/js/glocke.js?v=202609111501" defer></script>
<script src="assets/js/start.js?v=202609111501" defer></script>
</body>
</html>
+14 -14
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111404" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111404" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/startcheck.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111404" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111501" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111501" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/startcheck.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609111501" />
</head>
<body class="start">
@@ -85,11 +85,11 @@
</main>
<script src="assets/js/ki.js?v=202609111404" defer></script>
<script src="assets/js/wahl.js?v=202609111404" defer></script>
<script src="assets/js/bereiche.js?v=202609111404" defer></script>
<script src="assets/js/kopf.js?v=202609111404" defer></script>
<script src="assets/js/glocke.js?v=202609111404" defer></script>
<script src="assets/js/startcheck.js?v=202609111404" defer></script>
<script src="assets/js/ki.js?v=202609111501" defer></script>
<script src="assets/js/wahl.js?v=202609111501" defer></script>
<script src="assets/js/bereiche.js?v=202609111501" defer></script>
<script src="assets/js/kopf.js?v=202609111501" defer></script>
<script src="assets/js/glocke.js?v=202609111501" defer></script>
<script src="assets/js/startcheck.js?v=202609111501" defer></script>
</body>
</html>
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111404" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111404" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111404" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111501" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111501" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609111501" />
</head>
<body class="start">
@@ -126,10 +126,10 @@
</main>
<script src="assets/js/wahl.js?v=202609111404" defer></script>
<script src="assets/js/bereiche.js?v=202609111404" defer></script>
<script src="assets/js/kopf.js?v=202609111404" defer></script>
<script src="assets/js/glocke.js?v=202609111404" defer></script>
<script src="assets/js/steckbrief.js?v=202609111404" defer></script>
<script src="assets/js/wahl.js?v=202609111501" defer></script>
<script src="assets/js/bereiche.js?v=202609111501" defer></script>
<script src="assets/js/kopf.js?v=202609111501" defer></script>
<script src="assets/js/glocke.js?v=202609111501" defer></script>
<script src="assets/js/steckbrief.js?v=202609111501" defer></script>
</body>
</html>
+13 -13
View File
@@ -7,20 +7,20 @@
<meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111404" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111404" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/team.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111404" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111501" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111501" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/team.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609111501" />
</head>
<body class="start">
@@ -110,10 +110,10 @@
</main>
<script src="assets/js/wahl.js?v=202609111404" defer></script>
<script src="assets/js/bereiche.js?v=202609111404" defer></script>
<script src="assets/js/kopf.js?v=202609111404" defer></script>
<script src="assets/js/glocke.js?v=202609111404" defer></script>
<script src="assets/js/team.js?v=202609111404" defer></script>
<script src="assets/js/wahl.js?v=202609111501" defer></script>
<script src="assets/js/bereiche.js?v=202609111501" defer></script>
<script src="assets/js/kopf.js?v=202609111501" defer></script>
<script src="assets/js/glocke.js?v=202609111501" defer></script>
<script src="assets/js/team.js?v=202609111501" defer></script>
</body>
</html>
+12 -12
View File
@@ -7,20 +7,20 @@
<meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" />
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111404" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111404" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/team.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111404" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111501" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111501" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/team.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609111501" />
</head>
<!-- `data-buehne` steht hier fest und nicht im Skript: Die Kachel zu
@@ -84,9 +84,9 @@
Kaputt ist dabei nichts -- er funktioniert. Genau deshalb faellt
es niemandem auf, und deshalb prueft pruef-css-klassen die
ABHAENGIGKEIT statt der Optik. Hier hat sie angeschlagen. -->
<script src="assets/js/wahl.js?v=202609111404" defer></script>
<script src="assets/js/bereiche.js?v=202609111404" defer></script>
<script src="assets/js/kopf.js?v=202609111404" defer></script>
<script src="assets/js/teamlage.js?v=202609111404" defer></script>
<script src="assets/js/wahl.js?v=202609111501" defer></script>
<script src="assets/js/bereiche.js?v=202609111501" defer></script>
<script src="assets/js/kopf.js?v=202609111501" defer></script>
<script src="assets/js/teamlage.js?v=202609111501" defer></script>
</body>
</html>
+13 -13
View File
@@ -12,22 +12,22 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111404" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111404" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111404" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111501" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111501" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111501" />
<!-- Formular- und Dialogstile: seit dem 07.09.2026 steht hier der
Dialog "Creator anlegen". -->
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/uebersicht.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/uebersicht.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609111501" />
</head>
<body class="start">
@@ -149,11 +149,11 @@
</main>
<script src="assets/js/bereiche.js?v=202609111404" defer></script>
<script src="assets/js/bereiche.js?v=202609111501" defer></script>
<script src="assets/js/wahl.js?v=202609111404" defer></script>
<script src="assets/js/kopf.js?v=202609111404" defer></script>
<script src="assets/js/glocke.js?v=202609111404" defer></script>
<script src="assets/js/uebersicht.js?v=202609111404" defer></script>
<script src="assets/js/wahl.js?v=202609111501" defer></script>
<script src="assets/js/kopf.js?v=202609111501" defer></script>
<script src="assets/js/glocke.js?v=202609111501" defer></script>
<script src="assets/js/uebersicht.js?v=202609111501" defer></script>
</body>
</html>
+13 -13
View File
@@ -12,20 +12,20 @@
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111404" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111404" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/wissen.css?v=202609111404" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111404" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609111501" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609111501" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/start.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/wissen.css?v=202609111501" />
<link rel="stylesheet" href="assets/css/module.css?v=202609111501" />
<!-- 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=202609111404" />
<link rel="stylesheet" href="assets/css/haus.css?v=202609111501" />
</head>
<body class="start">
@@ -190,10 +190,10 @@
</main>
<script src="assets/js/wahl.js?v=202609111404" defer></script>
<script src="assets/js/bereiche.js?v=202609111404" defer></script>
<script src="assets/js/kopf.js?v=202609111404" defer></script>
<script src="assets/js/glocke.js?v=202609111404" defer></script>
<script src="assets/js/wissen.js?v=202609111404" defer></script>
<script src="assets/js/wahl.js?v=202609111501" defer></script>
<script src="assets/js/bereiche.js?v=202609111501" defer></script>
<script src="assets/js/kopf.js?v=202609111501" defer></script>
<script src="assets/js/glocke.js?v=202609111501" defer></script>
<script src="assets/js/wissen.js?v=202609111501" defer></script>
</body>
</html>