From d69eb344481cdc7edab8df05f5232db3bfe58c35 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Thu, 17 Sep 2026 14:08:15 +0200 Subject: [PATCH] Die Kanalzeile -- und der Kasten, der auf dem Chili-Motiv stand MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SCHRITT 3 AUS DEM VIDEO-PLAN: DIE KANALZEILE Filipe: "fuer meinen Account, dogfather, dann dogfather clips und hasidog." Sobald Videos aus drei Kanaelen nebeneinander liegen, ist die erste Frage nicht "welche Art", sondern "von welchem Kanal". ALS ZWEITE DIMENSION, nicht als weiterer Wert in derselben Leiste. Der vorhandene Filter ist EIN Wert (Alle / Nur offene / die Arten); wer den Kanal dort hineinpresst, kann "nur offene Clips von HasiDog" nicht mehr fragen. Zwei getrennte Reihen zeigen ausserdem, dass es zwei Fragen sind -- in einer Zeile klickt man das zweite und wundert sich, warum das erste wieder aus ist. SIE ERSCHEINT NUR AB ZWEI KANAELEN, und die Kanaele werden aus den vorhandenen Eintraegen abgeleitet statt aufgezaehlt. Auf einem Brett ohne Videos steht keine leere Leiste; bei einem Kanal steht kein Filter, der nichts tut. Ein Knopf, bei dem nichts passiert, kostet das Vertrauen in den naechsten. Die drei Toene sind dieselben wie am Zeichen auf der Karte -- wer "DogFather Clips" in Tuerkis filtert, findet darunter Karten mit tuerkisem Zeichen. Ungewaehlt bleiben sie gedeckt: drei bunte Knoepfe nebeneinander waeren eine Ampel, und eine Ampel sagt "gut/schlecht", wo nur "welcher Kanal" gemeint ist. DER KASTEN, DER AUF DEM CHILI-MOTIV STAND Der leere Zustand der Talente-Seite von heute Vormittag hatte nur einen gestrichelten Rand. Solange dort EIN Satz stand, ging das; jetzt stehen dort drei Absaetze, fuenf Merkmale und zwei Knoepfe -- und das Spicy-Media-Motiv mit Chilischoten und Neonlicht schien voll durch den Text. Lesbar war das nicht, augenschonend erst recht nicht. DIE WARNUNG STAND 70 ZEILEN WEITER UNTEN in derselben Datei, bei .t-fuss, und beschreibt exakt diesen Fehler ("Sie stand als einziger Text der Seite direkt auf der Buehne"). Ich habe sie gelesen und bin trotzdem hineingelaufen. Ein Kommentar, der vor einem Fehler warnt, verhindert ihn nicht -- gefunden hat es erst das Bildschirmfoto. GESEHEN STATT GEZAEHLT (server/bild-stufen.mjs) Stufenleiter und Talente-Seite waren heute Vormittag nur an Zahlen geprueft. Das Bild zeigte: die Leiste sitzt sauber in einer Zeile (1400 px) und in vier auf 412 px, nichts laeuft ueber den Rand, keine Konsolenfehler -- und den Kasten ohne Grund. DIE PRUEFUNG (server/pruef-kanalzeile.mjs, 14 Pruefungen) Sie prueft nicht nur, DASS gefiltert wird, sondern dass das Richtige uebrig bleibt: "weniger" allein waere auch erfuellt, wenn der Filter irgendetwas wegwirft. Dazu der zweite Klick (hebt auf) und Abschnitt 4: Kanal UND Art zusammen -- der eigentliche Grund fuer zwei Reihen. ZWEI GEGENPROBEN: a) Kanalfilter ausgebaut -> 3 Pruefungen rot b) Zeile immer anzeigen -> 1 rot, genau der Ein-Kanal-Fall UND ZWEI FEHLER IN DER PRUEFUNG SELBST, beide von mir: * Der Kartenselektor traf nichts, und die Messung meldete "0 -> 0". Das sah aus wie ein Ergebnis und war keins. * Beim Berichtigen machte String.replace aus "$$" ein "$" -- seite.$$() wurde zu seite.$(). Der Fehler steht woertlich in den Hausregeln und hat trotzdem zugeschlagen. Aufgefallen ist er nur, weil "undefined" in der Ausgabe stand; "0" waere durchgegangen. * Und eine verlorene Escaping-Ebene machte aus /\s+/ ein /s+/ -- die Pruefung ersetzte jedes "s" durch ein Leerzeichen und las "Ca per chläft im Wä chekorb". Sie wurde rot, und das war richtig. Stempel 202609171407. Co-Authored-By: Claude Opus 5 --- server/bild-stufen.mjs | 153 ++++++++++++++++ server/pruef-kanalzeile.mjs | 259 +++++++++++++++++++++++++++ workspace/app.webmanifest | 8 +- workspace/assets/css/bereich.css | 53 ++++++ workspace/assets/css/entwicklung.css | 25 ++- workspace/assets/js/bereich.js | 57 +++++- workspace/aufgaben.html | 24 +-- workspace/automation.html | 26 +-- workspace/befinden.html | 24 +-- workspace/bereich.html | 34 ++-- workspace/calls.html | 28 +-- workspace/chat.html | 26 +-- workspace/content.html | 30 ++-- workspace/crew-index.html | 10 +- workspace/crew.webmanifest | 8 +- workspace/dateien.html | 26 +-- workspace/entwicklung.html | 24 +-- workspace/index.html | 8 +- workspace/kalender.html | 28 +-- workspace/leistung.html | 26 +-- workspace/personen.html | 26 +-- workspace/profil.html | 28 +-- workspace/rechte.html | 24 +-- workspace/report.html | 28 +-- workspace/scouting.html | 26 +-- workspace/start.html | 24 +-- workspace/startcheck.html | 28 +-- workspace/steckbrief.html | 28 +-- workspace/talente.html | 24 +-- workspace/team.html | 26 +-- workspace/teamlage.html | 24 +-- workspace/treff-moderation.html | 24 +-- workspace/treff-regeln.html | 26 +-- workspace/uebersicht.html | 26 +-- workspace/wissen.html | 26 +-- 35 files changed, 903 insertions(+), 362 deletions(-) create mode 100644 server/bild-stufen.mjs create mode 100644 server/pruef-kanalzeile.mjs diff --git a/server/bild-stufen.mjs b/server/bild-stufen.mjs new file mode 100644 index 00000000..1274f1d3 --- /dev/null +++ b/server/bild-stufen.mjs @@ -0,0 +1,153 @@ +/* Bild der Stufenleiter und der Talente-Seite (17.09.2026) + + Beides wurde heute gebaut und bisher nur an Zahlen geprueft. Die + Hausregel dazu ist eindeutig: Ein gruener Haken sagt nicht, wie es + aussieht. Der Startkatalog hat am selben Tag den Countdown von + 458 px auf 1488 px geschoben, ohne dass eine einzige Pruefung rot + wurde -- gesehen habe ich es im Bildschirmfoto. + + Aufrufen mit: node server/bild-stufen.mjs +*/ +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const ordner = mkdtempSync(join(tmpdir(), "ws-bild-")); +process.env.WORKSPACE_DB = join(ordner, "workspace.db"); +const { portMussFreiSein } = await import("./helfer-port.mjs"); +await portMussFreiSein(4331, "das Bildwerkzeug"); +process.env.PORT = "4331"; +process.env.SITE_ACCESS_SECRET = "lokaler-test"; +process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; + +const express = (await import("express")).default; +const ec = express.response.cookie; +express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +await import("./index.js"); +await new Promise((r) => setTimeout(r, 1200)); +const BASIS = "http://127.0.0.1:4331"; + +const { DatabaseSync } = await import("node:sqlite"); +const { scryptSync, randomBytes } = await import("node:crypto"); +for (let i = 0; i < 60; i++) { + try { + const p = new DatabaseSync(process.env.WORKSPACE_DB); + try { p.prepare("SELECT 1 FROM personen LIMIT 1").get(); break; } finally { p.close(); } + } catch { await new Promise((r) => setTimeout(r, 250)); } +} +const d = new DatabaseSync(process.env.WORKSPACE_DB); +const j = new Date().toISOString(); +function anlegen(name, rolle, code) { + const salt = randomBytes(16).toString("hex"); + const hash = scryptSync(code, salt, 64, { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex"); + d.prepare("INSERT INTO personen (name,rolle,code_hash,code_salt,code_n,aktiv,erstellt) VALUES (?,?,?,?,?,1,?)") + .run(name, rolle, hash, salt, 32768, j); + return d.prepare("SELECT last_insert_rowid() AS id").get().id; +} +anlegen("Filipe", "admin", "CODE-DOGI-0001"); +anlegen("Diene", "modi", "CODE-MODI-0001"); +d.close(); + +const pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); +const browser = await pw.chromium.launch(); +const ctx = await browser.newContext({ viewport: { width: 1400, height: 1000 }, deviceScaleFactor: 2 }); +const seite = await ctx.newPage(); +const fehlerAufSeite = []; +seite.on("console", (m) => { if (m.type() === "error") fehlerAufSeite.push(m.text()); }); +seite.on("pageerror", (f) => fehlerAufSeite.push(String(f))); + +await seite.goto(BASIS + "/workspace/", { waitUntil: "networkidle" }); +await seite.click('.rolle[data-rolle="admin"]'); +await seite.fill("#code", "CODE-DOGI-0001"); +await seite.click("button[type=submit]"); +await seite.waitForURL("**/start.html", { timeout: 25000 }); + +/* ---- 1. Die Entwicklungsseite mit der Stufenleiter ------------------- */ +await seite.goto(BASIS + "/workspace/entwicklung.html", { waitUntil: "networkidle" }); +await seite.waitForTimeout(1800); + +/* Eine Person waehlen, damit die Karte mit den Bloecken erscheint. */ +const person = await seite.$(".e-person, .p-kachel, [data-person-id]"); +if (person) { await person.click(); await seite.waitForTimeout(1500); } + +const leiste = await seite.$(".e-stufen"); +console.log("\n=== 1. Die Stufenleiter ==="); +console.log(" vorhanden:", !!leiste); +if (leiste) { + const knoepfe = await seite.$$eval(".e-stufen__knopf", (ns) => ns.map((n) => ({ + text: n.textContent.trim().replace(/\s+/g, " "), + breite: Math.round(n.getBoundingClientRect().width), + gewaehlt: n.getAttribute("aria-pressed"), + }))); + for (const k of knoepfe) console.log(` ${String(k.breite).padStart(4)} px ${k.gewaehlt === "true" ? "[x]" : "[ ]"} ${k.text}`); + + /* WIE VIELE ZEILEN BRAUCHT DIE LEISTE? Bei sechs Knoepfen in einer + Leiste, die schon einen "Alle aufklappen"-Knopf enthaelt, ist das + die Frage. Gezaehlt ueber die verschiedenen Oberkanten -- nicht + ueber die Hoehe geteilt durch etwas. */ + const zeilen = await seite.$$eval(".e-stufen__knopf", + (ns) => new Set(ns.map((n) => Math.round(n.getBoundingClientRect().top))).size); + console.log(" Zeilen:", zeilen); + + await leiste.scrollIntoViewIfNeeded(); + await seite.waitForTimeout(400); + const kasten = await seite.$(".e-bloecke__leiste"); + await (kasten || leiste).screenshot({ path: "server/bild-stufenleiste.png" }); + + /* ---- Und nach einem Klick auf "Fortgeschritten" ------------------- */ + const fort = await seite.$('.e-stufen__knopf[data-erwartung="fort"]'); + if (fort) { + await fort.click(); + await seite.waitForTimeout(1200); + const koepfe = await seite.$$eval(".e-block", (ns) => ns.map((n) => ({ + name: n.querySelector(".e-block__name")?.textContent?.trim(), + stand: n.querySelector(".e-block__stand")?.textContent?.trim(), + }))); + console.log("\n Nach Klick auf „Fortgeschritten“:"); + for (const k of koepfe) console.log(` ${String(k.name).padEnd(28)} ${k.stand}`); + await seite.screenshot({ path: "server/bild-stufen-gefiltert.png", fullPage: false }); + } +} + +/* ---- 2. Die Talente-Seite, leer -------------------------------------- */ +await seite.goto(BASIS + "/workspace/talente.html", { waitUntil: "networkidle" }); +await seite.waitForTimeout(1800); +console.log("\n=== 2. Die Talente-Seite ==="); +const leer = await seite.$(".t-leer"); +console.log(" leerer Zustand:", !!leer); +if (leer) { + const merkmale = await seite.$$eval(".t-leer__merkmal", + (ns) => ns.map((n) => n.textContent.trim().replace(/\s+/g, " "))); + console.log(" Merkmalsgruppen:", merkmale.length); + for (const m of merkmale) console.log(" " + m); + const knoepfe = await seite.$$eval(".t-leer__knoepfe a", + (ns) => ns.map((n) => n.textContent.trim())); + console.log(" Wege:", knoepfe.join(" | ")); + await leer.scrollIntoViewIfNeeded(); + await seite.waitForTimeout(400); + await leer.screenshot({ path: "server/bild-talente-leer.png" }); +} + +/* ---- 3. Auf dem Handy ------------------------------------------------ */ +const handy = await ctx.newPage(); +await handy.goto(BASIS + "/workspace/entwicklung.html", { waitUntil: "networkidle" }); +await handy.setViewportSize({ width: 412, height: 915 }); +await handy.waitForTimeout(1500); +const p2 = await handy.$(".e-person, .p-kachel, [data-person-id]"); +if (p2) { await p2.click(); await handy.waitForTimeout(1500); } +console.log("\n=== 3. Auf 412 px (haeufigste Android-Breite) ==="); +const l2 = await handy.$(".e-stufen"); +if (l2) { + const zeilen = await handy.$$eval(".e-stufen__knopf", + (ns) => new Set(ns.map((n) => Math.round(n.getBoundingClientRect().top))).size); + const ueber = await handy.$$eval(".e-stufen__knopf", + (ns) => ns.filter((n) => n.getBoundingClientRect().right > window.innerWidth).length); + console.log(" Zeilen:", zeilen, " ueber den Rand hinaus:", ueber); + const kasten2 = await handy.$(".e-bloecke__leiste"); + await (kasten2 || l2).screenshot({ path: "server/bild-stufen-handy.png" }); +} else { console.log(" keine Leiste gefunden"); } + +console.log("\nFehler auf der Seite:", fehlerAufSeite.length ? fehlerAufSeite.join(" | ") : "keine"); +await browser.close(); +try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest */ } +process.exit(0); diff --git a/server/pruef-kanalzeile.mjs b/server/pruef-kanalzeile.mjs new file mode 100644 index 00000000..b5c0d01e --- /dev/null +++ b/server/pruef-kanalzeile.mjs @@ -0,0 +1,259 @@ +/* ===================================================================== + DIE KANALZEILE (17.09.2026) -- Schritt 3 aus dem Video-Plan + + Filipe: "fuer meinen Account, dogfather, dann dogfather clips und + hasidog." + + Geprueft wird nicht nur, DASS die Zeile da ist: + + * Bei weniger als zwei Kanaelen darf sie NICHT erscheinen. Ein + Filter, der nichts zu filtern hat, macht misstrauisch -- und + wer einmal einen Knopf gedrueckt hat, bei dem nichts passiert, + drueckt den naechsten nicht mehr. + * Ein Klick muss wirklich weniger Karten uebrig lassen, und zwar + die richtigen. + * Ein zweiter Klick auf denselben Knopf hebt den Filter auf. + * Kanal- und Art-Filter wirken ZUSAMMEN -- das war der Grund, den + Kanal als zweite Dimension zu bauen statt als weiteren Wert in + derselben Leiste. + + Aufrufen mit: node server/pruef-kanalzeile.mjs + ===================================================================== */ +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { createServer } from "node:http"; + +const ordner = mkdtempSync(join(tmpdir(), "ws-kz-")); +process.env.WORKSPACE_DB = join(ordner, "workspace.db"); +const { portMussFreiSein } = await import("./helfer-port.mjs"); +await portMussFreiSein(4332, "das Bildwerkzeug"); +const TIKTOK = await portMussFreiSein(4502, "nachgebautes TikTok"); +process.env.PORT = "4332"; +process.env.SITE_ACCESS_SECRET = "lokaler-test"; +process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; +process.env.TIKTOK_OEMBED_BASIS = `http://127.0.0.1:${TIKTOK}/oembed`; + +const PNG = Buffer.from( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", + "base64"); +const tiktok = createServer((q, a) => { + const u = new URL(q.url, `http://127.0.0.1:${TIKTOK}`); + if (u.pathname === "/oembed") { + const ziel = u.searchParams.get("url") || ""; + const wer = (ziel.match(/@([^/?]+)/) || [])[1] || ""; + if (!wer) { a.writeHead(404); a.end("{}"); return; } + const titel = { "dogfather0804": "Casper klaut die Fernbedienung 😄", + "hasidog00804": "Casper schläft im Wäschekorb", + "dogis.modi.gang": "Der Chat entscheidet: Horror oder Koop?" }[wer] || "Video"; + a.writeHead(200, { "content-type": "application/json" }); + a.end(JSON.stringify({ + version: "1.0", type: "video", title: titel, + author_url: `https://www.tiktok.com/@${wer}`, + thumbnail_url: `http://127.0.0.1:${TIKTOK}/cover.jpg`, + thumbnail_width: 576, thumbnail_height: 1024, + })); + return; + } + if (u.pathname === "/cover.jpg") { + a.writeHead(200, { "content-type": "image/jpeg" }); a.end(PNG); return; + } + a.writeHead(404); a.end("{}"); +}); +await new Promise((r) => tiktok.listen(TIKTOK, "127.0.0.1", r)); + +const express = (await import("express")).default; +const ec = express.response.cookie; +express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +await import("./index.js"); +await new Promise((r) => setTimeout(r, 1200)); +const BASIS = "http://127.0.0.1:4332"; + +const { DatabaseSync } = await import("node:sqlite"); +const { scryptSync, randomBytes } = await import("node:crypto"); +for (let i = 0; i < 60; i++) { + try { + const t = new DatabaseSync(process.env.WORKSPACE_DB); + try { t.prepare("SELECT 1 FROM personen LIMIT 1").get(); break; } finally { t.close(); } + } catch { await new Promise((r) => setTimeout(r, 250)); } +} +const d = new DatabaseSync(process.env.WORKSPACE_DB); +const j = new Date().toISOString(); +const salt = randomBytes(16).toString("hex"); +const hash = scryptSync("CODE-DOGI-0001", salt, 64, + { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex"); +d.prepare("INSERT INTO personen (name,rolle,code_hash,code_salt,code_n,aktiv,erstellt) VALUES (?,?,?,?,?,1,?)") + .run("Filipe", "admin", hash, salt, 32768, j); +d.close(); + +const an = await fetch(BASIS + "/workspace/api/anmelden", { + method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ rolle: "admin", code: "CODE-DOGI-0001" }), +}); +const keks = (an.headers.getSetCookie?.() || []).map((z) => z.split(";")[0]).join("; "); + +async function video(handle, nr) { + const a = await fetch(`${BASIS}/workspace/api/bereich/highlight/video`, { + method: "POST", + headers: { "Content-Type": "application/json", cookie: keks, origin: BASIS }, + body: JSON.stringify({ url: `http://127.0.0.1:${TIKTOK}/@${handle}/video/${nr}` }), + }); + return a.status; +} + +const pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); +const browser = await pw.chromium.launch(); +const ctx = await browser.newContext({ viewport: { width: 1200, height: 900 }, deviceScaleFactor: 2 }); +await ctx.addCookies(keks.split("; ").map((k) => { + const [name, ...rest] = k.split("="); + return { name, value: rest.join("="), domain: "127.0.0.1", path: "/" }; +})); +const seite = await ctx.newPage(); +const konsolenfehler = []; +seite.on("console", (m) => { if (m.type() === "error") konsolenfehler.push(m.text()); }); +seite.on("pageerror", (f) => konsolenfehler.push(String(f))); + +let fehler = 0, geprueft = 0; +const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; + +const zeileDa = async () => !!(await seite.$(".filter__kanaele")); +/* DER SELEKTOR HEISST .eintrag-karte -- nachgesehen in bereich.js + (el("article", "eintrag-karte")), nicht geraten. Mein erster + Versuch traf nichts, und die Messung meldete brav "0 -> 0": Das + sah aus wie ein Ergebnis und war keins. + + BEIM BERICHTIGEN bin ich dann in die naechste Falle gelaufen und + habe daraus "undefined -> undefined" gemacht: Das Patchskript + benutzte String.replace, und dort bedeutet "$$" im ERSATZTEXT ein + einzelnes "$". Aus seite.$$(...) wurde seite.$(...). Der Fehler + steht woertlich in meinen eigenen Regeln -- und hat trotzdem + zugeschlagen. Aufgefallen ist er nur, weil die Ausgabe gelesen + wurde: "undefined" ist wenigstens ehrlich, "0" waere es nicht + gewesen. */ +const karten = async () => (await seite.$$(".eintrag-karte")).length; + +/* ---- 1. EIN Kanal: die Zeile darf NICHT da sein --------------------- */ +console.log("\n=== 1. Mit nur einem Kanal ==="); +console.log(" Video 1:", await video("dogfather0804", "101")); +await seite.goto(BASIS + "/workspace/bereich.html?b=highlight", { waitUntil: "networkidle" }); +await seite.waitForTimeout(1500); +ok(!(await zeileDa()), "bei einem einzigen Kanal steht dort KEINE Filterzeile"); + +/* ---- 2. Drei Kanäle -------------------------------------------------- */ +console.log("\n=== 2. Mit drei Kanaelen ==="); +console.log(" Video 2:", await video("hasidog00804", "102")); +console.log(" Video 3:", await video("dogis.modi.gang", "103")); +await seite.reload({ waitUntil: "networkidle" }); +await seite.waitForTimeout(1800); +ok(await zeileDa(), "sobald drei Kanaele da sind, erscheint sie"); +const knoepfe = await seite.$$eval(".filter__kanal", (ns) => ns.map((n) => ({ + text: n.textContent.trim().replace(/\s+/g, " "), + kanal: n.dataset.kanal || "(alle)", + gewaehlt: n.getAttribute("aria-pressed"), +}))); +for (const k of knoepfe) console.log(` ${k.gewaehlt === "true" ? "[x]" : "[ ]"} ${k.text}`); +ok(knoepfe.length === 4, `vier Knoepfe: Alle Kanaele und die drei (${knoepfe.length})`); +ok(knoepfe[0]?.gewaehlt === "true", "und „Alle Kanaele“ ist die Vorgabe"); + +const zeile = await seite.$(".filter__kanaele"); +if (zeile) { + const leiste = await seite.$("#filter"); + await (leiste || zeile).scrollIntoViewIfNeeded(); + await seite.waitForTimeout(400); + await (leiste || zeile).screenshot({ path: "server/bild-kanalzeile.png" }); +} + +/* ---- 3. Ein Klick filtert wirklich ----------------------------------- */ +console.log("\n=== 3. Ein Klick ==="); +const vorher = await karten(); +ok(vorher === 3, `ungefiltert liegen drei Videos da (${vorher})`); + +const hasi = await seite.$('.filter__kanal[data-kanal="hasidog"]'); +ok(!!hasi, "der HasiDog-Knopf ist da"); +if (hasi) { + await hasi.click(); + await seite.waitForTimeout(1500); + const nachher = await karten(); + ok(nachher === 1, `nach einem Klick auf HasiDog bleibt eines uebrig (${nachher})`); + + /* UND ZWAR DAS RICHTIGE. "Weniger" allein waere auch erfuellt, wenn + der Filter irgendetwas wegwirft -- das ist der Unterschied + zwischen filtern und kaputt sein. */ + const titel = await seite.$$eval(".eintrag-karte", + /* /\s+/ und nicht /s+/ -- beim Erzeugen dieser Datei ist der + Backslash durch eine Escaping-Ebene verloren gegangen, und der + Ausdruck ersetzte danach JEDES "s" durch ein Leerzeichen. In der + Ausgabe stand "Ca per chläft im Wä chekorb". Das war kein Fehler + im Filter, sondern einer in der Pruefung -- und er hat sie rot + gemacht, was genau richtig ist: Eine Pruefung, die ihren eigenen + Text zerstoert, darf nicht gruen sein. */ + (ns) => ns.map((n) => n.textContent.replace(/\s+/g, " ").trim())); + for (const t of titel) console.log(" " + t.slice(0, 60)); + ok(titel.length === 1 && /Wäschekorb/.test(titel[0]), + "und es ist wirklich das HasiDog-Video"); + + const leiste2 = await seite.$("#filter"); + if (leiste2) await leiste2.screenshot({ path: "server/bild-kanalzeile-gefiltert.png" }); + + /* Der gewaehlte Knopf sagt es auch. */ + const gedrueckt = await seite.$$eval(".filter__kanal", + (ns) => ns.filter((n) => n.getAttribute("aria-pressed") === "true") + .map((n) => n.dataset.kanal || "(alle)")); + ok(gedrueckt.length === 1 && gedrueckt[0] === "hasidog", + `genau ein Knopf ist gewaehlt (${gedrueckt.join(", ")})`); + + /* ---- Und ein zweiter Klick hebt ihn auf --------------------------- + Ohne das sucht man den Weg zurueck zu "Alle Kanaele" und findet + ihn nicht, weil man nicht mehr weiss, dass man gefiltert hat. */ + const nochmal = await seite.$('.filter__kanal[data-kanal="hasidog"]'); + await nochmal.click(); + await seite.waitForTimeout(1500); + ok((await karten()) === vorher, + `ein zweiter Klick hebt den Filter auf (wieder ${await karten()})`); +} + +/* ---- 4. Kanal UND Art zusammen -------------------------------------- + Der eigentliche Grund fuer zwei getrennte Reihen. Waere der Kanal + nur ein weiterer Wert in derselben Leiste, koennte man das hier + nicht fragen -- und die Pruefung waere trotzdem gruen geblieben. */ +console.log("\n=== 4. Beide Filter zusammen ==="); +{ + const df = await seite.$('.filter__kanal[data-kanal="dogfather"]'); + await df.click(); + await seite.waitForTimeout(1200); + const nurKanal = await karten(); + ok(nurKanal === 1, `nur DogFather: ${nurKanal}`); + + /* Jetzt zusaetzlich eine Art, die dieses Video NICHT hat. */ + const arten = await seite.$$eval("#filter > .schritt", + (ns) => ns.map((n) => n.textContent.trim())); + console.log(" Arten in der ersten Reihe: " + arten.join(", ")); + const bild = (await seite.$$("#filter > .schritt")) + .find ? null : null; + const alleKnoepfe = await seite.$$("#filter > .schritt"); + let getroffen = false; + for (const k of alleKnoepfe) { + const t = (await k.textContent()).trim(); + if (t !== "Bild") continue; + await k.click(); + await seite.waitForTimeout(1200); + getroffen = true; + ok((await karten()) === 0, + `DogFather + Art "Bild" ergibt nichts (${await karten()}) -- beide Filter wirken`); + break; + } + ok(getroffen, "die Art „Bild“ war zum Gegenprobieren da"); + void bild; +} + +console.log(""); +ok(konsolenfehler.length === 0, + konsolenfehler.length ? konsolenfehler.join(" | ") : "keine Fehler in der Browserkonsole"); + +console.log(""); +console.log(`${geprueft} Pruefungen, ${fehler} Fehler`); +console.log(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG"); +await browser.close(); +await new Promise((r) => tiktok.close(r)); +try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest */ } +process.exit(fehler ? 1 : 0); diff --git a/workspace/app.webmanifest b/workspace/app.webmanifest index 6a00991b..1f9fd164 100644 --- a/workspace/app.webmanifest +++ b/workspace/app.webmanifest @@ -13,25 +13,25 @@ "theme_color": "#06090f", "icons": [ { - "src": "/assets/img/app-symbole/workspace-192.png?v=202609171318", + "src": "/assets/img/app-symbole/workspace-192.png?v=202609171407", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-512.png?v=202609171318", + "src": "/assets/img/app-symbole/workspace-512.png?v=202609171407", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609171318", + "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609171407", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609171318", + "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609171407", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/workspace/assets/css/bereich.css b/workspace/assets/css/bereich.css index f0c5906f..71dd043d 100644 --- a/workspace/assets/css/bereich.css +++ b/workspace/assets/css/bereich.css @@ -1164,3 +1164,56 @@ .videozeile__kanal[data-kanal="clips"] { --v-ton: #3f9c8a; } .videozeile__weg { flex: 0 0 auto; text-decoration: none; } + +/* --------------------------------------------------------------------- + DIE KANALZEILE (17.09.2026) -- Schritt 3 aus dem Video-Plan + + Sie steht als eigene Reihe UNTER dem Art-Filter, nicht daneben. Zwei + Fragen in einer Zeile sehen aus wie eine: Man klickt „Highlights" + und dann „HasiDog" und wundert sich, warum das erste wieder aus ist. + Getrennte Reihen zeigen, dass es zwei Dinge sind. + + DIESELBEN DREI TÖNE WIE AUF DER KARTE (.videozeile__kanal). Wer + „DogFather Clips" in Türkis filtert, soll darunter Karten mit + türkisem Zeichen finden -- sonst muss er bei jedem Video neu lesen. + ------------------------------------------------------------------- */ +.filter__kanaele { + display: flex; flex-wrap: wrap; gap: 8px; + /* Volle Breite erzwingt die eigene Reihe, auch wenn oben noch Platz + wäre. Das ist Absicht: Es sind zwei Fragen, keine Fortsetzung. + Und es ist eine REGEL statt einer Schwelle -- keine Pixelzahl, die + beim nächsten hinzugefügten Knopf still falsch wird. */ + flex-basis: 100%; + margin-top: 10px; padding-top: 10px; + border-top: 1px solid var(--rand); +} + +.filter__kanal { + --v-ton: #6f89a8; + display: inline-flex; align-items: center; gap: 7px; +} +.filter__kanal[data-kanal="dogfather"] { --v-ton: #c2544f; } +.filter__kanal[data-kanal="hasidog"] { --v-ton: #7d6bbd; } +.filter__kanal[data-kanal="clips"] { --v-ton: #3f9c8a; } + +/* GEWÄHLT heißt: der Ton wird sichtbar. Ungewählt bleibt er gedeckt -- + drei bunte Knöpfe nebeneinander wären eine Ampel, und eine Ampel + sagt „gut/schlecht", wo nur „welcher Kanal" gemeint ist. */ +.filter__kanal[aria-pressed="true"] { + color: color-mix(in srgb, var(--v-ton) 76%, #ffffff); + border-color: color-mix(in srgb, var(--v-ton) 55%, transparent); + background: color-mix(in srgb, var(--v-ton) 13%, transparent); +} + +.filter__kanal-zahl { + /* .72rem = 11,5 px, die Grenze, die pruef-css-klassen bewacht. */ + font-size: .72rem; font-weight: 700; + min-width: 1.5em; padding: 1px 5px; + border-radius: 999px; text-align: center; + color: var(--text-still); + background: rgba(255, 255, 255, .07); +} +.filter__kanal[aria-pressed="true"] .filter__kanal-zahl { + color: color-mix(in srgb, var(--v-ton) 82%, #ffffff); + background: color-mix(in srgb, var(--v-ton) 20%, transparent); +} diff --git a/workspace/assets/css/entwicklung.css b/workspace/assets/css/entwicklung.css index 04d5978e..4c7ad78e 100644 --- a/workspace/assets/css/entwicklung.css +++ b/workspace/assets/css/entwicklung.css @@ -567,7 +567,30 @@ Zeile den Anfang neu. Zentriert war richtig, solange dort ein einziger Satz stand; jetzt steht dort eine Aufzählung. ------------------------------------------------------------------- */ -.t-leer:has(.t-leer__merkmale) { text-align: start; } +.t-leer:has(.t-leer__merkmale) { + text-align: start; + /* UND ER BRAUCHT EINEN GRUND. Gesehen im Bildschirmfoto, nicht in + einer Zahl: Der Kasten hatte nur einen gestrichelten Rand, und das + Spicy-Media-Motiv -- Chilischoten, Neonlicht, helle Stellen -- + schien voll durch den Text. Lesbar war das nicht, augenschonend + erst recht nicht. + + SOLANGE DORT EIN SATZ STAND, ging es. Jetzt stehen dort drei + Absätze, fünf Merkmale und zwei Knöpfe. + + DIE WARNUNG STAND 70 ZEILEN WEITER UNTEN, bei .t-fuss, und sie + beschreibt exakt diesen Fehler ("Sie stand als einziger Text der + Seite direkt auf der Bühne"). Ich habe sie gelesen und bin + trotzdem hineingelaufen -- ein Kommentar, der vor einem Fehler + warnt, verhindert ihn nicht. Gefunden hat es erst das Bild. + + Derselbe Wert wie bei .t-fuss, damit auf dieser Seite nicht zwei + verschiedene Grautöne nebeneinander liegen. */ + padding: 20px; + background: color-mix(in srgb, var(--tinte) 94%, #ffffff 6%); + border-style: solid; + border-color: var(--rand); +} .t-leer:has(.t-leer__merkmale) p { margin-inline: 0; } .t-leer__titel { diff --git a/workspace/assets/js/bereich.js b/workspace/assets/js/bereich.js index 0947ccbc..f67a9f79 100644 --- a/workspace/assets/js/bereich.js +++ b/workspace/assets/js/bereich.js @@ -107,6 +107,14 @@ let treffBrett = false; let lage = null; let filter = ''; + /* WELCHER KANAL -- eine ZWEITE Dimension neben `filter`, nicht + derselbe Wert. + + Ein gemeinsamer Wert wäre einfacher zu bauen und falsch zu + benutzen: Man könnte dann „nur offene" ODER „nur HasiDog" fragen, + aber nie beides. Und genau das will man, sobald aus drei Kanälen + Videos nebeneinander liegen. */ + let kanalFilter = ''; /* Nur beim ersten Laden springen -- sonst reisst es die Seite nach jeder Bewertung erneut an dieselbe Stelle. */ let gesprungen = false; @@ -908,7 +916,7 @@ } catch { /* dann gilt die alte Regel, siehe darfEintragen */ } } - function filterBauen() { + function filterBauen(eintraege = []) { const ziel = $('filter'); ziel.textContent = ''; const knopf = (wert, text) => { @@ -921,6 +929,47 @@ ziel.append(knopf('', 'Alle')); ziel.append(knopf('offen', 'Nur offene')); for (const [wert, name] of Object.entries(einstellung.arten)) ziel.append(knopf(wert, name)); + + /* ---- UND DIE KANALZEILE, wenn hier Videos liegen ---------------- + + SIE ERSCHEINT NUR, WENN ES ETWAS ZU FILTERN GIBT. Die Kanäle + werden aus den vorhandenen Einträgen abgeleitet, nicht + aufgezählt: Auf einem Brett ohne Videos steht keine leere Leiste, + und auf einem mit nur einem Kanal steht kein Filter, der nichts + tut. Ein Knopf, der immer dasselbe zeigt, macht misstrauisch. + + ERST AB ZWEI KANÄLEN. Bei einem gibt es nichts zu wählen. */ + const kanaele = [...new Set(eintraege.map((e) => e.quelle_kanal).filter(Boolean))]; + if (kanaele.length < 2) { kanalFilter = ''; return; } + + const zweite = el('div', 'filter__kanaele'); + zweite.setAttribute('role', 'group'); + zweite.setAttribute('aria-label', 'Nach Kanal filtern'); + const kanalKnopf = (wert, text, zahl) => { + const b = el('button', 'schritt filter__kanal', text); + b.type = 'button'; + if (wert) b.dataset.kanal = wert; + b.setAttribute('aria-pressed', String(kanalFilter === wert)); + if (zahl !== null) b.append(el('span', 'filter__kanal-zahl', String(zahl))); + b.addEventListener('click', async () => { + /* Ein zweiter Klick auf denselben Kanal hebt ihn auf -- sonst + sucht man den Weg zurück zu „Alle Kanäle" und findet ihn + nicht, weil man nicht mehr weiß, dass man gefiltert hat. */ + kanalFilter = (kanalFilter === wert) ? '' : wert; + await laden(); + }); + return b; + }; + zweite.append(kanalKnopf('', 'Alle Kanäle', null)); + /* IN DER REIHENFOLGE VON KANAL_WORT, nicht in der, in der die + Einträge zufällig kommen. Sonst springen die Knöpfe, sobald + jemand ein Video einfügt. */ + for (const wert of Object.keys(KANAL_WORT)) { + if (!kanaele.includes(wert)) continue; + zweite.append(kanalKnopf(wert, KANAL_WORT[wert], + eintraege.filter((e) => e.quelle_kanal === wert).length)); + } + ziel.append(zweite); } /* WAS AUS DEN EINTRAEGEN GEWORDEN IST -- einmal fuer das ganze Brett. @@ -1061,7 +1110,7 @@ einstellung = daten.einstellung; treffBrett = !!daten.treff; await lageHolen(); - filterBauen(); + filterBauen(daten.eintraege || []); const ziel = $('liste'); ziel.textContent = ''; @@ -1080,6 +1129,10 @@ else delete ziel.dataset.aushang; const sichtbar = daten.eintraege.filter((e) => { + /* DER KANAL ZUERST und als eigene Bedingung: Er schließt den + Art-Filter nicht aus, er kommt dazu. „Nur offene Clips von + HasiDog" ist eine sinnvolle Frage. */ + if (kanalFilter && e.quelle_kanal !== kanalFilter) return false; if (!filter) return true; if (filter === 'offen') return e.status === 'offen'; return e.art === filter; diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index 711249ba..c3b49c42 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 a48160a6..73538f75 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 d19ced53..c940b53e 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 9a9da10c..beabbec4 100644 --- a/workspace/bereich.html +++ b/workspace/bereich.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -224,14 +224,14 @@ - - - - - - - - - + + + + + + + + + diff --git a/workspace/calls.html b/workspace/calls.html index 6e7d8217..548e4e32 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 62a0bef3..64fa6f0a 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,10 +341,10 @@ - - - - - + + + + + diff --git a/workspace/content.html b/workspace/content.html index 1ccdc909..4c8cced5 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 63a21f96..a2e6814d 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 157068a7..470a577a 100644 --- a/workspace/crew.webmanifest +++ b/workspace/crew.webmanifest @@ -13,25 +13,25 @@ "theme_color": "#06090f", "icons": [ { - "src": "/assets/img/app-symbole/crew-192.png?v=202609171318", + "src": "/assets/img/app-symbole/crew-192.png?v=202609171407", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/crew-512.png?v=202609171318", + "src": "/assets/img/app-symbole/crew-512.png?v=202609171407", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609171318", + "src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609171407", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609171318", + "src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609171407", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/workspace/dateien.html b/workspace/dateien.html index 16020f8c..4185b59f 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 8b7bbf76..4a31b172 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 6706b875..af2fb21f 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 be770312..dac957b8 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 679b5c3e..db233f27 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 e51e04a3..05e7a33b 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 372f5b11..fbdd8846 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 3c0290f2..ccaeeb0b 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 bda75ae0..ab4d4ed8 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 b0767097..4e099b54 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 de9cb8fe..2e45186b 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 a1330fb0..a4003c37 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 be984230..eeb96908 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 6ca94e87..44750cb6 100644 --- a/workspace/talente.html +++ b/workspace/talente.html @@ -7,14 +7,14 @@ - - - - - - - - + + + + + + + + @@ -94,9 +94,9 @@ - - - - + + + + diff --git a/workspace/team.html b/workspace/team.html index 1fd6d919..9c2602f6 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 201fa783..f559dc48 100644 --- a/workspace/teamlage.html +++ b/workspace/teamlage.html @@ -7,20 +7,20 @@ - - - - - - - + + + + + + + - + - - - - + + + + diff --git a/workspace/treff-moderation.html b/workspace/treff-moderation.html index 55be80ca..547bfeff 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 199da40c..b2c96fca 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 657d3d3c..8df09d73 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 39b72dcf..6b5afd04 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 @@ - - - - - + + + + +