Kampagnen-Kacheln: gegliederte Texte, Geschenke mit Preis, aufklappbar

Filipe, mit einem Bildschirmfoto des Regelblocks: "man soll die titel
in den texten besser erkennen und sehen was zu was passt. ich will
auch dass man sieht welche geschenke bei dem event zaehlen und so.
ich will alle informationen die moeglich sind wenn man den link
hochlaedt. ... auch wenn man teilt soll es einfach perfekt sein. man
soll alle infos sehen die man braucht. die kacheln von den events
soll auch nicht so lang sein, die soll man aufklappen koennen. genau
wie eine trennung fuer heute, letzte woche letzten monat und so."

DER FEHLER LAG NICHT IM TEXT, SONDERN IN SEINER ANZEIGE. Im Feld
stand die ganze Zeit eine Gliederung -- Hinweise mit "* ", eine
Ueberschrift mit Untertitel hinter einem Gedankenstrich, Absaetze
ohne Zeichen, Aufzaehlungen mit "- ". Die Kachel hat daraus mit
punkteAusText() EINE flache Liste gemacht: jede Zeile derselbe
Strich, alle gleich gross.

  gliederung()      liest die Gliederung, die schon dasteht, statt
                    den Text umzuschreiben. Die beiden Kampagnen, die
                    heute in der Datenbank liegen, sehen dadurch
                    sofort richtig aus -- ohne Umstellung.
  Geschenke         kommen aus der gelesenen LISTE, nicht aus dem
                    Text, und stehen OHNE Aufklappen auf der Kachel.
  Aufklappen        alles Uebrige hinter einem Druck: gemessen 555 px
                    weniger je Kachel bei Gipfelstuermer.
  Gruppen           je Spalte laeuft / kommt / vorbei, Vergangenes
                    nach diese Woche, dieser Monat, aelter.

NEU GELESEN (stand in allen vier gespeicherten Seiten und wurde nie
angesehen): die Rangliste mit ihren Gruppen ("Superstars" bis
"5. Liga", bei Glow Up "Team Glow"/"Team Shine"), die Beschreibung je
Missionsgruppe, der Steckbrief (Kurznummer, Markt, Sprache, Zone),
der zweite Bildbaustein, und Ueberschriften INNERHALB der Klapptexte
-- "WELCHE LIGA BIN ICH?" hat jetzt sechs eingerueckte Unterpunkte
statt zwoelf gleichrangiger Zeilen.

EVENT_TEXT_MAX 6000 -> 20000. Der Regeltext von Gipfelstuermer ist
7679 Zeichen lang; es fielen bisher jedes Mal rund 1600 Zeichen
echter Inhalt weg. Die Zahl steht jetzt an EINER Stelle.

TEILEN: Was die KAMPAGNE sagt, geht hinaus (Aufgaben, Geschenke,
Regeln) -- wer WIR sind, nicht (Name, Rolle, Haus, Kampagnennummer,
Steckbrief). Und keine Hinweiszeile: "Die Preise konnten nicht
gelesen werden -- bitte nachtragen" ist eine Notiz an DogFather und
hat auf einer Seite, die er in Discord postet, nichts zu suchen.

BERICHTIGT, WEIL NACHGEMESSEN:
  - Number(null) ist 0 und 0 ist endlich -- "Feuerwerk" bekam an drei
    Stellen "0 Coins" angeschrieben, also eine falsche Preisangabe.
  - Ein Kommentar im Leser behauptete, der Wertungszeitraum weiche
    vom Kampagnenzeitraum ab. Nachgemessen ist er in allen drei
    Seiten mit Rangliste auf die Millisekunde gleich; ich hatte zwei
    verschiedene Kampagnen verglichen.
  - bild-kampagne.mjs verlangte "jede Kachel traegt den Ton ihrer
    Seite" und war damit seit dem 07.10. rot, weil an dem Tag bewusst
    das Gegenteil entschieden wurde (Zustandston, Seitenkante). Nicht
    aufgefallen, weil nach dem Lauf nur die Zahl der MESSUNGEN notiert
    wurde, nicht die der FEHLER.
  - .kv-seite > .eintrag-karte wird .kv-seite .eintrag-karte: Die
    Karten haengen durch die Gruppen eine Ebene tiefer. Das waere der
    fuenfte Direktkind-Selektor gewesen, der nach einem Umbau still
    ausfaellt -- diesmal vorher gesehen.

