Die Dogen-Muenzen: drei Saetze zu je drei Stufen

Filipe, 28.09.2026: "ich werde die drei varianten schicken fuer
niedrige spenden. mittlere und hohe spenden. alle drei varianten will
ich drauf so dass ich sie aussuchen kann wie ich will. aber pass sie
sofort diesen 3 kategorien an."

DIE ZUORDNUNG IST GELESEN, NICHT GERATEN

In allen drei Saetzen geht es nacktes Metall -> Steinkranz ->
Vollbesatz; Filipes Dateinummern (01/02/03) und die Uhrzeiten seiner
Entwuerfe laufen genau mit. Klassik: Palladium, Gold mit Saphir,
Diamant. Amethyst: Stahl, Rosegold, Vollbesatz. Neon: Chrom auf
Schwarz, Steinkranz, Vollbesatz.

AUS 25 MB WURDEN 443 KB

Je Bild 320 px WebP statt 1254 px PNG, Faktor 58. Die Zahl ist
gemessen: Groesster Fall in der Seite 110 px (Karte, hoechste Stufe),
auf der OBS-Tafel 189 px, bei doppelter Bildschirmdichte rund 220.
Drei Megabyte fuer ein 24-Pixel-Zeichen waeren ein Ladebalken mitten
in der Sendung -- wer auf dem Handy zusieht, bekaeme die Karte, wenn
sie schon wieder weg ist. Die Originale liegen neben den
Datenbanksicherungen, nicht im Repo: 26 MB, die bei jedem Klon
mitkaemen und die niemand ausliefert.

WELCHE MUENZE WANN -- ABGELEITET STATT GEPFLEGT

"Niedrig, mittel, hoch" gibt es im Haus schon: die Stufenleiter. Zwei
eigene Grenzen daneben waeren eine zweite Antwort auf dieselbe Frage,
und spaetestens beim Verschieben einer Stufe zeigte die Muenze etwas
anderes an als der Name auf der Karte. Die Leiter wird deshalb in
DRITTEL geteilt -- bei den drei Werksstufen genau eine je Muenze, bei
sechs zwei je Muenze, bei einer einzigen ueberall die mittlere.

DIE MUENZE STEHT AUCH GROSS AUF DER KARTE

Sonst haette Filipe neun Bilder fuer ein 24-Pixel-Zeichen gezeichnet.
"dogen" ist dafuer eine neue Vorlage neben Herz, Welle und Krone --
und die drei Werksstufen bekommen sie EINMAL zugeteilt, nur wo noch
die Werksvorlage steht und kein eigenes Bild hochgeladen ist. Wer
danach ein Herz zurueckstellt, findet es morgen nicht wieder als
Muenze vor: Eine Einstellung, die sich gegen den Benutzer durchsetzt,
wird abgeschafft.

Und steht sie gross da, nimmt das Stilblatt die kleine neben der Zahl
weg -- dieselbe Muenze in zwei Groessen auf einer Karte sieht aus wie
ein Versehen.

AM BILDSCHIRMFOTO NACHGEBESSERT

Bei 0,92em blieb an den Spendenknoepfen ein 13,5-Pixel-Fleck uebrig;
bei einem flachen Symbol reicht das, bei einer Muenze mit Pfote,
Schriftzug und Steinen nicht. Jetzt 1,05em ueberall und 1,6em auf den
Knoepfen. In der Auswahl sahen "Amethyst" und "Neon" bei 40 px
praktisch gleich aus -- und genau sie auseinanderzuhalten ist der
Zweck dieser Liste. Jetzt 56/64/72 px. Und was gewaehlt ist, steht
als WORT da: Neben neun glaenzenden Muenzen geht ein ruhiger Rahmen
unter, und wer Farben schlecht unterscheidet, sieht ihn gar nicht.

EIN SATZWECHSEL ERREICHT ALLE

Die Karten trugen ihren Satz immer selbst mit und waren richtig. Die
Muenzen an den Spendenknoepfen und beim eigenen Stand aber nicht --
die holt eine Seite nur bei einer Spende neu. Eine Einstellung, die
nur dort ankommt, wo sie gemacht wurde, ist keine Einstellung des
Hauses.

ZWEI BEFUNDE GEGEN DIE PRUEFUNG SELBST

1. Sie verlangte "erste Grenze ECHT kleiner als zweite". Bei genau
   zwei Stufen fallen beide absichtlich zusammen -- sie meldete einen
   Fehler, wo das Verhalten richtig ist.
