diff --git a/server/pruef-video.mjs b/server/pruef-video.mjs index 23007ff9..20315ad9 100644 --- a/server/pruef-video.mjs +++ b/server/pruef-video.mjs @@ -421,6 +421,131 @@ melde("=== 5b. Wenn ein Video bei TikTok verschwindet ==="); void l1; } +/* ======================================================================= + 5c. DER KREISLAUF (Stufe 7) + + „Ein Video aus einem Wunsch zeigt den Namen." Wer sich etwas + gewuenscht hat, soll sehen, dass daraus etwas geworden ist. + + DER WICHTIGSTE TEIL IST DER ZWEITE: Der Titel der Quelle wird auf + der Kachel ANGEZEIGT. Eine Kette in die Content-Planung waere + deshalb kein Schoenheitsfehler, sondern ein Leck -- dort steht + Arbeit, die die Community nichts angeht. + ======================================================================= */ +melde(""); +melde("=== 5c. Aus einem Wunsch wird ein Video ==="); +{ + const db3 = new DatabaseSync(process.env.WORKSPACE_DB); + const nun = new Date().toISOString(); + const anlegen = (bereich, art, titel) => { + const { lastInsertRowid } = db3.prepare(` + INSERT INTO eintraege (bereich, art, titel, datum, status, erstellt, erstellt_von) + VALUES (?,?,?,?, 'offen', ?, (SELECT id FROM personen LIMIT 1))`) + .run(bereich, art, titel, nun.slice(0, 10), nun); + return Number(lastInsertRowid); + }; + const wunschId = anlegen("wunsch", "wunsch", "Bitte mal Casper beim Baden zeigen"); + /* Ein Eintrag aus der Content-Planung -- NICHT aus der Community. + Er ist der Koeder fuer die Leck-Pruefung weiter unten. */ + const internId = anlegen("content", "idee", "Interne Planung: Kampagne Q4"); + + /* ---- Der gute Weg ------------------------------------------------ */ + { + const a = await fetch(`${BASIS}/workspace/api/bereich/highlight/video`, { + method: "POST", headers: kopf, + body: JSON.stringify({ url: videoVon("dogfather0804", "7a"), aus: wunschId }), + }); + const r = await a.json().catch(() => ({})); + ok(a.status === 201 && r.aus === wunschId, + `ein Video zu einem Wunsch wird angenommen (${a.status})`); + + const e = (await liste()).find((x) => x.id === r.id); + ok(e?.aus?.id === wunschId, "der Eintrag kennt seinen Wunsch"); + ok(e?.aus?.titel === "Bitte mal Casper beim Baden zeigen", + `und zeigt dessen Titel (${e?.aus?.titel})`); + ok(!!e?.aus?.von_name, `und den Namen dessen, der ihn geaeussert hat (${e?.aus?.von_name})`); + } + + /* ---- DAS LECK, das es nicht geben darf --------------------------- */ + { + const a = await fetch(`${BASIS}/workspace/api/bereich/highlight/video`, { + method: "POST", headers: kopf, + body: JSON.stringify({ url: videoVon("dogfather0804", "7b"), aus: internId }), + }); + ok(a.status === 404, + `ein Eintrag aus der Content-Planung kommt NICHT als Quelle durch (${a.status})`); + const bleibt = db3.prepare( + "SELECT COUNT(*) AS n FROM eintraege WHERE aus_eintrag_id = ?").get(internId); + ok(bleibt.n === 0, `und es ist auch nichts entstanden (${bleibt.n})`); + } + { + const a = await fetch(`${BASIS}/workspace/api/bereich/highlight/video`, { + method: "POST", headers: kopf, + body: JSON.stringify({ url: videoVon("dogfather0804", "7c"), aus: 999999 }), + }); + ok(a.status === 404, `eine Nummer, die es nicht gibt, ebenfalls nicht (${a.status})`); + } + { + const a = await fetch(`${BASIS}/workspace/api/bereich/highlight/video`, { + method: "POST", headers: kopf, + body: JSON.stringify({ url: videoVon("dogfather0804", "7d"), aus: "irgendwas" }), + }); + ok(a.status === 400, `und etwas, das keine Nummer ist, wird abgewiesen (${a.status})`); + } + + /* ---- ZWEI WEGE, EIN ZIEL ----------------------------------------- */ + { + /* „Daraus ein Highlight machen" gibt es laenger. Wer ihn geht und + DANACH das Video einfuegt, darf kein zweites Highlight bekommen. */ + const wunsch2 = anlegen("wunsch", "wunsch", "Ein Video vom Gassigehen"); + const d = await fetch(`${BASIS}/workspace/api/bereich/wunsch/${wunsch2}/daraus`, { + method: "POST", headers: kopf, body: JSON.stringify({ nach: "highlight" }), + }); + const dr = await d.json().catch(() => ({})); + ok(d.status === 201 && !!dr.id, `erst „Daraus ein Highlight machen" (${d.status})`); + + const v = await fetch(`${BASIS}/workspace/api/bereich/highlight/video`, { + method: "POST", headers: kopf, + body: JSON.stringify({ url: videoVon("dogfather0804", "7e"), aus: wunsch2 }), + }); + const vr = await v.json().catch(() => ({})); + ok(v.status === 201 && vr.id === dr.id && vr.angehaengt === true, + `dann das Video -- es haengt sich an den vorhandenen Eintrag (#${vr.id} vs #${dr.id})`); + + const wieviele = db3.prepare( + "SELECT COUNT(*) AS n FROM eintraege WHERE aus_eintrag_id = ? AND bereich = 'highlight'") + .get(wunsch2); + ok(wieviele.n === 1, + `aus einem Wunsch ist GENAU EIN Highlight geworden, nicht zwei (${wieviele.n})`); + + const e2 = (await liste()).find((x) => x.id === dr.id); + ok(e2?.titel === "Ein Video vom Gassigehen", + `und der Titel bleibt der des Wunsches, nicht der von TikTok (${e2?.titel})`); + ok(!!e2?.quelle_url, "das Video ist trotzdem dran"); + + /* Noch einmal dasselbe: nichts Neues, kein Fehler. */ + const w = await fetch(`${BASIS}/workspace/api/bereich/highlight/video`, { + method: "POST", headers: kopf, + body: JSON.stringify({ url: videoVon("dogfather0804", "7f"), aus: wunsch2 }), + }); + const wr = await w.json().catch(() => ({})); + ok(w.status === 200 && wr.schon === true, + `ein zweites Video zum selben Wunsch aendert nichts (${w.status})`); + + /* Und die Wunschkarte weiss, dass es schon ein Video gibt -- + daran haengt, ob der Knopf noch angeboten wird. */ + const wl = await fetch(`${BASIS}/workspace/api/bereich/wunsch`, { headers: { cookie: keks } }); + const wliste = (await wl.json().catch(() => null))?.eintraege || []; + const karte = wliste.find((x) => x.id === wunsch2); + ok((karte?.wurde || []).some((z) => z.bereich === "highlight" && z.hat_video === true), + "die Wunschkarte sieht, dass daraus ein Highlight MIT Video geworden ist"); + const karte1 = wliste.find((x) => x.id === wunschId); + ok(!!karte1, "und der erste Wunsch steht auch noch in der Liste"); + } + + db3.close(); +} + /* ======================================================================= 6. UND ES ÜBERLEBT, WENN TIKTOK WEG IST ← der eigentliche Punkt ======================================================================= */ diff --git a/server/workspace-bereiche.js b/server/workspace-bereiche.js index 472436e4..49f3388a 100644 --- a/server/workspace-bereiche.js +++ b/server/workspace-bereiche.js @@ -1293,13 +1293,23 @@ bereicheRouter.get("/workspace/api/bereich/:bereich", (req, res) => { } } const p3 = eintraege.map(() => "?").join(", "); + /* `quelle_url` kommt mit, damit die Karte unterscheiden kann: + Aus dem Wunsch ist ein Highlight geworden -- hat es auch schon + ein Video? Sonst waere der Knopf „Video dazu" entweder immer da + (auch wenn es laengst eins gibt) oder nie (sobald irgendein + Highlight daraus entstanden ist). */ const draus = db().prepare( - `SELECT id, bereich, titel, aus_eintrag_id FROM eintraege + `SELECT id, bereich, titel, aus_eintrag_id, quelle_url FROM eintraege WHERE aus_eintrag_id IN (${p3})`).all(...eintraege.map((e) => e.id)); const jeQuelle = new Map(); for (const z of draus) { if (!jeQuelle.has(z.aus_eintrag_id)) jeQuelle.set(z.aus_eintrag_id, []); - jeQuelle.get(z.aus_eintrag_id).push({ id: z.id, bereich: z.bereich, titel: z.titel }); + jeQuelle.get(z.aus_eintrag_id).push({ + id: z.id, bereich: z.bereich, titel: z.titel, + /* Nur ja/nein -- die Adresse selbst gehoert nicht in eine + Liste, die nur „was wurde daraus" beantworten soll. */ + hat_video: !!z.quelle_url, + }); } for (const e of eintraege) e.wurde = jeQuelle.get(e.id) || []; diff --git a/server/workspace-video.js b/server/workspace-video.js index d3ec8179..9447b9f5 100644 --- a/server/workspace-video.js +++ b/server/workspace-video.js @@ -49,6 +49,7 @@ import { KANAELE, kanalVonHandle, TREFF_BRETTER, } from "./workspace.js"; import { darfSchreiben } from "./workspace-treff.js"; +import { sichtbar } from "./workspace-bereiche.js"; import { bildArtVon } from "./workspace-dateien.js"; export const videoRouter = express.Router(); @@ -170,6 +171,36 @@ videoRouter.post("/workspace/api/bereich/:bereich/video", async (req, res) => { return res.status(400).json({ fehler: "Das ist keine TikTok-Adresse." }); } + /* ---- DER KREISLAUF (Stufe 7, 18.09.2026) -------------------------- + + „Ein Video aus einem Wunsch zeigt den Namen." Wer sich etwas + gewuenscht hat, soll sehen, dass daraus etwas geworden ist -- + das ist der ganze Sinn eines Wunschbretts. Ohne diese Verbindung + ist die Galerie eine Sammlung, und der Wunsch von letzter Woche + bleibt unbeantwortet im Raum stehen. + + DIE QUELLE MUSS EIN COMMUNITY-BRETT SEIN. Eine Kette, die in die + Content-Planung eines Creators fuehrt, waere ein Leck: Dort steht + Arbeit, die die Community nichts angeht -- und der TITEL der + Quelle wird auf der Kachel angezeigt. Dieselbe Regel wie beim + „Daraus"-Knopf, aus demselben Grund. */ + const aus = Number(req.body?.aus || 0); + let ausQuelle = null; + if (req.body?.aus !== undefined && req.body?.aus !== null && req.body?.aus !== "") { + if (!Number.isInteger(aus) || aus <= 0) { + return res.status(400).json({ fehler: "Die Quelle ist keine Nummer." }); + } + if (!sichtbar(req.person)) return res.status(404).json({ fehler: "nicht_gefunden" }); + ausQuelle = db().prepare( + "SELECT id, bereich, titel FROM eintraege WHERE id = ?").get(aus); + /* Nicht da, oder kein Community-Brett: dieselbe Antwort. Sonst + liesse sich durch Ausprobieren herausfinden, welche Nummern es + gibt und in welchem Bereich sie liegen. */ + if (!ausQuelle || !TREFF_BRETTER.includes(ausQuelle.bereich)) { + return res.status(404).json({ fehler: "nicht_gefunden" }); + } + } + /* SCHON DA? Derselbe Link zweimal soll dasselbe Ergebnis geben, nicht zwei Eintraege. Wer den Knopf zweimal drueckt, meint nicht zwei Videos. */ @@ -177,6 +208,22 @@ videoRouter.post("/workspace/api/bereich/:bereich/video", async (req, res) => { "SELECT id FROM eintraege WHERE quelle_url = ? AND bereich = ?").get(url, bereich); if (schon) return res.status(200).json({ id: schon.id, schon: true }); + /* IST AUS DIESEM WUNSCH SCHON ETWAS GEWORDEN? + + Den Weg „Daraus ein Highlight machen" gibt es laenger als diesen + hier. Wer ihn gegangen ist und DANACH das Video einfuegt, bekaeme + sonst ein ZWEITES Highlight aus demselben Wunsch -- zwei Wege zum + selben Ziel, die nichts voneinander wissen. Das Video wird + deshalb an den vorhandenen Eintrag gehaengt. */ + let anhaengenAn = 0; + if (ausQuelle) { + const da = db().prepare( + "SELECT id, quelle_url FROM eintraege WHERE aus_eintrag_id = ? AND bereich = ?") + .get(aus, bereich); + if (da?.quelle_url) return res.status(200).json({ id: da.id, schon: true }); + if (da) anhaengenAn = da.id; + } + const auskunft = await videoAuskunft(url); if (auskunft.fehler) return res.status(502).json({ fehler: auskunft.fehler }); @@ -194,14 +241,27 @@ videoRouter.post("/workspace/api/bereich/:bereich/video", async (req, res) => { const jetzt = new Date().toISOString(); const arten = { highlight: "clip", anschlag: "hinweis", treff: "lob" }; - const { lastInsertRowid } = db().prepare(` - INSERT INTO eintraege - (bereich, art, titel, datum, status, erstellt, erstellt_von, - quelle_url, quelle_kanal, quelle_geholt_am) - VALUES (?,?,?,?, 'offen', ?,?,?,?,?)`) - .run(bereich, arten[bereich] || "clip", auskunft.titel, jetzt.slice(0, 10), - jetzt, req.person.id, url, auskunft.kanal, jetzt); - const eintragId = Number(lastInsertRowid); + let eintragId; + if (anhaengenAn) { + /* DER TITEL WIRD NICHT UEBERSCHRIEBEN. Er stammt vom Wunsch und + damit von einem Menschen; der von TikTok ist eine Bildunter- + schrift mit Schlagwoertern. Wer den ersetzte, naehme dem + Eintrag genau das, was ihn mit dem Wunsch verbindet. */ + db().prepare(`UPDATE eintraege SET quelle_url = ?, quelle_kanal = ?, + quelle_geholt_am = ?, geaendert = ? + WHERE id = ?`) + .run(url, auskunft.kanal, jetzt, jetzt, anhaengenAn); + eintragId = anhaengenAn; + } else { + const { lastInsertRowid } = db().prepare(` + INSERT INTO eintraege + (bereich, art, titel, datum, status, erstellt, erstellt_von, + quelle_url, quelle_kanal, quelle_geholt_am, aus_eintrag_id) + VALUES (?,?,?,?, 'offen', ?,?,?,?,?,?)`) + .run(bereich, arten[bereich] || "clip", auskunft.titel, jetzt.slice(0, 10), + jetzt, req.person.id, url, auskunft.kanal, jetzt, ausQuelle ? aus : null); + eintragId = Number(lastInsertRowid); + } if (abgelegt) { db().prepare(` @@ -215,11 +275,12 @@ videoRouter.post("/workspace/api/bereich/:bereich/video", async (req, res) => { protokolliere("video_eingelesen", { personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req), - detail: `${auskunft.kanal} ${url}`.slice(0, 120), + detail: `${auskunft.kanal} ${url}${ausQuelle ? ` (aus #${aus})` : ""}`.slice(0, 120), }); res.status(201).json({ id: eintragId, schon: false, kanal: auskunft.kanal, titel: auskunft.titel, cover: !!abgelegt || undefined, + ...(ausQuelle ? { aus: aus, angehaengt: !!anhaengenAn } : {}), }); } catch (fehler) { /* GING ETWAS SCHIEF, NACHDEM DIE DATEI SCHON LAG: wegraeumen. Eine diff --git a/workspace/app.webmanifest b/workspace/app.webmanifest index 35a1f0a8..272e8a3d 100644 --- a/workspace/app.webmanifest +++ b/workspace/app.webmanifest @@ -22,25 +22,25 @@ }, "icons": [ { - "src": "/assets/img/app-symbole/workspace-192.png?v=202609181607", + "src": "/assets/img/app-symbole/workspace-192.png?v=202609181700", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-512.png?v=202609181607", + "src": "/assets/img/app-symbole/workspace-512.png?v=202609181700", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609181607", + "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609181700", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609181607", + "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609181700", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/workspace/assets/css/bereich.css b/workspace/assets/css/bereich.css index 6443391a..ad2c5290 100644 --- a/workspace/assets/css/bereich.css +++ b/workspace/assets/css/bereich.css @@ -1396,3 +1396,40 @@ color: color-mix(in srgb, var(--akzent) 42%, #ffffff); } +/* ---- „Video dazu einfügen" an einer Wunschkarte (Stufe 7) ---------- + Eine Zeile, kein Kasten mit Rahmen: Es ist ein Zwischenschritt, der + gleich wieder verschwindet. Ein aufwendig gestaltetes Formular an + dieser Stelle sähe aus, als gehörte es dauerhaft zur Karte. */ +.videodazu { + display: flex; + flex-wrap: wrap; + gap: .5rem; + align-items: center; + margin-top: .7rem; + padding-top: .7rem; + border-top: 1px solid rgba(140, 160, 185, .18); +} +.videodazu__feld { + flex: 1 1 15rem; + min-width: 0; + padding: .45rem .6rem; + border: 1px solid rgba(140, 160, 185, .35); + border-radius: .55rem; + background: rgba(10, 18, 30, .55); + color: #e8f1fa; + font-size: .9rem; +} +.videodazu__feld:focus-visible { + outline: 2px solid #6ea8d8; + outline-offset: 1px; +} +.videodazu__senden { flex: 0 0 auto; } +/* Die Rückmeldung bekommt eine eigene Zeile, damit eine lange + Fehlermeldung das Feld nicht aus der Zeile drückt. */ +.videodazu__sagt { + flex: 1 0 100%; + margin: 0; + min-height: 1.1em; + color: #9fb3c9; + font-size: .84rem; +} diff --git a/workspace/assets/js/bereich.js b/workspace/assets/js/bereich.js index 310157a5..37acf590 100644 --- a/workspace/assets/js/bereich.js +++ b/workspace/assets/js/bereich.js @@ -869,6 +869,36 @@ })); } + /* ---- VIDEO DAZU (Stufe 7, 18.09.2026) -------------------------- + + „Ein Video aus einem Wunsch zeigt den Namen." Der Knopf steht + AN DEM WUNSCH -- aus demselben Grund wie die Kettenknoepfe + darueber: Wer oben im Formular einen Link einfuegt, denkt nicht + an den Wunsch von letzter Woche. Hier denkt er an nichts + anderes. + + Er verschwindet, sobald aus dem Wunsch ein Highlight MIT Video + geworden ist. Steht dort schon ein Highlight ohne Video, bleibt + er -- der Server haengt das Video dann an den vorhandenen + Eintrag, statt ein zweites anzulegen. */ + if (VIDEO_AUS.includes(bereich) + && !(e.wurde || []).some((w) => w.bereich === 'highlight' && w.hat_video)) { + knoepfe.append(tuKnopf('Video dazu einfügen', () => { + /* Schon offen? Dann wieder zu. Ein Knopf, der beim zweiten + Druecken ein zweites Feld baut, ist ein Fehler, den man + erst sieht, wenn drei davon dastehen. */ + const alt = k.querySelector('.videodazu'); + if (alt) { alt.remove(); return; } + /* DIREKT UNTER DIE KNOPFREIHE, nicht ans Ende der Karte. + Zuerst stand hier `k.append` -- und das Bildschirmfoto zeigte + das Feld UNTER der Fusszeile („18.09.2026 - von Lena"), + getrennt von dem Knopf, den man gerade gedrueckt hat. Eine + Zahl haette das nie gemeldet: Das Feld war da, es reagierte, + alle Pruefungen gruen. */ + knoepfe.insertAdjacentElement('afterend', videoDazuFeld(e)); + })); + } + /* VIER ZUSTAENDE STATT EINES UMSCHALTERS (17.09.2026). "erledigt / wieder offen" reicht fuer einen Vorfall. Ein Wunsch @@ -1095,6 +1125,56 @@ Link einfuegen, den Rest macht der Server. Nur bei den Highlights: Dort gehoeren Clips hin, und ein Feld an jedem anderen Brett waere eine Einladung, sie ueberall hinzuschreiben. */ + /* Aus welchen Brettern heraus ein Video eingefuegt werden kann. + „mitmachen" und „regeln" stehen bewusst nicht dabei: Dort geht es + nicht um Inhalte, aus denen ein Clip wird. */ + const VIDEO_AUS = ['wunsch', 'ansteht']; + + /** Das Eingabefeld, das direkt an der Karte aufgeht. */ + function videoDazuFeld(e) { + const kasten = el('div', 'videodazu'); + const feld = el('input', 'videodazu__feld'); + feld.type = 'url'; + feld.placeholder = 'TikTok-Link einfügen'; + /* Ohne Beschriftung waere das Feld fuer eine Vorleseansage ein + Kaestchen ohne Bedeutung. Sichtbar ist der Knopftext daneben. */ + feld.setAttribute('aria-label', 'TikTok-Link zu diesem Eintrag'); + const senden = el('button', 'schritt videodazu__senden', 'Übernehmen'); + senden.type = 'button'; + const sagt = el('p', 'videodazu__sagt'); + + senden.addEventListener('click', async () => { + const url = feld.value.trim(); + if (!url) { feld.focus(); return; } + senden.disabled = true; + sagt.textContent = 'Wird geholt …'; + try { + const a = await hole('/workspace/api/bereich/highlight/video', { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ url, aus: e.id }), + }); + const r = await a.json().catch(() => ({})); + if (!a.ok) { + /* Der Grund des Servers, nicht „Ging nicht" -- er weiss, ob + der Account fremd war oder die Adresse keine ist. */ + sagt.textContent = r.fehler || 'Ging nicht.'; + senden.disabled = false; + return; + } + await laden(); + } finally { senden.disabled = false; } + }); + feld.addEventListener('keydown', (ev) => { + if (ev.key === 'Enter') { ev.preventDefault(); senden.click(); } + }); + + kasten.append(feld, senden, sagt); + /* Der Fokus wandert ins Feld: Wer den Knopf gedrueckt hat, will + tippen, nicht erst noch zielen. */ + setTimeout(() => feld.focus(), 0); + return kasten; + } + function videoFeldZeigen() { const ziel = $('videoholen'); if (!ziel) return; diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index a3d860e5..5ad1139b 100644 --- a/workspace/aufgaben.html +++ b/workspace/aufgaben.html @@ -12,19 +12,19 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - + + + + + + - + @@ -291,10 +291,10 @@ - - - - - + + + + + diff --git a/workspace/automation.html b/workspace/automation.html index 1de1580b..ee5e7a8c 100644 --- a/workspace/automation.html +++ b/workspace/automation.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -168,10 +168,10 @@ - - - - - + + + + + diff --git a/workspace/befinden.html b/workspace/befinden.html index 4f043935..bc6144e6 100644 --- a/workspace/befinden.html +++ b/workspace/befinden.html @@ -7,14 +7,14 @@ - - - - - - - - + + + + + + + + @@ -105,9 +105,9 @@ - - - - + + + + diff --git a/workspace/bereich.html b/workspace/bereich.html index d0e4cd83..8bc22168 100644 --- a/workspace/bereich.html +++ b/workspace/bereich.html @@ -12,24 +12,24 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - + + + + + + - - + + - + @@ -243,14 +243,14 @@ - - - - - - - - - + + + + + + + + + diff --git a/workspace/bewerben.html b/workspace/bewerben.html index 85ef4d21..a3e8f911 100644 --- a/workspace/bewerben.html +++ b/workspace/bewerben.html @@ -7,17 +7,17 @@ - - - - - - - - - + + + + + + + + + - + @@ -107,10 +107,10 @@ - - - - - + + + + + diff --git a/workspace/bewerbungen.html b/workspace/bewerbungen.html index 77e48757..3a8aeb7d 100644 --- a/workspace/bewerbungen.html +++ b/workspace/bewerbungen.html @@ -7,16 +7,16 @@ - - - - - - - - + + + + + + + + - + @@ -80,10 +80,10 @@ - - - - - + + + + + diff --git a/workspace/calls.html b/workspace/calls.html index a4660570..6d972c54 100644 --- a/workspace/calls.html +++ b/workspace/calls.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -89,11 +89,11 @@ - - - - - - + + + + + + diff --git a/workspace/chat.html b/workspace/chat.html index 4db3b2c0..21ed90f5 100644 --- a/workspace/chat.html +++ b/workspace/chat.html @@ -12,26 +12,26 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - + + + + - - - + + + - + @@ -341,15 +341,15 @@ - - - - + + + + - - + + diff --git a/workspace/content.html b/workspace/content.html index 47a0c7c6..ae843fa2 100644 --- a/workspace/content.html +++ b/workspace/content.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -179,12 +179,12 @@ - - - - - - - + + + + + + + diff --git a/workspace/crew-index.html b/workspace/crew-index.html index 232763cf..4c62a9ab 100644 --- a/workspace/crew-index.html +++ b/workspace/crew-index.html @@ -36,15 +36,15 @@ - - - + + + - + @@ -339,6 +339,6 @@ - + diff --git a/workspace/crew.webmanifest b/workspace/crew.webmanifest index 3273f7fd..ab3eaf68 100644 --- a/workspace/crew.webmanifest +++ b/workspace/crew.webmanifest @@ -22,25 +22,25 @@ }, "icons": [ { - "src": "/assets/img/app-symbole/crew-192.png?v=202609181607", + "src": "/assets/img/app-symbole/crew-192.png?v=202609181700", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/crew-512.png?v=202609181607", + "src": "/assets/img/app-symbole/crew-512.png?v=202609181700", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609181607", + "src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609181700", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609181607", + "src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609181700", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/workspace/dateien.html b/workspace/dateien.html index 77ff6476..008076e8 100644 --- a/workspace/dateien.html +++ b/workspace/dateien.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -108,10 +108,10 @@ - - - - - + + + + + diff --git a/workspace/entwicklung.html b/workspace/entwicklung.html index 18acd7ef..8d1b1fc2 100644 --- a/workspace/entwicklung.html +++ b/workspace/entwicklung.html @@ -7,14 +7,14 @@ - - - - - - - - + + + + + + + + @@ -102,9 +102,9 @@ - - - - + + + + diff --git a/workspace/index.html b/workspace/index.html index 7490d393..af44fc77 100644 --- a/workspace/index.html +++ b/workspace/index.html @@ -30,9 +30,9 @@ - - - + + + @@ -401,6 +401,6 @@ - + diff --git a/workspace/kalender.html b/workspace/kalender.html index 9062e557..1dc41261 100644 --- a/workspace/kalender.html +++ b/workspace/kalender.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -377,11 +377,11 @@ - - - - - - + + + + + + diff --git a/workspace/leistung.html b/workspace/leistung.html index 4011e04f..e708dabe 100644 --- a/workspace/leistung.html +++ b/workspace/leistung.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -444,10 +444,10 @@ - - - - - + + + + + diff --git a/workspace/personen.html b/workspace/personen.html index d76a275d..a0ebf863 100644 --- a/workspace/personen.html +++ b/workspace/personen.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -423,10 +423,10 @@ - - - - - + + + + + diff --git a/workspace/profil.html b/workspace/profil.html index 8d82b928..683060aa 100644 --- a/workspace/profil.html +++ b/workspace/profil.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -214,11 +214,11 @@ - - - - - - + + + + + + diff --git a/workspace/rechte.html b/workspace/rechte.html index 3a5eec43..43ba6dc0 100644 --- a/workspace/rechte.html +++ b/workspace/rechte.html @@ -7,14 +7,14 @@ - - - - - - - - + + + + + + + + @@ -134,9 +134,9 @@ Beim ersten Anlauf fehlte die Zeile auf genau diesen beiden neuen Seiten. Gefunden hat das nicht das Auge, sondern pruef-css-klassen, die seit dem 07.09. jede Seite danach absucht. --> - - - - + + + + diff --git a/workspace/report.html b/workspace/report.html index b9fcd167..85062b85 100644 --- a/workspace/report.html +++ b/workspace/report.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -106,11 +106,11 @@ - - - - - - + + + + + + diff --git a/workspace/scouting.html b/workspace/scouting.html index e54968a9..e4ab2ca1 100644 --- a/workspace/scouting.html +++ b/workspace/scouting.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -173,10 +173,10 @@ - - - - - + + + + + diff --git a/workspace/start.html b/workspace/start.html index b00f9d44..25f7fac7 100644 --- a/workspace/start.html +++ b/workspace/start.html @@ -12,21 +12,21 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - + + + + - - + + - + @@ -674,10 +674,10 @@

- - - - - + + + + + diff --git a/workspace/startcheck.html b/workspace/startcheck.html index fc8be5bb..81c946cc 100644 --- a/workspace/startcheck.html +++ b/workspace/startcheck.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -85,11 +85,11 @@ - - - - - - + + + + + + diff --git a/workspace/steckbrief.html b/workspace/steckbrief.html index 6e0e777b..ba7d7e3b 100644 --- a/workspace/steckbrief.html +++ b/workspace/steckbrief.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -146,11 +146,11 @@ - - - - - - + + + + + + diff --git a/workspace/talente.html b/workspace/talente.html index f365f790..30102ac1 100644 --- a/workspace/talente.html +++ b/workspace/talente.html @@ -7,18 +7,18 @@ - - - - - - + + + + + + - - - + + + @@ -111,9 +111,9 @@ - - - - + + + + diff --git a/workspace/team.html b/workspace/team.html index b29657d7..89fc7485 100644 --- a/workspace/team.html +++ b/workspace/team.html @@ -7,20 +7,20 @@ - - - - - - - + + + + + + + - + @@ -110,10 +110,10 @@ - - - - - + + + + + diff --git a/workspace/teamlage.html b/workspace/teamlage.html index bd5d22d5..79cb6882 100644 --- a/workspace/teamlage.html +++ b/workspace/teamlage.html @@ -7,20 +7,20 @@ - - - - - - - + + + + + + + - + - - - - + + + + diff --git a/workspace/teilen.html b/workspace/teilen.html index 59c860b2..bea02fb2 100644 --- a/workspace/teilen.html +++ b/workspace/teilen.html @@ -7,13 +7,13 @@ - - - - - - - + + + + + + + @@ -112,9 +112,9 @@ - - - - + + + + diff --git a/workspace/treff-moderation.html b/workspace/treff-moderation.html index 10dd3a9f..85b8a5ea 100644 --- a/workspace/treff-moderation.html +++ b/workspace/treff-moderation.html @@ -7,14 +7,14 @@ - - - - - - - - + + + + + + + + @@ -126,9 +126,9 @@ - - - - + + + + diff --git a/workspace/treff-regeln.html b/workspace/treff-regeln.html index d56f5362..05d61055 100644 --- a/workspace/treff-regeln.html +++ b/workspace/treff-regeln.html @@ -7,18 +7,18 @@ - - - - - - - + + + + + + + - + @@ -211,10 +211,10 @@ Beim ersten Anlauf fehlte die Zeile auf genau diesen beiden neuen Seiten. Gefunden hat das nicht das Auge, sondern pruef-css-klassen, die seit dem 07.09. jede Seite danach absucht. --> - - - - - + + + + + diff --git a/workspace/uebersicht.html b/workspace/uebersicht.html index 0dea434b..a9016832 100644 --- a/workspace/uebersicht.html +++ b/workspace/uebersicht.html @@ -12,22 +12,22 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - + + + + - - - + + + - + @@ -149,11 +149,11 @@ - + - - - - + + + + diff --git a/workspace/wissen.html b/workspace/wissen.html index c71aec0c..b51cd7c9 100644 --- a/workspace/wissen.html +++ b/workspace/wissen.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -190,10 +190,10 @@ - - - - - + + + + +