diff --git a/server/pruef-kalender.mjs b/server/pruef-kalender.mjs index 25787abc..e5a25baf 100644 --- a/server/pruef-kalender.mjs +++ b/server/pruef-kalender.mjs @@ -84,8 +84,84 @@ for (const [art, beginn, titel] of termine) { d.prepare(`INSERT INTO aufgaben (titel, status, creator_id, verantwortlich_id, frist, prioritaet, erstellt, erstellt_von) VALUES (?,?,?,?,?,?,?,?)`) .run("Thumbnail bauen", "offen", idAnna, idAnna, morgen, "hoch", jetzt, idDogi); + +/* ---- Zettel von den Brettern (17.09.2026) -------------------------- + + Filipe: „ich will dass die auch in einer kalender drin sind." + + SECHS STUECK, und jedes beantwortet eine andere Frage. Vier sollen + erscheinen, zwei ausdruecklich nicht -- ohne die beiden waere der + Abschnitt eine Pruefung, die nur bestaetigt: Dass etwas auftaucht, + ist leicht; dass das Richtige WEGBLEIBT, ist der eigentliche + Schnitt. */ +const brettzettel = [ + /* [bereich, titel, datum, uhrzeit, geplant, event_ende, status, soll_erscheinen] */ + ["wunsch", "Zugesagt: Retro-Abend", heute, null, in5, null, "angenommen", true], + ["content", "Clip am Wochenende", in5, null, null, null, "offen", true], + ["live", "Stream um acht", morgen,"20:00", null, null, "offen", true], + ["agentur", "Messe Koeln", morgen, null, null, in5, "offen", true], + /* DER RUECKFALL. `datum` ist ein Pflichtfeld und faellt auf HEUTE + zurueck, wenn niemand eins waehlt -- das ist das Erfassungsdatum + und kein Termin. Erschiene dieser Zettel, stuende im Kalender + kuenftig jeder je getippte Gedanke an dem Tag, an dem er getippt + wurde. */ + ["ideen", "Nur so notiert", heute, null, null, null, "offen", false], + /* WAS ERLEDIGT IST, GEHOERT AUFS BRETT UND NICHT IN DIE PLANUNG. */ + ["content", "Laengst rausgegangen", in5, null, null, null, "erledigt", false], +]; +for (const [bereich, titel, datum, uhrzeit, geplant, ende, status] of brettzettel) { + d.prepare(`INSERT INTO eintraege (bereich, art, titel, text, datum, uhrzeit, geplant, + event_ende, status, dringlichkeit, erstellt, erstellt_von) + VALUES (?,?,?,?,?,?,?,?,?,'mittel',?,?)`) + .run(bereich, "sonstiges", titel, "Text dazu", datum, uhrzeit, geplant, ende, + status, jetzt, idDogi); +} +/* UND EINER, DEN NUR DIE LEITUNG SEHEN DARF. Er ist die Gegenprobe zur + Rechtefrage: Im Kalender steht nur eine kleine Pille mit einem Titel + -- dass darin etwas Vertrauliches steckt, faellt niemandem auf, der + nicht danach sucht. Genau so ist am 03.09. eine Managerin an fremde + Aufgabenfristen gekommen. */ +d.prepare(`INSERT INTO eintraege (bereich, art, titel, text, datum, status, + dringlichkeit, nur_leitung, erstellt, erstellt_von) + VALUES (?,?,?,?,?,'offen','mittel',1,?,?)`) + .run("ideen", "sonstiges", "Vertraulich: Sponsorgespraech", null, in5, jetzt, idDogi); d.close(); +/* ===================================================================== + DIE ERWARTETEN ZAHLEN WERDEN GERECHNET, NICHT ABGESCHRIEBEN + (17.09.2026) + + Hier standen „alle fünf Einträge", „vier Arten, vier Farben", „vier + Filter", „drei Einträge übrig". Alle vier stimmten -- solange die + Testdaten oben aus genau vier Terminen und einer Frist bestanden. + + Mit den Zetteln von den Brettern wurden daraus zehn, und SIEBEN + Prüfungen je Bildschirmgröße wurden rot, ohne dass am Kalender + irgendetwas kaputt war. Das ist dieselbe Falle wie eine feste + Umbruchschwelle im CSS: eine Rechnung von gestern, die still falsch + wird, sobald jemand etwas hinzufügt. + + Jetzt kommt jede Zahl aus den Daten darüber. Wer morgen einen + fünften Termin einträgt, muss hier nichts nachziehen -- und wenn + doch etwas rot wird, liegt es am Kalender und nicht an der + Buchhaltung dieser Datei. */ +const ERWARTET = { + termine: termine.length, + fristen: 1, + /* Was der Server ausliefern MUSS: die Zettel mit `soll_erscheinen`. + Der vertrauliche kommt dazu, weil DogFather ihn sehen darf. */ + brettzettel: brettzettel.filter((z) => z[7]).length + 1, +}; +ERWARTET.pillen = ERWARTET.termine + ERWARTET.fristen + ERWARTET.brettzettel; +/* Die Arten: call, termin, review (aus `termine`), frist, brett. */ +ERWARTET.arten = new Set([...termine.map((x) => x[0]), "frist", "brett"]).size; +/* Mit Uhrzeit: die Termine (alle haben eine) plus die Zettel, denen + eine gegeben wurde. Die Frist hat keine. */ +ERWARTET.mitUhr = ERWARTET.termine + + brettzettel.filter((z) => z[7] && z[3]).length; +ERWARTET.calls = termine.filter((x) => x[0] === "call").length; +ERWARTET.ohneCalls = ERWARTET.pillen - ERWARTET.calls; + const { chromium } = await import( "file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); const browser = await chromium.launch(); @@ -115,6 +191,8 @@ const lesen = (seite) => seite.evaluate(() => ({ pillen: [...document.querySelectorAll(".k-pille")].map((p) => ({ text: p.textContent, farbe: getComputedStyle(p).borderLeftColor, + brett: p.querySelector(".k-pille__brett")?.textContent || null, + zugesagt: !!p.querySelector('.k-pille__zusage'), })), anlaesse: [...document.querySelectorAll("#kalender .k-anlass")].map((a) => a.textContent), /* Leisten */ @@ -174,22 +252,29 @@ for (const [breite, wie] of [[1440, "Computer"], [390, "Handy"]]) { ok(r.fremdZellen > 0, `die Nachbarmonatstage sind erkennbar (${r.fremdZellen})`); /* ---- Eintraege im Raster ---- */ - ok(r.pillen.length === 5, `alle fünf Einträge stehen im Raster (${r.pillen.length})`); + ok(r.pillen.length === ERWARTET.pillen, + `alle ${ERWARTET.pillen} Einträge stehen im Raster (${r.pillen.length})`); ok(r.pillen.some((p) => p.text.includes("Wochen-Call")), "der Call ist dabei"); ok(r.pillen.some((p) => p.text.includes("Thumbnail bauen")), "die Frist aus den Aufgaben auch"); /* Jede Art traegt ihre eigene Farbe. Zwei Calls, ein Termin, ein Review, eine Frist -> vier verschiedene Farben. */ const farben = new Set(r.pillen.map((p) => p.farbe)); - ok(farben.size === 4, `vier Arten, vier Farben (${farben.size})`); + ok(farben.size === ERWARTET.arten, + `${ERWARTET.arten} Arten, ${ERWARTET.arten} Farben (${farben.size})`); /* Die Uhrzeit steht vorn -- ausser bei der Frist, die keine hat. */ - ok(r.pillen.filter((p) => /^\d{2}:\d{2}/.test(p.text)).length === 4, - "vier Einträge mit Uhrzeit, die Frist ohne"); + { + const mitUhr = r.pillen.filter((p) => /^\d{2}:\d{2}/.test(p.text)).length; + ok(mitUhr === ERWARTET.mitUhr, + `${ERWARTET.mitUhr} Einträge mit Uhrzeit, die Frist ohne (${mitUhr})`); + } /* ---- Zaehler in der Kopfzeile ---- */ - ok(/KW \d+( – \d+)? · 5 Einträge/.test(r.unter), `die Kopfzeile zählt mit ("${r.unter}")`); + ok(new RegExp(`KW \\d+( – \\d+)? · ${ERWARTET.pillen} Einträge`).test(r.unter), + `die Kopfzeile zählt mit ("${r.unter}")`); /* ---- Filter ---- */ - ok(r.chips.length === 4, `vier Filter (${r.chips.length})`); + ok(r.chips.length === ERWARTET.arten, + `${ERWARTET.arten} Filter (${r.chips.length})`); ok(r.chips.every((c) => c.an), "und alle zunächst an"); ok(r.chips.some((c) => c.text.includes("Call (2)")), `der Call-Filter zählt zwei (${r.chips.map((c) => c.text).join(" ")})`); ok(r.chips.some((c) => c.text.includes("Frist (1)")), "der Fristen-Filter zählt eine"); @@ -199,9 +284,10 @@ for (const [breite, wie] of [[1440, "Computer"], [390, "Handy"]]) { await seite.click('.k-chip:has-text("Call")'); await seite.waitForTimeout(250); const nachFilter = await lesen(seite); - ok(nachFilter.pillen.length === 3, - `Calls ausgeschaltet: drei Einträge übrig (${nachFilter.pillen.length})`); - ok(/· 3 Einträge/.test(nachFilter.unter), `und die Kopfzeile zählt neu ("${nachFilter.unter}")`); + ok(nachFilter.pillen.length === ERWARTET.ohneCalls, + `Calls ausgeschaltet: ${ERWARTET.ohneCalls} Einträge übrig (${nachFilter.pillen.length})`); + ok(new RegExp(`· ${ERWARTET.ohneCalls} Einträge`).test(nachFilter.unter), + `und die Kopfzeile zählt neu ("${nachFilter.unter}")`); await seite.click('.k-chip:has-text("Call")'); await seite.waitForTimeout(250); @@ -426,6 +512,191 @@ console.log("\n=== Die gerechneten Tage"); await kontext.close(); } +/* ===================================================================== + WAS VON DEN BRETTERN KOMMT (17.09.2026) + + Gemessen vor dem Bau: /workspace/api/termine lieferte `termine` und + `fristen` -- die Zettel von den achtzehn Brettern kamen nirgends + vor, obwohl `eintraege` seit langem vier Zeitfelder hat (datum, + uhrzeit, geplant, event_ende). + ===================================================================== */ +{ + console.log("\n=== Zettel von den Brettern ==="); + const kontext = await browser.newContext({ viewport: { width: 1366, height: 900 } }); + const seite = await anmelden(kontext, "admin", "CODE-DOGI-0001"); + await seite.goto(BASIS + "/workspace/kalender.html", { waitUntil: "networkidle" }); + await seite.waitForTimeout(1200); + + /* Erst die Schnittstelle -- sie ist die Quelle, und wenn sie schon + falsch liefert, sagt die Oberflaeche nichts ueber die Ursache. */ + const roh = await seite.evaluate(async () => { + const a = await fetch("/workspace/api/termine?tage=30", { credentials: "same-origin" }); + return a.ok ? a.json() : null; + }); + ok(!!roh && Array.isArray(roh.brettsachen), + `die Schnittstelle liefert die Zettel (${roh?.brettsachen?.length})`); + const titel = (roh?.brettsachen || []).map((b) => b.titel); + + for (const [, t2, , , , , , soll] of brettzettel) { + ok(titel.includes(t2) === soll, + soll ? `„${t2}" ist dabei` : `„${t2}" bleibt draussen -- und das ist der Punkt`); + } + ok(!titel.includes("Vertraulich: Sponsorgespraech") + || true, "(die Leitung darf den vertraulichen sehen)"); + ok(titel.includes("Vertraulich: Sponsorgespraech"), + "DogFather sieht auch den vertraulichen"); + + /* DER MASSGEBLICHE TAG. Eine Zusage schlaegt das Erfassungsdatum -- + wer sagt „am 24.", meint den 24., auch wenn der Zettel am 3. + entstanden ist. Ohne diese Zeile stuende die Zusage am falschen + Tag, und das faellt niemandem auf, weil sie ja dasteht. */ + { + const z = (roh?.brettsachen || []).find((b) => b.titel === "Zugesagt: Retro-Abend"); + ok(z?.wann === in5, + `die Zusage steht am zugesagten Tag (${z?.wann}), nicht am Erfassungstag (${heute})`); + ok(z?.grund === "zugesagt", `und der Grund steht dabei (${z?.grund})`); + } + + /* Jetzt die Oberflaeche. */ + const gesehen = await lesen(seite); + const brettpillen = gesehen.pillen.filter((p) => p.brett); + ok(brettpillen.length >= 4, + `im Kalender stehen ${brettpillen.length} Zettel mit Brettnamen`); + ok(brettpillen.some((p) => p.zugesagt), + "eine Zusage traegt ihr eigenes Zeichen"); + ok(brettpillen.some((p) => !p.zugesagt), + "und ein bloss datierter Zettel nicht -- sonst waere das Zeichen bedeutungslos"); + ok(gesehen.chips.some((c) => c.text.startsWith("Vom Brett")), + `der Filter kennt sie (${gesehen.chips.map((c) => c.text).join(", ")})`); + + /* WEGSCHALTEN MUSS GEHEN. Ein Kalender, in dem alles steht und nichts + ausblendbar ist, ist wieder eine Liste. */ + { + const vorher = (await lesen(seite)).pillen.filter((p) => p.brett).length; + await seite.click('.k-chip:has-text("Vom Brett")'); + await seite.waitForTimeout(300); + const nachher = (await lesen(seite)).pillen.filter((p) => p.brett).length; + ok(vorher > 0 && nachher === 0, + `der Schalter blendet sie aus (${vorher} -> ${nachher})`); + await seite.click('.k-chip:has-text("Vom Brett")'); + await seite.waitForTimeout(300); + } + + /* KEINE KALENDER-KNOEPFE AN EINEM ZETTEL. „Erledigt" haette + PATCH /api/termine/b12 geschickt -- eine Nummer, die es dort nicht + gibt, an eine Schnittstelle, die davon nichts weiss. Still, ohne + Wirkung, ohne Meldung. */ + { + await seite.click('.k-ansicht[data-ansicht="liste"]'); + await seite.waitForTimeout(600); + const zeilen = await seite.evaluate(() => + [...document.querySelectorAll('.k-zeile[data-art="brett"]')].map((z) => ({ + art: z.querySelector(".k-zeile__art")?.textContent || "", + knoepfe: [...z.querySelectorAll(".k-zeile__knoepfe *")] + .map((k) => k.textContent.trim()).filter(Boolean), + }))); + ok(zeilen.length > 0, `in der Liste stehen ${zeilen.length} Zettel`); + /* DIE ANZAHL GEHOERT IN DIE BEDINGUNG, nicht nur in den Meldetext. + `every()` liefert auf einem LEEREN Feld `true` -- beim ersten Lauf + stand hier genau das: „jeder hat den Weg zurueck" war gruen, und + es gab null Zeilen, weil der Umschalter-Selektor falsch war. Eine + Pruefung, die ohne Daten bestaetigt, bestaetigt nichts. */ + ok(zeilen.length > 0 && zeilen.every((z) => z.knoepfe.includes("zum Brett")), + `jeder der ${zeilen.length} hat den Weg zurueck zu seinem Brett`); + ok(zeilen.length > 0 + && zeilen.every((z) => !z.knoepfe.some((k) => /erledigt|l..schen|Wecker/i.test(k))), + `und keiner die Knoepfe eines Termins (${zeilen[0]?.knoepfe.join(", ")})`); + ok(zeilen.length > 0 && zeilen.some((z) => z.art && z.art !== "Vom Brett"), + `in der Liste steht das Brett und nicht die Art (${zeilen[0]?.art})`); + } + /* ---- DIE ZWEITE ZEILE NUR, WO SIE LESBAR IST --------------------- + + GEMESSEN WIRD SICHTBARKEIT, NICHT VORHANDENSEIN. `textContent` + liefert den Brettnamen auch dann, wenn `display: none` ihn + ausblendet -- eine Pruefung darauf waere fuer diese Aenderung + blind gewesen und trotzdem gruen. Gefragt wird deshalb nach der + tatsaechlichen Hoehe. */ + { + const messen = () => seite.evaluate(() => + [...document.querySelectorAll(".k-tag .k-pille__brett")] + .map((b) => b.offsetHeight > 0)); + await seite.click('.k-ansicht[data-ansicht="monat"]'); + await seite.waitForTimeout(400); + const breit = await messen(); + ok(breit.length > 0 && breit.every(Boolean), + `am Rechner steht das Brett an allen ${breit.length} Zetteln`); + + await seite.setViewportSize({ width: 390, height: 844 }); + await seite.waitForTimeout(500); + const eng = await messen(); + ok(eng.length > 0 && eng.every((x) => x === false), + `am Handy bleibt es weg -- dort waere es ein „Co…" (${eng.length} geprueft)`); + await seite.setViewportSize({ width: 1366, height: 900 }); + await seite.waitForTimeout(400); + } + + /* ---- DER WEG ZUM ZETTEL, NICHT NUR ZUM BRETT --------------------- + + Ein Link, der auf das Brett fuehrt, laesst einen zwischen zwanzig + Eintraegen suchen -- dann hat er nichts gebracht. Geprueft wird + deshalb nicht, dass die Adresse stimmt, sondern dass am Ende der + richtige Zettel HERVORGEHOBEN dasteht. */ + { + await seite.click('.k-ansicht[data-ansicht="liste"]'); + await seite.waitForTimeout(500); + const wohin = await seite.evaluate(() => { + const z = [...document.querySelectorAll('.k-zeile[data-art="brett"]')] + .find((x) => /Clip am Wochenende/.test(x.textContent)); + return z?.querySelector("a")?.getAttribute("href") || null; + }); + ok(!!wohin && /zeigen=eintrag-\d+/.test(wohin), + `der Weg fuehrt auf den Zettel (${wohin})`); + await seite.goto(BASIS + "/workspace/" + wohin, { waitUntil: "networkidle" }); + await seite.waitForTimeout(1400); + const dort = await seite.evaluate(() => { + const t2 = [...document.querySelectorAll('.eintrag-karte[data-treffer="ja"]')]; + return { + markiert: t2.length, + text: t2[0]?.textContent?.slice(0, 60) || "", + leiste: document.querySelector(".ziel-leiste__text")?.textContent || "", + }; + }); + ok(dort.markiert === 1, + `auf dem Brett ist genau ein Zettel hervorgehoben (${dort.markiert})`); + ok(/Clip am Wochenende/.test(dort.text), + `und zwar der richtige (${dort.text.trim().slice(0, 40)})`); + ok(dort.leiste.length > 5, `mit einer Zeile, die sagt was gezeigt wird ("${dort.leiste}")`); + } + + await kontext.close(); + + /* ---- DIE RECHTEFRAGE --------------------------------------------- + + Im Kalender steht nur eine kleine Pille mit einem Titel. Dass + darin ein vertrauliches Vorhaben steckt, faellt niemandem auf, der + nicht danach sucht -- genau deshalb ist das die Stelle, an der ein + Leck lange unbemerkt bliebe. */ + { + const k2 = await browser.newContext({ viewport: { width: 1366, height: 900 } }); + const s2 = await anmelden(k2, "creator", "CODE-ANNA-0001"); + await s2.goto(BASIS + "/workspace/kalender.html", { waitUntil: "networkidle" }); + await s2.waitForTimeout(1200); + const roh2 = await s2.evaluate(async () => { + const a = await fetch("/workspace/api/termine?tage=30", { credentials: "same-origin" }); + return a.ok ? a.json() : null; + }); + const t2 = (roh2?.brettsachen || []).map((b) => b.titel); + ok(!t2.includes("Vertraulich: Sponsorgespraech"), + `eine Creatorin sieht den vertraulichen Zettel NICHT (${t2.length} sichtbar)`); + /* GEGENPROBE: Sie sieht ueberhaupt etwas. Ohne diese Zeile waere + „sieht das Vertrauliche nicht" auch dann gruen, wenn die Abfrage + gar nichts liefert -- und dann bewiese sie nichts. */ + ok(t2.length > 0, + `aber sehr wohl die offenen (${t2.slice(0, 2).join(", ")})`); + await k2.close(); + } +} + await browser.close(); /* Der Testordner bleibt manchmal gesperrt, weil der Server im selben Prozess noch auf der Datenbank sitzt. Das ist kein Prüfergebnis -- diff --git a/server/workspace-kalender.js b/server/workspace-kalender.js index 274c8fb8..0a40c5a8 100644 --- a/server/workspace-kalender.js +++ b/server/workspace-kalender.js @@ -20,6 +20,16 @@ import { import { nachfuellenAlle, serienPruefen, serieAnlegen, zuordnungErzwingen, } from "./workspace-serien.js"; +/* DIE SICHTBARKEIT DER BRETTEINTRAEGE KOMMT AUS IHREM EIGENEN HAUS. + + `sichtbarEintrag` ist dieselbe Funktion, durch die auch das Lesen + eines Bretts, das Aendern und das Loeschen gehen. Eine hier + nachgebaute Fassung waere eine zweite Meinung darueber, wer was + sehen darf -- und die zweite ist erfahrungsgemaess die, die mehr + durchlaesst. Genau so kam am 03.09. eine Managerin an fremde + Aufgabenfristen: nicht durch ein Loch, sondern durch eine zweite + Regel, die "istLeitung" fragte statt der richtigen. */ +import { sichtbarEintrag, BEREICHE } from "./workspace-bereiche.js"; export const kalenderRouter = express.Router(); @@ -398,7 +408,84 @@ kalenderRouter.get("/workspace/api/termine", (req, res) => { for (const t of termine) t.checkliste_aufgaben = gezaehlt.get(t.id) || 0; } } - res.json({ termine, fristen, von, bis, checkliste_moeglich: moeglich }); + /* ================================================================= + WAS AUF DEN BRETTERN STEHT UND EINEN TERMIN HAT (17.09.2026) + + Filipe, zu einem Bildschirmfoto einer Clip-Karte: „ich will dass + die auch in einer kalender drin sind." + + WELCHE? Das ist die ganze Frage, und sie laesst sich messen + statt raten. `eintraege.datum` ist ein PFLICHTFELD und faellt + auf „heute" zurueck, wenn niemand eins waehlt (workspace- + bereiche.js, pruefe()). Alle Eintraege in den Kalender zu kippen + hiesse deshalb: jeder je angelegte Zettel steht an dem Tag, an + dem ihn jemand getippt hat. Nach einem Monat waere der Kalender + ein Protokoll und kein Plan -- und ein Kalender, in dem alles + steht, sagt nichts mehr. + + GEZEIGT WIRD DESHALB NUR, WOFUER JEMAND EINE ZEIT BESTIMMT HAT. + Vier Faelle, jeder einzeln begruendbar: + + geplant Eine Zusage. „Wird gemacht -- am 24.09." Das ist + ein Termin, den jemand jemandem gegeben hat. + event_ende Ein Zeitraum. Wer ein Ende eintraegt, meint eine + Veranstaltung, keine Notiz. + uhrzeit Wer eine Uhrzeit tippt, meint einen Zeitpunkt. + Niemand traegt aus Versehen 20:00 ein. + datum > heute Der Rueckfall ist IMMER heute oder frueher. Ein + Datum in der Zukunft kann nur Absicht sein. + + Der vierte Fall ist der wichtigste und der unauffaelligste: Er + braucht kein neues Feld und keine Umgewoehnung. Wer einem + Eintrag ein kuenftiges Datum gibt, hat ihn geplant -- und sieht + ihn ab sofort im Kalender. + + ERLEDIGTES BLEIBT DRAUSSEN. Ein Kalender zeigt, was ansteht. + Was schon gemacht ist, gehoert auf sein Brett, nicht in die + Planung der naechsten Woche. */ + const eintragRegel = sichtbarEintrag(req.person, "e"); + const brettsachen = eintragRegel ? db().prepare(` + SELECT e.id, e.bereich, e.art, e.titel, e.text, e.status, + e.dringlichkeit, e.datum, e.uhrzeit, e.geplant, e.event_ende, + pe.name AS von_name, + /* WELCHER TAG ZAEHLT -- abgeleitet, nicht im Browser + nachgerechnet. Eine Zusage schlaegt das Erfassungsdatum: + Wer sagt "am 24.", meint den 24., auch wenn der Zettel + am 3. entstanden ist. */ + COALESCE(e.geplant, e.datum) AS wann, + CASE + WHEN e.geplant IS NOT NULL THEN 'zugesagt' + WHEN e.event_ende IS NOT NULL THEN 'zeitraum' + WHEN e.uhrzeit IS NOT NULL THEN 'uhrzeit' + ELSE 'termin' + END AS grund + FROM eintraege e + LEFT JOIN personen pe ON pe.id = e.erstellt_von + WHERE ${eintragRegel.wo} + AND e.status NOT IN ('erledigt', 'abgelehnt') + AND ( + e.geplant IS NOT NULL + OR e.event_ende IS NOT NULL + OR e.uhrzeit IS NOT NULL + OR e.datum > ? + ) + AND COALESCE(e.geplant, e.datum) >= ? + AND COALESCE(e.geplant, e.datum) <= ? + ORDER BY wann, e.uhrzeit, e.id`) + .all(...eintragRegel.werte, heuteLokal(), von, bis) : []; + + /* DER NAME DES BRETTS KOMMT MIT -- und zwar aus dem Katalog, nicht + als abgeschriebene Liste im Browser. Ein Brett, das morgen + dazukommt, steht damit von selbst richtig da; eine zweite Liste + waere die, die beim naechsten Mal vergessen wird. */ + for (const b of brettsachen) { + b.brett_name = BEREICHE[b.bereich]?.name || b.bereich; + /* Nur die ersten Zeilen -- im Kalender steht eine Pille, kein + Aufsatz. Der ganze Text steht auf dem Brett. */ + b.text = b.text ? String(b.text).slice(0, 180) : null; + } + + res.json({ termine, fristen, brettsachen, von, bis, checkliste_moeglich: moeglich }); } catch (fehler) { console.error("[workspace] Termine lesen:", fehler?.message); res.status(503).json({ fehler: "nicht_verfuegbar" }); diff --git a/workspace/app.webmanifest b/workspace/app.webmanifest index c6bb4bb8..abeaaa2f 100644 --- a/workspace/app.webmanifest +++ b/workspace/app.webmanifest @@ -22,25 +22,25 @@ }, "icons": [ { - "src": "/assets/img/app-symbole/workspace-192.png?v=202609171858", + "src": "/assets/img/app-symbole/workspace-192.png?v=202609172248", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-512.png?v=202609171858", + "src": "/assets/img/app-symbole/workspace-512.png?v=202609172248", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609171858", + "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609172248", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609171858", + "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609172248", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/workspace/assets/css/kalender.css b/workspace/assets/css/kalender.css index 12367b6c..efc2ca37 100644 --- a/workspace/assets/css/kalender.css +++ b/workspace/assets/css/kalender.css @@ -1546,3 +1546,86 @@ .k-schritt { border: 1px solid ButtonText; background: ButtonFace; color: ButtonText; } .k-ansicht { border: 1px solid ButtonText; } } + +/* ===================================================================== + WAS VOM BRETT KOMMT (17.09.2026) + + Filipe: „ich will dass die auch in einer kalender drin sind, aber ich + will dass es richtig geil ist." + + ZWEI ZEILEN, NICHT ZWEI SPALTEN. Der erste Anlauf stellte den + Brettnamen neben den Titel -- und das Bildschirmfoto zeigte sofort, + dass daraus „CONTENT-IDEEN Cli…" wird. Das Etikett verdraengte genau + die Information, die es einordnen sollte. + + Jetzt steht der Titel in seiner Zeile und das Brett darunter, klein + und gedaempft. Es beantwortet „was ist das?", ohne dem Titel Platz + wegzunehmen. + ===================================================================== */ +.k-pille__brett { + display: block; + margin-top: 1px; + /* 11,52 px -- gerade eben ueber der Hausgrenze von 11,5 px. + Der erste Anlauf stand auf .64rem (10,24 px) und wurde von + pruef-css-klassen gefunden: eine Stelle mehr als die Grundlinie. + + GEDAEMPFT WIRD UEBER FARBE UND GEWICHT, NICHT UEBER GROESSE. Eine + Nebenzeile darf leiser sein als der Titel -- sie darf nicht + schlechter lesbar sein. Das ist der Unterschied zwischen + „zurueckhaltend" und „zusammengekniffen", und die Hausregel + „augenschonend" meint genau den. */ + font-size: .72rem; font-weight: 600; + letter-spacing: .01em; + /* Gedaempft, aber nicht per `opacity`: Das macht die ganze Zeile + durchsichtig, und der Hintergrund des Tages scheint hindurch. Eine + leisere FARBE tut dasselbe, ohne die Flaeche anzufassen. */ + color: color-mix(in srgb, var(--pfarbe, #7f9cc0) 55%, var(--text-still)); + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; +} + +/* DER HAKEN VOR DEM TITEL -- fuer das, was jemandem zugesagt wurde. + Er ist ein Zeichen UND eine Farbe, nicht nur eine Farbe: Eine + Rueckmeldung, die allein aus einem Farbwechsel besteht, kommt bei + niemandem an, der Farben schlecht unterscheidet. */ +.k-pille__zusage { + margin-inline-end: 4px; + font-weight: 800; + color: color-mix(in srgb, var(--pfarbe, #7f9cc0) 30%, #ffffff); +} + +/* In der Listenansicht steht das Brett ohnehin neben dem Titel + (k-zeile__art). Dort waere die zweite Zeile eine Dopplung. */ +.k-zeile .k-pille__brett { display: none; } + +/* DIE ZWEITE ZEILE NUR, WO SIE LESBAR IST (17.09.2026). + + Am Handy ist eine Monatszelle rund 45 px breit. Dort wurde aus + „Content-Ideen" ein „Co…" -- eine Zeile, die Hoehe kostet und nichts + sagt. Am Bildschirmfoto in 390 px sofort zu sehen, an keiner Zahl. + + GEMESSEN STATT GESCHALTET: Entschieden wird nach der Breite der + ZELLE, nicht nach der des Fensters. Eine Fensterschwelle waere + wieder die Rechnung von gestern -- sie muesste bei jeder Aenderung + am Raster nachgezogen werden (und genau das ist im September an der + Kopfleiste zweimal schiefgegangen). Eine Zellenabfrage stimmt auch + dann noch, wenn das Raster morgen eine Spalte mehr oder weniger hat. + + 110 px ist die Breite, ab der ein Brettname mehr als drei Buchstaben + zeigt. Darunter steht nur der Titel -- und das Brett weiterhin im + Tooltip, in der Listenansicht und im Tagesdialog. */ +.k-tag { container-type: inline-size; } + +.k-pille__brett { display: none; } +@container (min-width: 110px) { + .k-pille__brett { display: block; } +} + +/* Ausserhalb einer Zelle -- Wochenspalte, Zeitstrahl -- gibt es keinen + Container, und `@container` greift nicht. Dort ist Platz, also steht + der Name da. `:not()` ueber den Vorfahren geht in CSS nicht; deshalb + die umgekehrte Richtung: erst ueberall aus (oben), dann dort wieder + an, wo es passt. */ +.k-wochentag .k-pille__brett, +.k-strahl .k-pille__brett { display: block; } +.k-zeile .k-pille__brett { display: none; } + diff --git a/workspace/assets/js/bereich.js b/workspace/assets/js/bereich.js index 38c51690..b100f716 100644 --- a/workspace/assets/js/bereich.js +++ b/workspace/assets/js/bereich.js @@ -900,6 +900,22 @@ } else if (was === 'offen') { window.zielMarkieren?.([...karten.values()], 'Einträge, die noch offen sind', async () => { filter = ''; await laden(); }); + } else if (was.startsWith('eintrag-')) { + /* EIN EINZELNER ZETTEL (17.09.2026). + + Der Kalender zeigt seit heute, was auf den Brettern steht und + einen Termin hat. Ein Klick darauf fuehrte bisher auf das + BRETT -- und dort steht der gesuchte Zettel irgendwo zwischen + zwanzig anderen. Wer ihn dann von Hand sucht, hat vom Link + nichts gehabt. + + BENUTZT WIRD DER VORHANDENE WEG (`?zeigen=`, kopf.js) und + keine zweite Mechanik: Sonst springt die eine Seite hart, die + andere weich, und niemand traut dem Weg mehr -- das steht + woertlich in der Begruendung dieses Helfers. */ + const k = karten.get(Number(was.slice(8))); + window.zielMarkieren?.(k ? [k] : [], 'diesen einen Eintrag', + async () => { filter = ''; await laden(); }); } } diff --git a/workspace/assets/js/kalender.js b/workspace/assets/js/kalender.js index bb56f04b..73a74209 100644 --- a/workspace/assets/js/kalender.js +++ b/workspace/assets/js/kalender.js @@ -60,6 +60,19 @@ call: 'Call', termin: 'Termin', review: 'Review', frist: 'Frist', bigmatch: 'BigMatch', turnier: 'Turnier', special: 'Special-Live', collab: 'Collab', raid: 'Raid-Train', charity: 'Charity', + /* WAS AUF DEN BRETTERN STEHT UND EINEN TERMIN HAT (17.09.2026). + + Eine EINZIGE Art, nicht eine je Brett. Der Filter haette sonst + achtzehn Schalter, und eine Leiste mit achtzehn Schaltern liest + niemand mehr -- derselbe Grund, aus dem die sechs Auftritts-Arten + nur bei Bedarf erscheinen. + + UNTERSCHIEDEN WIRD UEBER DAS WORT, NICHT UEBER DIE FARBE: In der + Pille steht der Name des Bretts. Eine Farbe je Brett waere eine + von Hand gepflegte Liste -- und die waere beim naechsten neuen + Brett vergessen, genau wie die Filterliste, die hier schon + einmal stehengeblieben ist (siehe unten bei `zeigen`). */ + brett: 'Vom Brett', }; /* Jede Art trägt ihre eigene Farbe -- dieselbe Idee wie bei den Kacheln auf der Startseite: Die Farbe gehört zur Sache, nicht zum Rang. */ @@ -92,7 +105,25 @@ collab: '#2fd6c4', raid: '#ff9040', charity: '#a7d64a', + /* BEWUSST RUHIGER ALS DIE AUFTRITTE. Ein Zettel vom Brett ist + keine Sendung -- er soll im Monatsraster sichtbar sein, aber + einem BigMatch nicht die Aufmerksamkeit wegnehmen. Stahlblau ist + ausserdem der letzte Ton, der sich von allen neun anderen klar + unterscheidet: nicht das helle Blau der Calls, nicht das Violett + der Termine, nicht das Tuerkis der Collabs. */ + brett: '#7f9cc0', }; + /* Warum ein Zettel vom Brett im Kalender steht -- in Worten, die man + ohne Erklaerung versteht. Die Schluessel kommen vom Server; steht + ein unbekannter dabei, bleibt die Zeile einfach ohne dieses Stueck + statt einen Rohwert zu zeigen. */ + const GRUNDWORT = { + zugesagt: 'zugesagt', + zeitraum: 'laeuft ueber mehrere Tage', + uhrzeit: null, + termin: 'geplant', + }; + const FEIERTAG_FARBE = '#ff9aa2'; const ANLASS_FARBE = '#d8a13a'; @@ -335,6 +366,10 @@ const fmt = (o) => new Intl.DateTimeFormat('de-DE', { timeZone: 'UTC', ...o }); const tagLang = (t) => fmt({ weekday: 'long', day: '2-digit', month: 'long' }).format(ausText(t)); + /* Kurzfassung fuer eine Pille: „24.09." -- ohne Jahr und ohne + Wochentag. In einer Zelle von 150 px ist jedes weitere Zeichen + eines, das den Titel abschneidet. */ + const kurz = (t) => fmt({ day: '2-digit', month: '2-digit' }).format(ausText(t)); /* ---------- Anlässe ------------------------------------------------------ */ @@ -515,6 +550,46 @@ ziel: 'aufgaben.html', }); } + /* ---- Was auf den Brettern steht (17.09.2026) -------------------- + + Filipe, zu einer Clip-Karte: „ich will dass die auch in einer + kalender drin sind." + + WELCHE hier ankommen, entscheidet der Server (workspace- + kalender.js) -- und zwar nach der Regel „nur, wofuer jemand eine + Zeit bestimmt hat". Der Browser rechnet das NICHT nach: Eine + zweite Fassung derselben Regel ist die, die beim naechsten + Nachschaerfen auseinanderlaeuft. Er zeigt, was kommt. */ + for (const b of roh.brettsachen || []) { + raus.push({ + art: 'brett', tag: b.wann, uhr: b.uhrzeit || null, + titel: b.titel, erledigt: false, id: 'b' + b.id, + farbe: ARTFARBE.brett, + brett_name: b.brett_name, + /* DER GRUND STEHT DABEI. „Zugesagt am 24." ist etwas anderes + als „liegt auf dem 24." -- das erste ist ein Versprechen an + jemanden, das zweite eine Notiz an sich selbst. Wer das nicht + unterscheiden kann, behandelt beides gleich, und dann zaehlt + die Zusage so wenig wie die Notiz. */ + grund: b.grund, + bis: b.event_ende || null, + zusatz: [b.brett_name, GRUNDWORT[b.grund] || null, + b.event_ende && b.event_ende !== b.wann + ? 'bis ' + kurz(b.event_ende) : null, + b.von_name ? 'von ' + b.von_name : null].filter(Boolean).join(' · '), + beschreibung: b.text || null, + /* Der Weg zurueck zum Brett. Ein Kalendereintrag, den man nur + ansehen und nicht oeffnen kann, ist eine Sackgasse -- und der + ganze Vorgang (kommentieren, erledigen, verschieben) steht + dort, nicht hier. */ + /* Auf den ZETTEL, nicht nur auf das Brett. Zwischen zwanzig + anderen ist der gesuchte sonst wieder von Hand zu suchen -- + und dann hat der Link nichts gebracht. `?zeigen=` ist der + Weg, den das Haus dafuer schon hat (kopf.js). */ + ziel: 'bereich.html?b=' + encodeURIComponent(b.bereich) + + '&zeigen=eintrag-' + b.id, + }); + } /* Erst nach Tag, dann nach Uhrzeit. Fristen haben keine Uhrzeit und stehen deshalb ans Ende des Tages -- sie blockieren keinen Slot. */ return raus.sort((a, b) => @@ -563,7 +638,41 @@ (siehe zeile()) ist Platz, dort steht es hinter dem Titel und liest sich natürlicher. */ if (e.serie_id) k.append(wiederholZeichen()); + /* WOHER ER KOMMT, STEHT VOR DEM TITEL (17.09.2026). + + „Wer ist euer Komet?!" allein sagt im Kalender nichts -- man + weiss nicht, ob das ein Stream, eine Aufgabe oder ein Zettel + ist. „Content · Wer ist euer Komet?!" beantwortet das in einem + Wort, und zwar VOR dem Titel: Eine Zelle im Monatsraster ist gut + 150 px breit und schneidet hinten ab. Dieselbe Ueberlegung wie + beim Wiederhol-Zeichen eine Zeile darueber. + + UND ES IST EINE ZUSAGE ERKENNBAR. Was jemandem versprochen + wurde, traegt ein eigenes Zeichen -- ein Versprechen, das + aussieht wie eine Notiz, wird auch wie eine behandelt. */ + /* EINE ZUSAGE TRAEGT EIN HAKEN, und der steht VOR dem Titel. + + „Wird gemacht -- am 24.09." ist ein Versprechen an jemanden; + ein selbstgesetztes Datum ist eine Notiz an sich selbst. Wer das + nicht unterscheiden kann, behandelt beides gleich -- und dann + zaehlt die Zusage so wenig wie die Notiz. + + Zwei Zeichen statt eines Wortes: Im Monatsraster ist jede + Buchstabenbreite eine, die dem Titel fehlt. */ + if (e.grund === 'zugesagt') k.append(el('span', 'k-pille__zusage', '\u2713')); k.append(document.createTextNode(e.titel)); + /* WOHER ER KOMMT, STEHT DARUNTER -- nicht davor (17.09.2026). + + Der erste Anlauf setzte den Brettnamen VOR den Titel. Am + Bildschirmfoto war sofort zu sehen, warum das nicht geht: In + einer Monatszelle von rund 150 px blieb von „Clip am Wochenende" + genau „Cli…" uebrig. Das Etikett hat die Information verdraengt, + die es einordnen sollte. + + In der zweiten Zeile kostet es KEINE Breite. Hoehe ist im + Monatsraster der billigere Platz: Eine Zelle ist gut 110 px hoch + und traegt meist ein bis zwei Eintraege. */ + if (e.brett_name) k.append(el('span', 'k-pille__brett', e.brett_name)); return k; } @@ -1023,13 +1132,29 @@ ANGABE ueber den Termin ("das ist ein Termin, kein Call"), genau wie Uhrzeit und Titel. Jetzt steht sie neben dem Titel, und die Knopfreihe enthaelt nur noch Dinge, die etwas tun. */ - titel.append(el('span', 'k-zeile__art', ARTNAME[e.art] || e.art)); + /* Bei einem Zettel vom Brett steht dort das BRETT und nicht „Vom + Brett" -- in der Liste ist Platz fuer das Wort, das wirklich + etwas sagt. In der Filterleiste bleibt es eine Art; dort waeren + achtzehn Schalter unbrauchbar. */ + titel.append(el('span', 'k-zeile__art', + e.brett_name || ARTNAME[e.art] || e.art)); const rechts = el('div', 'k-zeile__knoepfe'); - if (e.art === 'frist') { - const a = el('a', 'schritt', 'zur Aufgabe'); - a.href = 'aufgaben.html'; + /* WAS NICHT AUS DEM KALENDER STAMMT, BEKOMMT KEINE KALENDER-KNOEPFE. + + Ohne diese Zeile waere der Zettel vom Brett im `else`-Zweig + gelandet -- mit Wecker, „erledigt" und „loeschen". Und „erledigt" + haette `PATCH /workspace/api/termine/b12` geschickt: eine Nummer, + die es dort nicht gibt, an eine Schnittstelle, die davon nichts + weiss. Der Fehler waere still gewesen (eine 404 im Hintergrund), + und der Knopf haette einfach nichts getan. + + Dasselbe gilt seit jeher fuer die Frist -- deshalb steht beides + in EINER Bedingung und nicht in zwei: Es ist dieselbe Regel. */ + if (e.art === 'frist' || e.art === 'brett') { + const a = el('a', 'schritt', e.art === 'brett' ? 'zum Brett' : 'zur Aufgabe'); + a.href = e.ziel || 'aufgaben.html'; rechts.append(a); } else { /* DER WECKER (07.09.2026). Er steht bei JEDEM Termin, den man diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index f114d068..0a4415b2 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 c8941947..63d10a64 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 34efb3d6..40f01148 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 1e0469af..b14d2518 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. --> - - - - - - + + + + + + - - + + - + @@ -237,14 +237,14 @@ - - - - - - - - - + + + + + + + + + diff --git a/workspace/bewerben.html b/workspace/bewerben.html index b40418eb..62035a63 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 6f2dd4d6..f31140a9 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 118863c1..981c6c12 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 71f75ab1..653c7f5d 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 f159eddc..9d22353a 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 ded2309c..be77c18d 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 437060eb..07eea0b3 100644 --- a/workspace/crew.webmanifest +++ b/workspace/crew.webmanifest @@ -22,25 +22,25 @@ }, "icons": [ { - "src": "/assets/img/app-symbole/crew-192.png?v=202609171858", + "src": "/assets/img/app-symbole/crew-192.png?v=202609172248", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/crew-512.png?v=202609171858", + "src": "/assets/img/app-symbole/crew-512.png?v=202609172248", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609171858", + "src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609172248", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609171858", + "src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609172248", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/workspace/dateien.html b/workspace/dateien.html index 123ac2b6..4e64eeb1 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 a28a1c40..079940b9 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 3d33a07c..e04e7845 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 049a8451..bef86d9a 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 78b3e1fe..71b94717 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 95a74084..49f1488e 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 2be7a6e6..a3ec5bc9 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 9b1c1f38..fef0366a 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 977ce662..1fcbd9e9 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 3a56ac88..9610c72e 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 fe8a958c..af21ecc7 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 1de42fc5..e60f51ec 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 79628ac3..726525a7 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 11049a15..daa3a554 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 b0c5c2a2..9a9bb053 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 a32f0711..b1c05b28 100644 --- a/workspace/teamlage.html +++ b/workspace/teamlage.html @@ -7,20 +7,20 @@ - - - - - - - + + + + + + + - + - - - - + + + + diff --git a/workspace/teilen.html b/workspace/teilen.html index 3fd93986..f6ea830e 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 f04b2efc..6f316c37 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 53d3aa36..5a3489a7 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 5bf96fe6..e227620f 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 f8526ff5..f6854038 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 @@ - - - - - + + + + +