2. Sie prueft die Muenzverteilung jetzt auf einer FRISCHEN Datenbank.
   Vorher lief sie auf der, die ein frueherer Abschnitt umgebaut
   hatte: Eine Pruefung, die den eigenen Kollateralschaden misst,
   misst sich selbst. Dazu eine Gegenprobe, dass eine selbst
   gewaehlte Vorlage NICHT ueberschrieben wird.

UND DREI AN DER MESSUNG

Sie suchte noch die gezeichnete Muenze (svg), wo jetzt ein Bild
liegt. Umgestellt nicht auf "ist ein img da", sondern auf
`naturalWidth > 0` -- ob es etwas ZEIGT. Ein img, das 404 antwortet,
steht genauso im Dokument wie eines, das laedt; auf dem Bildschirm
ist an seiner Stelle nichts. Und beim Satzwechsel las sie die vorige
Karte, die noch acht Sekunden stand: Die Karten laufen in einer
Schlange, also wird auf das Merkmal gewartet, das nur die neue hat.

pruef-spenden 167/0 (vorher 136) - pruef-reaktion 321/0 -
pruef-css-klassen ok - pruef-tippziele 11/0 -
mess-reaktion und mess-buehne ohne Beanstandung

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-28 19:36:05 +02:00
co-authored by Claude Opus 5
parent c00c209ac9
commit 96bdd79a91
63 changed files with 1446 additions and 770 deletions
+143 -14
View File
@@ -46,6 +46,7 @@ import {
VORLAGEN, STUFEN_MAX, DAUER_MS_MIN, DAUER_MS_MAX,
GROESSE_MIN, GROESSE_MAX, BILD_MAX_BYTES,
centInMille, milleInCent, ZEICHEN_NAME,
DOGEN_SAETZE, DOGEN_STUFEN, SATZ_NAME, muenzeAdresse,
} from "./spenden-tabellen.js";
const BILDER = join(DATEN_ORDNER, "spendenbilder");
@@ -284,7 +285,7 @@ function alsKarte(z) {
die eine Karte zeigt, an zwei Dinge denken muss. Die Karte
traegt alles, was sie braucht; genau das verspricht
spendenkarte.js in ihrem Kopf. */
zeichen: zeichenAdresse(),
zeichen: zeichenSatz(),
name: z.name,
gruss: z.gruss,
herkunft: z.herkunft,
@@ -307,15 +308,98 @@ function bildAdresse(datei) {
return datei ? `/workspace/api/buehne/spendenbild/${datei}` : "";
}
/** Das Dogen-Zeichen, wenn eines hochgeladen wurde.
/* ======================================================================
WELCHE MUENZE ZU WELCHER SPENDE
Filipe: „ich werde die drei varianten schicken fuer niedrige
spenden. mittlere und hohe spenden."
---------------------------------------------------------------------
DIE GRENZEN WERDEN ABGELEITET UND NICHT GEPFLEGT
„Niedrig, mittel, hoch" gibt es im Haus schon -- das ist die
Stufenleiter. Zwei eigene Grenzen daneben waeren eine zweite
Antwort auf dieselbe Frage, und spaetestens wenn Filipe eine Stufe
verschiebt, zeigte die Muenze etwas anderes an als der Name auf
der Karte.
Die Leiter wird deshalb in DRITTEL geteilt. Bei den drei Stufen ab
Werk ist das genau eine je Muenze. Bei sechs Stufen sind es zwei
je Muenze -- und bei einer einzigen gibt es kein Oben und Unten,
dann steht ueberall die mittlere.
====================================================================== */
function zeichenGrenzen() {
try {
const leiter = db().prepare("SELECT ab_cent FROM spenden_stufen ORDER BY ab_cent")
.all().map((z) => centInMille(z.ab_cent));
const n = leiter.length;
/* Ohne Leiter und bei genau einer Stufe gibt es keine Rangfolge:
alles bekommt die mittlere Muenze. Die Untergrenze 0 erreicht
niemand (Betraege sind immer groesser), die Obergrenze ist
unerreichbar gross -- also faellt alles dazwischen. */
if (n < 2) return [0, Number.MAX_SAFE_INTEGER];
if (n === 2) return [leiter[1], leiter[1]];
return [leiter[Math.floor(n / 3)], leiter[Math.floor((2 * n) / 3)]];
} catch { return [0, Number.MAX_SAFE_INTEGER]; }
}
/** Der gewaehlte Satz -- mit allen drei Muenzen und den Grenzen.
*
* Filipe: „die symbole schick ich dir spaeter."
* ==== EINE FORM UEBERALL
*
* Bis dahin ist die Antwort leer, und die Oberflaeche zeichnet
* selbst eine Muenze. Ein Platzhalterbild, das wie eine kaputte
* Datei aussieht, waere schlechter als eine saubere Zeichnung. */
function zeichenAdresse() {
return bildAdresse(einstellung(ZEICHEN_NAME, ""));
* Die Karte, die Listen, die Knoepfe und der eigene Stand zeigen
* alle dieselbe Muenze zur selben Zahl. Wuerde der Server fuer die
* Karte EINE Adresse schicken und fuer den Rest die Auswahl, gaebe
* es zwei Formen desselben Dinges -- und die Oberflaeche muesste an
* jeder Stelle wissen, welche gerade kommt. Also immer die Auswahl;
* wer sie benutzt, rechnet aus der Zahl.
*
* „eigen" ist der vierte Satz: ein hochgeladenes Bild, das fuer
* alle drei Stufen gilt. Wer ein einzelnes Zeichen hochlaedt, meint
* ein Zeichen und keine Rangfolge. */
function zeichenSatz() {
const eigen = einstellung(ZEICHEN_NAME, "");
const gewuenscht = einstellung(SATZ_NAME, "") || DOGEN_SAETZE[0].schluessel;
/* WAS NICHT GEHT, FAELLT AUF DEN ERSTEN SATZ ZURUECK -- etwa
„eigen", nachdem das Bild geloescht wurde. Ein leeres Zeichen
waere sonst ein kaputtes Bild neben jeder Zahl. */
const satz = gewuenscht === "eigen" && eigen ? "eigen"
: DOGEN_SAETZE.some((x) => x.schluessel === gewuenscht) ? gewuenscht
: DOGEN_SAETZE[0].schluessel;
const bild = (stufe) => (satz === "eigen"
? bildAdresse(eigen)
: muenzeAdresse(satz, stufe));
return {
satz,
eigen: eigen ? bildAdresse(eigen) : "",
niedrig: bild("niedrig"),
mittel: bild("mittel"),
hoch: bild("hoch"),
grenzen: zeichenGrenzen(),
};
}
/** Alle Saetze zum Aussuchen -- Filipe: „alle drei varianten will
* ich drauf so dass ich sie aussuchen kann wie ich will." */
function alleSaetze() {
const eigen = einstellung(ZEICHEN_NAME, "");
const liste = DOGEN_SAETZE.map((x) => ({
...x,
bilder: Object.fromEntries(DOGEN_STUFEN.map((st) => [st, muenzeAdresse(x.schluessel, st)])),
}));
/* Der eigene Satz steht nur da, wenn es ihn gibt. Ein leerer
vierter Knopf waere eine Auswahl, die nichts auswaehlt. */
if (eigen) {
const a = bildAdresse(eigen);
liste.push({
schluessel: "eigen",
name: "Eigenes Zeichen",
wort: "Dein hochgeladenes Bild, fuer alle drei Stufen",
bilder: Object.fromEntries(DOGEN_STUFEN.map((st) => [st, a])),
});
}
return liste;
}
/** Wen die Leitung einer Spende zuordnen kann.
@@ -374,9 +458,12 @@ spendenRouter.get("/workspace/api/spenden", (req, res) => {
umgestellt wurden, waere genau diese Kopie die stehen
gebliebene gewesen. */
stufen: stufenLesen(),
/* Das Zeichen, das neben jeder Zahl steht. Leer, solange
keines hochgeladen ist. */
zeichen: zeichenAdresse(),
/* Die Muenzen, die neben jeder Zahl stehen -- und die zwei
Grenzen, an denen sich entscheidet, welche. */
zeichen: zeichenSatz(),
/* Zum Aussuchen: nur fuer die Leitung. Ein Zuschauer soll das
Bild sehen, nicht die Auswahl daneben. */
saetze: darf ? alleSaetze() : null,
/* WAS SICH EINSTELLEN LAESST, KOMMT VOM SERVER. Stuenden die
Vorlagennamen und die Grenzen auch in der Oberflaeche, waeren
es zwei Listen -- und die eine liefe irgendwann von der
@@ -843,6 +930,38 @@ spendenRouter.post("/workspace/api/spenden/stufe/:id(\\d+)/bild", nurGeld,
ohne Schranke ausliefert. Ein Zeichen, das nur angemeldet
sichtbar waere, fehlte ausgerechnet im Stream.
====================================================================== */
/** Einen Satz auswaehlen. */
spendenRouter.put("/workspace/api/spenden/zeichen/satz", nurGeld,
express.json({ limit: "2kb" }), (req, res) => {
try {
const satz = String(req.body?.satz ?? "");
const geht = DOGEN_SAETZE.some((x) => x.schluessel === satz)
|| (satz === "eigen" && einstellung(ZEICHEN_NAME, ""));
/* EIN SATZ, DEN ES NICHT GIBT, WIRD ABGELEHNT -- und „eigen"
ohne hochgeladenes Bild gibt es nicht. Sonst stuende neben
jeder Zahl ein Bild, das 404 antwortet. */
if (!geht) return res.status(400).json({ fehler: "satz_unbekannt" });
einstellungSetzen(SATZ_NAME, satz, req.person);
/* ==== UND ALLE ERFAHREN ES ================================
Die KARTEN tragen ihren Satz selbst mit und waren immer
richtig. Die Muenzen an den Spendenknoepfen und beim eigenen
Stand aber nicht: Die holt sich eine Seite nur bei einer
Spende neu, und bis dahin stuende beim Zuschauer noch der
alte Satz -- und in einem zweiten Regiefenster auch.
Eine Einstellung, die nur dort ankommt, wo sie gemacht
wurde, ist keine Einstellung des Hauses. */
stromAn(empfaenger(), "spende", { was: "zeichen" });
buehneMelden("spende", { was: "zeichen" });
protokolliere("dogen_satz", {
personId: req.person.id, detail: satz, ip: echteIp(req),
});
res.json({ ok: true, zeichen: zeichenSatz(), saetze: alleSaetze() });
} catch (fehler) {
console.error("[spenden] Satz:", fehler?.message);
res.status(503).json({ fehler: "nicht_verfuegbar" });
}
});
spendenRouter.post("/workspace/api/spenden/zeichen", nurGeld,
express.raw({ type: "*/*", limit: BILD_MAX_BYTES }), zuGross, (req, res) => {
try {
@@ -855,6 +974,11 @@ spendenRouter.post("/workspace/api/spenden/zeichen", nurGeld,
const alt = einstellung(ZEICHEN_NAME, "");
einstellungSetzen(ZEICHEN_NAME, name, req.person);
/* WER EIN ZEICHEN HOCHLAEDT, WILL ES AUCH SEHEN. Ohne diese
Zeile laege es in den Einstellungen und neben den Zahlen
stuende weiter die Muenze -- und Filipe suchte den Fehler
beim Hochladen. */
einstellungSetzen(SATZ_NAME, "eigen", req.person);
/* Das alte wegraeumen -- sonst bleibt bei jedem Wechsel eine
Datei liegen, die niemand mehr findet. */
if (alt && alt !== name) {
@@ -865,7 +989,7 @@ spendenRouter.post("/workspace/api/spenden/zeichen", nurGeld,
detail: `${Math.round(req.body.length / 1024)} KB`,
ip: echteIp(req),
});
res.json({ ok: true, zeichen: zeichenAdresse() });
res.json({ ok: true, zeichen: zeichenSatz(), saetze: alleSaetze() });
} catch (fehler) {
console.error("[spenden] Zeichen:", fehler?.message);
res.status(503).json({ fehler: "nicht_verfuegbar" });
@@ -876,8 +1000,13 @@ spendenRouter.delete("/workspace/api/spenden/zeichen", nurGeld, (req, res) => {
try {
const alt = einstellung(ZEICHEN_NAME, "");
einstellungSetzen(ZEICHEN_NAME, "", req.person);
/* UND ZURUECK AUF EINEN SATZ, DEN ES GIBT. Bliebe „eigen"
stehen, zeigte jede Zahl ein Bild, das es nicht mehr gibt. */
if (einstellung(SATZ_NAME, "") === "eigen") {
einstellungSetzen(SATZ_NAME, DOGEN_SAETZE[0].schluessel, req.person);
}
if (alt) { try { unlinkSync(join(BILDER, alt)); } catch { /* war weg */ } }
res.json({ ok: true, zeichen: "" });
res.json({ ok: true, zeichen: zeichenSatz(), saetze: alleSaetze() });
} catch (fehler) {
console.error("[spenden] Zeichen loeschen:", fehler?.message);
res.status(503).json({ fehler: "nicht_verfuegbar" });
@@ -923,7 +1052,7 @@ spendenRouter.post("/workspace/api/spenden/probe", nurGeld,
const karte = {
id: 0,
dogen_mille: centInMille(cent),
zeichen: zeichenAdresse(),
zeichen: zeichenSatz(),
/* KENNTLICH ALS PROBE. Stuende hier ein Name, waere auf dem
Bildschirmfoto nicht mehr zu unterscheiden, ob jemand
gespendet hat. */