GEPRUEFT: 186 (lesen, +58), 148 (Route/Teilen, +25), 158 (Kachel,
+64), 67 (Agentur, +5), 67 Messungen (Bild, +14), dazu struktur,
css-klassen, zeichen, deutsche-texte, tippziele, lesbarkeit und
haus-trennung (107, unveraendert). Sechs Gegenproben: alle sechs
Sabotagen werden bemerkt, alle Dateien byte-gleich wiederhergestellt.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-10-07 22:39:03 +02:00
co-authored by Claude Opus 5
parent d7720a3687
commit cadab24a5f
58 changed files with 3950 additions and 798 deletions
+135 -3
View File
@@ -46,6 +46,11 @@ import { terminVon } from "./workspace-termin-regel.js";
-- und dann der Link). Die vierte ist immer die, die etwas
durchlaesst. */
import { darfSeite } from "./rechte.js";
/* Die Gliederung des Regeltextes kommt aus demselben Modul, das
ihn schreibt. Sie im Browser zu bauen waere die zweite Fassung
derselben Regel -- und die Teilen-Seite (workspace-teilen.js)
braeuchte dann eine dritte. */
import { gliederung } from "./kampagne-lesen.mjs";
/* EIN KATALOG AUS ZWEI QUELLEN (17.09.2026)
@@ -766,6 +771,38 @@ const MIT_ANTWORTEN = new Set(["treff", "wunsch"]);
const TITEL_MAX = 160;
const TEXT_MAX = 6000;
/* SO VIEL FASSEN DIE ZWEI EVENT-TEXTFELDER (07.10.2026).
Filipe: „ich will alle informationen die möglich sind wenn man
den link hochlädt." Gemessen stehen dem 6000 Zeichen im Weg:
Der Regeltext von „Gipfelstuermer" ist 7605 Zeichen lang --
Geschenke, sechs Preisgruppen, Rangliste mit sechs Ligen,
Teilnahmebedingungen, Themenideen. Er wurde beim Einlesen an
einer Abschnittsgrenze abgeschnitten, und abgeschnitten wurde
das Ende: rund 1600 Zeichen echter Inhalt, jedes Mal.
WARUM NICHT EINFACH TEXT_MAX ERHOEHEN: Weil die 6000 fuer das
Beschreibungsfeld eines Aushangs eine sinnvolle Grenze sind --
wer dort mehr tippt, schreibt keinen Aushang mehr. Diese zwei
Felder sind etwas anderes: Sie halten das Regelwerk einer
fremden Kampagne, und wie lang das ist, entscheidet TikTok.
Eine Grenze fuer zwei verschiedene Dinge wird immer fuer eines
von beiden falsch sein.
WARUM 20000 UND NICHT UNBEGRENZT: Eine Grenze muss es geben,
sonst ist das Feld ein Ablageplatz fuer beliebig viel Text aus
dem Netz. 20000 ist das Zweieinhalbfache des laengsten
gemessenen Regeltextes -- Platz fuer eine Kampagne, die
doppelt so ausfuehrlich ist, und keiner fuer ein Buch.
DIE ZAHL STEHT NUR HIER. workspace-kampagne.js holt sie sich
von hier (`FELD_MAX`), statt sie abzuschreiben: Zwei Fassungen
derselben Grenze laufen auseinander, und dann kuerzt der eine
Weg auf 6000, waehrend der andere 20000 erlaubt -- eine Kachel,
die man speichern kann, aber nicht so, wie sie eingelesen
wurde. */
export const EVENT_TEXT_MAX = 20000;
/* =====================================================================
DIE PUNKTE EINES AGENTUR-EVENTS (03.10.2026)
@@ -1485,6 +1522,15 @@ const SPALTEN = `
KEINE BACKTICKS IN DIESEM BLOCK: SPALTEN ist selbst ein Template.
Ein Backtick im Kommentar beendet es mitten im SQL. */
e.kampagne_id, e.kampagne_quelle,
/* DAS GELESENE SELBST (07.10.2026, zweite Runde). Daraus baut
der Server die Geschenkliste mit Coinpreisen, den Steckbrief
und die Beschreibung je Missionsgruppe. Die Spalte selbst
geht NICHT an die Oberflaeche -- sie wird unten gelesen und
dann aus der Antwort entfernt: ein Auszug ist mehrere
Kilobyte gross, und davon braucht die Kachel drei Felder.
KEINE BACKTICKS IN DIESEM BLOCK, siehe oben. */
e.kampagne_daten,
/* OB DIESES EVENT GETEILT IST (07.10.2026). Die Oberflaeche baut
daraus entweder "Teilen" oder "Link kopieren" plus "nicht mehr
teilen". Der Schluessel selbst geht NICHT mit: Er steht in der
@@ -1814,9 +1860,90 @@ bereicheRouter.get("/workspace/api/bereich/:bereich", (req, res) => {
for (const e of events) {
const haken = meine.get(e.id) || new Set();
/* ---- WAS AUS DER KAMPAGNE GELESEN WURDE -------------
`kampagne_daten` ist der Auszug, den der Leser beim
Einlesen abgelegt hat. Drei Dinge daraus stehen auf
der Kachel, und alle drei sind GELESENE Daten, nicht
aus dem Text zurueckgewonnene:
geschenke Name, Coinpreis und Hinweis je Geschenk
steckbrief Kurznummer, Region, Sprache, Zeitzone
gruppen die Beschreibung je Missionsgruppe
WARUM NICHT AUS DEM TEXT: Im Regeltext steht
„· Feuerwerk (Sammle Feuerwerk in deinem LIVE!)" --
ohne Preis, weil es ein Kampagnengeschenk ist. Wer
daraus den Preis lesen will, muss raten; wer die
Liste hat, weiss es. Genau danach hat Filipe
gefragt: „ich will auch dass man sieht welche
geschenke bei dem event zaehlen und so."
IN EIN try: Die Spalte ist Text, und ein Eintrag von
Hand oder aus einer alten Fassung muss kein JSON
sein. Eine Kachel darf daran nicht scheitern -- ohne
Auszug fehlen drei Felder, der Regeltext steht
trotzdem da. */
let auszug = null;
if (e.kampagne_daten) {
try { auszug = JSON.parse(e.kampagne_daten); } catch { auszug = null; }
}
delete e.kampagne_daten;
const gruppen = new Map();
for (const g of auszug?.aufgabenGruppen || []) {
if (g?.titel) gruppen.set(String(g.titel).trim(), g);
}
e.event_punkte = punkteAusText(e.event_aufgaben)
.map((p) => ({ ...p, meiner: haken.has(p.schluessel) }));
e.event_regelpunkte = punkteAusText(e.event_regeln);
.map((p) => {
const g = gruppen.get(p.text.trim());
return {
...p,
meiner: haken.has(p.schluessel),
/* Nur wenn es etwas zu sagen gibt. Ein leeres
Feld an jeder Aufgabe ist eine Zeile Luft
unter jedem Haken. */
...(g?.beschreibung ? { beschreibung: g.beschreibung } : {}),
...(g?.von_tag && g?.bis_tag
? { von_tag: g.von_tag, bis_tag: g.bis_tag } : {}),
};
});
/* DIE GLIEDERUNG STATT EINER FLACHEN LISTE (07.10.2026).
Hier stand `punkteAusText(e.event_regeln)` -- jede
Zeile derselbe Strich. Damit sah „INFOS ZUR
KAMPAGNE" genauso aus wie der Absatz darunter, und
die Hinweise standen zwischen den Regeln, als waeren
sie welche. Die Begruendung in Laenge steht bei
`gliederung()` in kampagne-lesen.mjs.
DER DRITTE AUSGANG BLEIBT: `event_regeln` geht
weiterhin als Rohtext mit (siehe SPALTEN). Kommt
hier nichts heraus, zeigt die Kachel den Text --
schlicht, aber vollstaendig. Eine zweite
Punkteliste daneben waere eine dritte Abschrift
desselben Inhalts in derselben Antwort. */
e.event_regelabschnitte = gliederung(e.event_regeln,
{ geschenke: auszug?.geschenke || [] });
if (auszug?.geschenke?.length) {
e.event_geschenke = {
titel: auszug.geschenkTitel || null,
hinweis: auszug.geschenkHinweis || null,
liste: auszug.geschenke,
};
}
/* Leere Felder fallen weg, statt als „—" zu erscheinen:
`union_name` ist in allen gemessenen Kampagnen leer. */
if (auszug?.steckbrief) {
const s = Object.fromEntries(Object.entries(auszug.steckbrief)
.filter(([, v]) => v !== null && v !== undefined && v !== ""));
if (Object.keys(s).length) e.event_steckbrief = s;
}
/* DIE ZAHL KOMMT MIT, statt im Browser gezaehlt zu werden --
sie steht sonst an zwei Stellen, und „2 von 3" ist genau
die Sorte Angabe, die man nicht zweimal haben will. */
@@ -2187,7 +2314,12 @@ function pruefe(bereich, körper, { neu }) {
for (const [feld, name] of [["event_aufgaben", "Die Aufgaben"], ["event_regeln", "Die Regeln"]]) {
if (körper[feld] === undefined) continue;
const t = String(körper[feld] ?? "").trim();
if (t.length > TEXT_MAX) fehler.push(`${name} sind zu lang.`);
/* EVENT_TEXT_MAX, nicht TEXT_MAX -- Begruendung oben bei der
Zahl. Eine Kachel, die der Server beim Speichern ablehnt,
laesst den Menschen davor sich selbst fuer den Fehler
halten: Er hat ein Wort geaendert und bekommt eine
Meldung ueber 1600 Zeichen, die er nie getippt hat. */
if (t.length > EVENT_TEXT_MAX) fehler.push(`${name} sind zu lang.`);
else aus[feld] = t || null;
}
/* "Bis" vor "von" ist kein Zeitraum, sondern ein Tippfehler -- und