Workspace: Versionsstempel gegen Cloudflares 4-Stunden-Cache

Filipe sah die neue Kachel nicht, obwohl sie live war. Ursache gemessen,
nicht vermutet:

  Node liefert   : Cache-Control: no-cache        (richtig)
  Cloudflare macht: Cache-Control: max-age=14400  (ueberschreibt es)
  cf-cache-status: REVALIDATED, Server: cloudflare

Cloudflares voreingestellte Browser-Cache-Zeit von vier Stunden setzt
sich ueber das no-cache des Servers hinweg -- aber NUR bei JS und CSS.
HTML kommt mit DYNAMIC und no-cache immer frisch durch (geprueft an
/creator.html und /workspace/).

Genau deshalb genuegt ein Stempel in der HTML: neue Seite -> neue
Adresse -> Cloudflare kennt sie nicht -> frische Datei. Keine
Cloudflare-Einstellung noetig, nichts, worauf ich warten muss.

tools/workspace-stempel.mjs setzt den Stempel auf alle 71 Verweise in
13 Dateien. Der Lauf ist wiederholbar: ein vorhandener Stempel wird
ersetzt, nicht angehaengt (geprueft).

Gehoert ab jetzt vor jeden Deploy, der JS oder CSS im Workspace anfasst.
Ich verlasse mich damit nicht mehr aufs Erinnern -- das war der
eigentliche Fehler.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-08-28 13:03:33 +02:00
co-authored by Claude Opus 5
parent 9b2e09be58
commit c0ac041122
16 changed files with 367 additions and 71 deletions
+2
View File
@@ -27,6 +27,7 @@ import { hinweisRouter } from "./workspace-hinweise.js";
import { sucheRouter } from "./workspace-suche.js";
import { startcheckRouter } from "./workspace-startcheck.js";
import { wissenRouter } from "./workspace-wissen.js";
import { kiRouter } from "./workspace-ki.js";
const __dirname = dirname(fileURLToPath(import.meta.url));
const SITE_DIR = join(__dirname, "..");
@@ -94,6 +95,7 @@ app.use(hinweisRouter);
app.use(sucheRouter);
app.use(startcheckRouter);
app.use(wissenRouter);
app.use(kiRouter);
/* Sicherheits-Fix 20.08.2026 (Audit vor dem geplanten öffentlichen Start morgen).
SITE_DIR ist der GESAMTE Repo-Ordner (join(__dirname, "..")) — express.static liefert
+235
View File
@@ -0,0 +1,235 @@
/* =====================================================================
workspace-ki.js — lokale KI-Assistenz (Konzept, Phase 4: "optionale
KI-Assistenz").
Laeuft auf DIESEM Server, in llama.cpp mit Qwen3.5-4B (Apache 2.0).
Nichts verlaesst die Maschine, es entstehen keine Kosten, und wenn der
Dienst aus ist, funktioniert der ganze Workspace unveraendert weiter.
VIER REGELN, die hier ueber allem stehen:
1. DIE KI SCHLAEGT VOR, SIE ENTSCHEIDET NIE. Kein Endpunkt dieser
Datei schreibt in die Datenbank. Was uebernommen wird, bestaetigt
ein Mensch -- an genau derselben Stelle wie bisher.
2. SIE DARF DEN WORKSPACE NIE MITREISSEN. Der Dienst kann aus sein,
haengen oder Unsinn liefern. Jede Antwort hat eine harte Zeitgrenze,
jeder Fehler endet in einer ruhigen Absage -- nie in einem 500er.
3. NUR EINE ANFRAGE GLEICHZEITIG. Der Server hat vier Kerne, zwei
davon darf die KI benutzen. Zwei parallele Anfragen wuerden beide
langsam und niemandem helfen. Wer wartet, bekommt das gesagt.
4. DENKEN IST ABGESCHALTET. Qwen3.5 ist ein Denk-Modell; ohne diese
Einstellung verbraucht es die ganze Antwortlaenge fuer
Selbstgespraeche und liefert am Ende NICHTS. Beim ersten Test
genau so passiert.
===================================================================== */
import express from "express";
import { sitzungLesen, protokolliere, echteIp } from "./workspace.js";
export const kiRouter = express.Router();
const KI_URL = process.env.KI_URL || "http://127.0.0.1:8771";
const ZEITGRENZE_MS = 45_000;
const TEXT_MAX = 4000;
/* Nur eine Anfrage zur Zeit -- siehe Regel 3. */
let laeuft = false;
/* Einfache Bremse je Person: fuenf Anfragen in zehn Minuten. Die KI ist
billig im Geld, aber teuer in Rechenzeit, die der Website gehoert. */
const VERBRAUCH = new Map();
const LIMIT = 5;
const FENSTER_MS = 10 * 60_000;
function zuVielGefragt(id) {
const jetzt = Date.now();
const liste = (VERBRAUCH.get(id) || []).filter((t) => jetzt - t < FENSTER_MS);
VERBRAUCH.set(id, liste);
return liste.length >= LIMIT;
}
function merkeAnfrage(id) {
const liste = VERBRAUCH.get(id) || [];
liste.push(Date.now());
VERBRAUCH.set(id, liste);
}
function angemeldet(req, res, next) {
const person = sitzungLesen(req);
if (!person) return res.status(401).json({ fehler: "nicht_angemeldet" });
req.person = person;
next();
}
function gleicheHerkunft(req, res, next) {
const herkunft = req.get("origin");
if (!herkunft) return next();
let erlaubt;
try { erlaubt = new URL(herkunft).host === req.get("host"); } catch { erlaubt = false; }
if (!erlaubt) return res.status(403).json({ fehler: "fremde_herkunft" });
next();
}
kiRouter.use("/workspace/api/ki", angemeldet);
/* ---------- Die eine Stelle, an der gefragt wird ------------------------- */
/* Ein kleines Modell folgt einer BESCHREIBUNG schlecht und einem
BEISPIEL gut. Ohne Beispiel kam auf einen Start-Check-Befund
"Klar machen wir feste Sendetermine fuer den Start-Check" zurueck --
Geplauder statt Aufgabe. Mit einem einzigen Beispiel:
"Zeitpunkt der Sendung festlegen". Deshalb hat jede Aufgabe hier ihr
Beispielpaar. */
async function frage(anweisung, text, maxWorte, beispiele = []) {
const abbruch = new AbortController();
const uhr = setTimeout(() => abbruch.abort(), ZEITGRENZE_MS);
try {
const antwort = await fetch(KI_URL + "/v1/chat/completions", {
method: "POST",
headers: { "Content-Type": "application/json" },
signal: abbruch.signal,
body: JSON.stringify({
messages: [
{ role: "system", content: anweisung },
...beispiele,
{ role: "user", content: text },
],
/* Ohne diese Zeile denkt das Modell laut und liefert nichts. */
chat_template_kwargs: { enable_thinking: false },
max_tokens: maxWorte,
temperature: 0.3,
stream: false,
}),
});
if (!antwort.ok) throw new Error("HTTP " + antwort.status);
const d = await antwort.json();
return String(d?.choices?.[0]?.message?.content ?? "").trim();
} finally {
clearTimeout(uhr);
}
}
/* Gemeinsamer Rahmen: Bremse, Sperre, Fehler abfangen. So steht die
Absicherung einmal da und nicht in jedem Endpunkt neu. */
async function mitSchutz(req, res, aufgabe, was) {
if (zuVielGefragt(req.person.id)) {
return res.status(429).json({
fehler: `Höchstens ${LIMIT} Vorschläge in ${FENSTER_MS / 60000} Minuten – die KI teilt sich den Server mit der Website.`,
});
}
if (laeuft) {
return res.status(409).json({ fehler: "Die KI denkt gerade über etwas anderes nach. Gleich nochmal probieren." });
}
laeuft = true;
const start = Date.now();
try {
const ergebnis = await aufgabe();
merkeAnfrage(req.person.id);
protokolliere("ki_gefragt", {
personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req),
detail: `${was}, ${Math.round((Date.now() - start) / 1000)} s`.slice(0, 120),
});
res.json(ergebnis);
} catch (fehler) {
const weg = fehler?.name === "AbortError";
console.error("[workspace] KI:", fehler?.message);
res.status(503).json({
fehler: weg
? "Die KI hat zu lange gebraucht. Schreib es diesmal selbst – es geht auch ohne."
: "Die KI ist gerade nicht erreichbar. Alles andere funktioniert normal weiter.",
});
} finally {
laeuft = false;
}
}
/* ---------- Ist die KI ueberhaupt da? -----------------------------------
Die Oberflaeche fragt das beim Laden und blendet die Knoepfe sonst
aus. Ein Knopf, der immer eine Fehlermeldung bringt, ist schlimmer
als gar kein Knopf. */
kiRouter.get("/workspace/api/ki/status", async (req, res) => {
const abbruch = new AbortController();
const uhr = setTimeout(() => abbruch.abort(), 2500);
try {
const a = await fetch(KI_URL + "/health", { signal: abbruch.signal });
res.json({ da: a.ok, beschaeftigt: laeuft });
} catch {
res.json({ da: false, beschaeftigt: false });
} finally {
clearTimeout(uhr);
}
});
/* ---------- Aus einem Gespraech To-dos vorschlagen ----------------------- */
kiRouter.post("/workspace/api/ki/todos", gleicheHerkunft, express.json(), (req, res) => {
const text = String(req.body?.text ?? "").trim().slice(0, TEXT_MAX);
if (text.length < 20) {
return res.status(400).json({ fehler: "Schreib erst ein paar Sätze – daraus kann die KI dann To-dos ableiten." });
}
return mitSchutz(req, res, async () => {
const roh = await frage(
"Du leitest aus Gespraechsnotizen To-dos ab. Nur die Liste, eine Zeile je To-do, "
+ "jede mit Bindestrich, hoechstens 10 Woerter, Deutsch, Verb am Anfang, keine Vorrede.",
text,
200,
[
{ role: "user", content: "Kamera ist zu dunkel, wir wollten ein Ringlicht kaufen. Overlay ist noch vom letzten Jahr." },
{ role: "assistant", content: "- Ringlicht besorgen und ausrichten\n- Overlay neu gestalten" },
]);
/* Die Zeilen aufraeumen: Aufzaehlungszeichen weg, Leeres raus,
hoechstens acht. Was das Modell an Vorrede doch liefert, faellt
hier durch das Raster. */
const todos = roh.split("\n")
.map((z) => z.replace(/^\s*[-*•\d.)\]]+\s*/, "").trim())
.filter((z) => z.length >= 4 && z.length <= 160)
.slice(0, 8);
return { todos, hinweis: "Vorschlag der KI – prüfe ihn, bevor du ihn übernimmst." };
}, "todos");
});
/* ---------- Aus einem Befund einen Aufgabentitel ------------------------- */
kiRouter.post("/workspace/api/ki/aufgabentitel", gleicheHerkunft, express.json(), (req, res) => {
const befund = String(req.body?.befund ?? "").trim().slice(0, 600);
const bereich = String(req.body?.bereich ?? "").trim().slice(0, 120);
if (befund.length < 8) {
return res.status(400).json({ fehler: "Schreib erst eine Notiz zu dem Punkt." });
}
return mitSchutz(req, res, async () => {
const roh = await frage(
"Du formulierst kurze Aufgabentitel. Nur der Titel, hoechstens 8 Woerter, "
+ "Deutsch, Verb am Anfang, kein Punkt, keine Anfuehrungszeichen.",
`Befund${bereich ? " (" + bereich + ")" : ""}: ${befund}`,
40,
[
{ role: "user", content: "Befund: Profilbild ist unscharf und alt." },
{ role: "assistant", content: "Neues Profilbild aufnehmen und hochladen" },
]);
const titel = roh.split("\n")[0].replace(/^["'„»\s]+|["'“«.\s]+$/g, "").slice(0, 160);
return { titel, hinweis: "Vorschlag der KI – du kannst ihn vor dem Anlegen ändern." };
}, "aufgabentitel");
});
/* ---------- Zahlen in Worte ---------------------------------------------- */
kiRouter.post("/workspace/api/ki/reporttext", gleicheHerkunft, express.json(), (req, res) => {
const zahlen = String(req.body?.zahlen ?? "").trim().slice(0, 1500);
if (zahlen.length < 10) return res.status(400).json({ fehler: "Keine Zahlen zum Zusammenfassen." });
return mitSchutz(req, res, async () => {
const roh = await frage(
"Du fasst Kennzahlen sachlich zusammen. Hoechstens drei Saetze auf Deutsch, "
+ "ohne Vorrede, ohne Aufzaehlung. Nenne nur, was in den Zahlen steht.",
zahlen, 140,
[
{ role: "user", content: "30 Tage: 6 Aufgaben erledigt (davor 2), 0 ueberfaellig, 1 dringender Punkt." },
{ role: "assistant", content: "Die Erledigungen haben sich gegenueber dem Vormonat verdreifacht, von zwei auf sechs. Ueberfaellig ist nichts, offen bleibt ein dringender Punkt." },
]);
return { text: roh.split("\n").filter(Boolean).join(" ").slice(0, 700),
hinweis: "Vorschlag der KI – geprüft wird an den Zahlen darüber." };
}, "reporttext");
});
+59
View File
@@ -0,0 +1,59 @@
/* =====================================================================
workspace-stempel.mjs — Versionsstempel an die Workspace-Dateien.
WARUM ES DAS GIBT:
Cloudflare speichert JS und CSS vier Stunden im Browser -- es
ueberschreibt damit das "no-cache", das der Server korrekt schickt.
Nach einem Deploy sah man deshalb bis zu vier Stunden lang die ALTE
Oberflaeche, obwohl der Server laengst die neue auslieferte. Genau so
ist es passiert: eine neue Kachel war live, aber nicht sichtbar.
Die HTML-Seiten kommen dagegen immer frisch an (no-cache, DYNAMIC).
Deshalb genuegt es, in der HTML einen Stempel an die Adressen zu
haengen: Neue Seite -> neue Adresse -> Cloudflare kennt sie nicht ->
frische Datei. Ohne Cloudflare-Einstellung, ohne Zutun von aussen.
Der Stempel ist die Minute des Aufrufs. Er wird bei jedem Lauf neu
gesetzt, auch wenn schon einer dasteht.
AUFRUF: node tools/workspace-stempel.mjs
Gehoert vor jeden Deploy, der JS oder CSS im Workspace anfasst.
===================================================================== */
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const HIER = dirname(fileURLToPath(import.meta.url));
const ORDNER = join(HIER, "..", "workspace");
const d = new Date();
const p = (n) => String(n).padStart(2, "0");
const STEMPEL = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}${p(d.getHours())}${p(d.getMinutes())}`;
/* Trifft href="assets/..." und src="assets/..." -- mit oder ohne
vorhandenen Stempel, damit wiederholte Laeufe nicht anhaengen. */
const MUSTER = /(href|src)="(assets\/(?:css|js)\/[A-Za-z0-9._-]+)(?:\?v=\d+)?"/g;
let dateien = 0;
let stellen = 0;
for (const name of readdirSync(ORDNER)) {
if (!name.endsWith(".html")) continue;
const pfad = join(ORDNER, name);
const alt = readFileSync(pfad, "utf8");
let n = 0;
const neu = alt.replace(MUSTER, (_, was, datei) => {
n += 1;
return `${was}="${datei}?v=${STEMPEL}"`;
});
if (neu !== alt) {
writeFileSync(pfad, neu);
dateien += 1;
stellen += n;
console.log(` ${name.padEnd(20)} ${n} Verweise`);
}
}
console.log(`\nStempel ${STEMPEL} auf ${stellen} Verweise in ${dateien} Dateien gesetzt.`);
if (!dateien) console.log("Nichts geaendert -- stimmt das Muster noch?");
+5 -5
View File
@@ -7,9 +7,9 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css" />
<link rel="stylesheet" href="assets/css/start.css" />
<link rel="stylesheet" href="assets/css/aufgaben.css" />
<link rel="stylesheet" href="assets/css/gate.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/start.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202608281303" />
</head>
<body class="start">
@@ -134,7 +134,7 @@
</form>
</dialog>
<script src="assets/js/kopf.js" defer></script>
<script src="assets/js/aufgaben.js" defer></script>
<script src="assets/js/kopf.js?v=202608281303" defer></script>
<script src="assets/js/aufgaben.js?v=202608281303" defer></script>
</body>
</html>
+6 -6
View File
@@ -7,10 +7,10 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css" />
<link rel="stylesheet" href="assets/css/start.css" />
<link rel="stylesheet" href="assets/css/aufgaben.css" />
<link rel="stylesheet" href="assets/css/bereich.css" />
<link rel="stylesheet" href="assets/css/gate.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/start.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202608281303" />
</head>
<body class="start">
@@ -98,7 +98,7 @@
</main>
<script src="assets/js/kopf.js" defer></script>
<script src="assets/js/bereich.js" defer></script>
<script src="assets/js/kopf.js?v=202608281303" defer></script>
<script src="assets/js/bereich.js?v=202608281303" defer></script>
</body>
</html>
+6 -6
View File
@@ -7,10 +7,10 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css" />
<link rel="stylesheet" href="assets/css/start.css" />
<link rel="stylesheet" href="assets/css/aufgaben.css" />
<link rel="stylesheet" href="assets/css/calls.css" />
<link rel="stylesheet" href="assets/css/gate.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/start.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/calls.css?v=202608281303" />
</head>
<body class="start">
@@ -49,7 +49,7 @@
</main>
<script src="assets/js/kopf.js" defer></script>
<script src="assets/js/calls.js" defer></script>
<script src="assets/js/kopf.js?v=202608281303" defer></script>
<script src="assets/js/calls.js?v=202608281303" defer></script>
</body>
</html>
+6 -6
View File
@@ -7,10 +7,10 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css" />
<link rel="stylesheet" href="assets/css/start.css" />
<link rel="stylesheet" href="assets/css/aufgaben.css" />
<link rel="stylesheet" href="assets/css/dateien.css" />
<link rel="stylesheet" href="assets/css/gate.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/start.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/dateien.css?v=202608281303" />
</head>
<body class="start">
@@ -79,7 +79,7 @@
</main>
<script src="assets/js/kopf.js" defer></script>
<script src="assets/js/dateien.js" defer></script>
<script src="assets/js/kopf.js?v=202608281303" defer></script>
<script src="assets/js/dateien.js?v=202608281303" defer></script>
</body>
</html>
+2 -2
View File
@@ -25,7 +25,7 @@
type="image/webp" media="(max-width: 899px)" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css" />
<link rel="stylesheet" href="assets/css/gate.css?v=202608281303" />
</head>
<body class="gate">
@@ -130,6 +130,6 @@
</p>
</main>
<script src="assets/js/gate.js" defer></script>
<script src="assets/js/gate.js?v=202608281303" defer></script>
</body>
</html>
+6 -6
View File
@@ -7,10 +7,10 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css" />
<link rel="stylesheet" href="assets/css/start.css" />
<link rel="stylesheet" href="assets/css/aufgaben.css" />
<link rel="stylesheet" href="assets/css/kalender.css" />
<link rel="stylesheet" href="assets/css/gate.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/start.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/kalender.css?v=202608281303" />
</head>
<body class="start">
@@ -94,7 +94,7 @@
</main>
<script src="assets/js/kopf.js" defer></script>
<script src="assets/js/kalender.js" defer></script>
<script src="assets/js/kopf.js?v=202608281303" defer></script>
<script src="assets/js/kalender.js?v=202608281303" defer></script>
</body>
</html>
+6 -6
View File
@@ -7,10 +7,10 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css" />
<link rel="stylesheet" href="assets/css/start.css" />
<link rel="stylesheet" href="assets/css/aufgaben.css" />
<link rel="stylesheet" href="assets/css/personen.css" />
<link rel="stylesheet" href="assets/css/gate.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/start.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/personen.css?v=202608281303" />
</head>
<body class="start">
@@ -87,7 +87,7 @@
</main>
<script src="assets/js/kopf.js" defer></script>
<script src="assets/js/personen.js" defer></script>
<script src="assets/js/kopf.js?v=202608281303" defer></script>
<script src="assets/js/personen.js?v=202608281303" defer></script>
</body>
</html>
+6 -6
View File
@@ -7,10 +7,10 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css" />
<link rel="stylesheet" href="assets/css/start.css" />
<link rel="stylesheet" href="assets/css/aufgaben.css" />
<link rel="stylesheet" href="assets/css/profil.css" />
<link rel="stylesheet" href="assets/css/gate.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/start.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/profil.css?v=202608281303" />
</head>
<body class="start">
@@ -127,7 +127,7 @@
</main>
<script src="assets/js/kopf.js" defer></script>
<script src="assets/js/profil.js" defer></script>
<script src="assets/js/kopf.js?v=202608281303" defer></script>
<script src="assets/js/profil.js?v=202608281303" defer></script>
</body>
</html>
+6 -6
View File
@@ -7,10 +7,10 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css" />
<link rel="stylesheet" href="assets/css/start.css" />
<link rel="stylesheet" href="assets/css/aufgaben.css" />
<link rel="stylesheet" href="assets/css/report.css" />
<link rel="stylesheet" href="assets/css/gate.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/start.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/report.css?v=202608281303" />
</head>
<body class="start">
@@ -86,7 +86,7 @@
</main>
<script src="assets/js/kopf.js" defer></script>
<script src="assets/js/report.js" defer></script>
<script src="assets/js/kopf.js?v=202608281303" defer></script>
<script src="assets/js/report.js?v=202608281303" defer></script>
</body>
</html>
+6 -6
View File
@@ -7,10 +7,10 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css" />
<link rel="stylesheet" href="assets/css/start.css" />
<link rel="stylesheet" href="assets/css/aufgaben.css" />
<link rel="stylesheet" href="assets/css/scouting.css" />
<link rel="stylesheet" href="assets/css/gate.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/start.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/scouting.css?v=202608281303" />
</head>
<body class="start">
@@ -124,7 +124,7 @@
</div>
</div>
<script src="assets/js/kopf.js" defer></script>
<script src="assets/js/scouting.js" defer></script>
<script src="assets/js/kopf.js?v=202608281303" defer></script>
<script src="assets/js/scouting.js?v=202608281303" defer></script>
</body>
</html>
+4 -4
View File
@@ -7,8 +7,8 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css" />
<link rel="stylesheet" href="assets/css/start.css" />
<link rel="stylesheet" href="assets/css/gate.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/start.css?v=202608281303" />
</head>
<body class="start">
@@ -56,7 +56,7 @@
</p>
</main>
<script src="assets/js/kopf.js" defer></script>
<script src="assets/js/start.js" defer></script>
<script src="assets/js/kopf.js?v=202608281303" defer></script>
<script src="assets/js/start.js?v=202608281303" defer></script>
</body>
</html>
+6 -6
View File
@@ -7,10 +7,10 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css" />
<link rel="stylesheet" href="assets/css/start.css" />
<link rel="stylesheet" href="assets/css/aufgaben.css" />
<link rel="stylesheet" href="assets/css/startcheck.css" />
<link rel="stylesheet" href="assets/css/gate.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/start.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/startcheck.css?v=202608281303" />
</head>
<body class="start">
@@ -65,7 +65,7 @@
</main>
<script src="assets/js/kopf.js" defer></script>
<script src="assets/js/startcheck.js" defer></script>
<script src="assets/js/kopf.js?v=202608281303" defer></script>
<script src="assets/js/startcheck.js?v=202608281303" defer></script>
</body>
</html>
+6 -6
View File
@@ -7,10 +7,10 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#05070d" />
<link rel="icon" href="assets/img/favicon.png" />
<link rel="stylesheet" href="assets/css/gate.css" />
<link rel="stylesheet" href="assets/css/start.css" />
<link rel="stylesheet" href="assets/css/aufgaben.css" />
<link rel="stylesheet" href="assets/css/wissen.css" />
<link rel="stylesheet" href="assets/css/gate.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/start.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202608281303" />
<link rel="stylesheet" href="assets/css/wissen.css?v=202608281303" />
</head>
<body class="start">
@@ -145,7 +145,7 @@
</main>
<script src="assets/js/kopf.js" defer></script>
<script src="assets/js/wissen.js" defer></script>
<script src="assets/js/kopf.js?v=202608281303" defer></script>
<script src="assets/js/wissen.js?v=202608281303" defer></script>
</body>
</html>