/* ===================================================================== DIE SPENDEN — die Tabellen (28.09.2026) Filipe: „ich will dass du das mit dem paypal auch perfektionierst. ich will dass das richtig perfekt gemacht wird so dass die leute so einfach wie möglich eine spende aufs paypal machen können. und wenn jemand spendet soll auch der betrag erscheinen mit einem bild." --------------------------------------------------------------------- WAS GEHT UND WAS NICHT — nachgesehen, nicht angenommen Hinterlegt ist ein PayPal.me-Link auf ein PRIVATES Konto. Damit gilt: ES GEHT: `paypal.me//5EUR` öffnet PayPal mit schon eingetragenem Betrag. Ein Tipp, fertig. Das ist der einfachste Weg, den es für einen Zuschauer überhaupt gibt, und er braucht nichts weiter als den Link, der ohnehin schon da ist. ES GEHT NICHT VON SELBST: PayPal meldet eine Zahlung nur, wenn ein Webhook oder IPN eingerichtet ist — und beides setzt Zugangsdaten voraus, die nur Filipe selbst anlegen kann. Ob ein privates Konto das ueberhaupt kann, sagt PayPals eigene Doku nicht eindeutig. DESHALB DREI HERKUENFTE UND NICHT EINE: „hand" — Filipe sieht die PayPal-Meldung auf dem Handy und tippt den Betrag ins Pult. Geht immer, braucht nichts, ist in drei Sekunden getan. „gemeldet" — Der Zuschauer sagt nach dem Spenden selbst Bescheid. Landet als OFFEN und wird erst gezeigt, wenn jemand von der Leitung es bestaetigt. „paypal" — Kommt automatisch, sobald ein Webhook eingerichtet ist. Bis dahin steht dieser Weg einfach leer da. WARUM „gemeldet" NICHT SOFORT ERSCHEINT: Sonst tippt irgendwer „500 Euro" und steht damit gross im Bild. Eine Spende ist eine Aussage ueber Geld; die gehoert bestaetigt, bevor sie oeffentlich wird. Der Weg dahin ist EIN Tipp im Pult — das ist billig genug, dass niemand in Versuchung kommt, ihn abzukuerzen. --------------------------------------------------------------------- DER BETRAG STEHT IN CENT Nie als Kommazahl. 0.1 + 0.2 ist in jeder Programmiersprache nicht 0.3, und bei Geld faellt das irgendwann jemandem auf — meistens dem, der zahlt. Gerechnet wird in ganzen Cent, formatiert wird erst beim Anzeigen. ===================================================================== */ /** So lange bleibt eine Spende im Verlauf stehen. */ export const SPENDEN_TAGE = 400; /** Hoechstbetrag, den jemand von Hand eintragen kann (in Cent). * * Nicht gegen Betrug — wer eintraegt, gehoert zur Leitung. Gegen * den Zahlendreher: Eine Null zu viel steht sonst als „5.000 EUR" * im Bild, und das ist der Sorte Fehler, die man nicht mehr * einfangen kann, wenn hundert Leute zusehen. */ export const BETRAG_MAX_CENT = 500000; /** Was auf der Karte stehen darf. */ export const NAME_MAX = 40; export const GRUSS_MAX = 200; /** Die mitgelieferten Zeichnungen. * * DIESE LISTE IST DIE WAHRHEIT -- `spendenkarte.js` hat dieselben * Namen, und eine Pruefung haelt beide aneinander. Ein Name, den * nur eine Seite kennt, waere eine Vorlage, die sich auswaehlen * laesst und dann als Herz erscheint. */ export const VORLAGEN = ["herz", "welle", "krone", "stern", "pfote"]; /** So viele Stufen hoechstens. * * Sechs. Bei sieben merkt sich niemand mehr, was welche bedeutet -- * und eine Stufenleiter, die man nicht im Kopf hat, ist keine * Belohnung, sondern eine Preisliste. */ export const STUFEN_MAX = 6; /** Wie lange eine Karte stehen darf. */ export const DAUER_MS_MIN = 2000; export const DAUER_MS_MAX = 30000; /** Wie gross eine Karte werden darf (Faktor auf die Grundgroesse). * * Filipe: „auch so dass ich das anders gestalten kann oder die * groesse veraendern kann." * * JE STUFE UND NICHT EINMAL FUER ALLE. Genau darin steckt „spezielle * sachen bei speziellen spenden": Eine Rudel-Legende darf GROESSER * dastehen als ein Danke. Eine einzige Groesse fuer alle waere * wieder die Preisliste. * * WARUM ES NUR NACH OBEN GEHT. Die Karte ist bereits auf die * Untergrenze des Hauses gebaut: Das kleinste Wort darauf steht bei * 0,72rem, also 11,52 Pixel, und 11,5 ist die Grenze, unter die * nichts in diesem Haus faellt (pruef-css-klassen misst das). Ein * Faktor von 0,8 machte daraus 9,2 Pixel -- ein Dank, den man nicht * mehr lesen kann, ist keiner. * * KLEINER GEHT TROTZDEM -- aber an der richtigen Stelle: Die * OBS-Tafel hat ihren eigenen Regler in der Adresse (`&g=`, 0,5 bis * 4). Dort ist es eine Videoeinblendung und kein Text zum Lesen, * und dort entscheidet ohnehin die Groesse des Streams. * * Die Obergrenze ist gemessen: Ueber 2,5 passt die Karte auf 1080 * Pixel Hoehe nicht mehr neben den Chat. */ export const GROESSE_MIN = 1; export const GROESSE_MAX = 2.5; /** Wie gross ein hochgeladenes Bild sein darf. */ export const BILD_MAX_BYTES = 3 * 1024 * 1024; /** Die Stufen, die es ab Werk gibt — „paar fertige" aus Filipes * Ansage. Sie stehen hier und nicht in der Datenbank, damit ein * frisches Haus sofort etwas anzuzeigen hat; geaendert werden sie * danach in der Datenbank. * * DIE GRENZEN SIND NICHT GERATEN, sondern an dem ausgerichtet, was * Leute im Netz tatsaechlich geben: der Kaffee, die Runde, das * richtig grosse Danke. Drei Stufen, nicht sieben — bei sieben * merkt sich niemand mehr, was welche bedeutet. */ export const STUFEN_START = [ { ab_cent: 100, name: "Danke", vorlage: "herz", farbe: "#8ec9ff", dauer_ms: 6000, groesse: 1 }, { ab_cent: 500, name: "Starke Runde", vorlage: "welle", farbe: "#a98bff", dauer_ms: 8000, groesse: 1.15 }, { ab_cent: 2000, name: "Rudel-Legende", vorlage: "krone", farbe: "#e0a13a", dauer_ms: 11000, groesse: 1.45 }, ]; /** Die Betraege, die als Knopf angeboten werden (in Cent). * * VIER UND EIN FREIES FELD. Wer eine lange Liste sieht, rechnet; * wer vier Knoepfe sieht, tippt. Die Reihe endet bewusst nicht bei * einer runden Zahl wie 50 — das waere eine Erwartung, keine * Auswahl. */ export const BETRAG_KNOEPFE = [200, 500, 1000, 2500]; export function spendenTabellen(d) { d.exec(` /* Eine Zeile je Spende. Auch die abgelehnten bleiben stehen: Wer zweimal dasselbe meldet, soll nicht zweimal gefragt werden, und wer sich einen Spass erlaubt, faellt beim dritten Mal auf. */ CREATE TABLE IF NOT EXISTS spenden ( id INTEGER PRIMARY KEY AUTOINCREMENT, betrag_cent INTEGER NOT NULL CHECK (betrag_cent > 0), waehrung TEXT NOT NULL DEFAULT 'EUR', /* Wie es im Bild stehen soll. Darf vom Kontonamen abweichen -- viele wollen mit ihrem Rudelnamen dastehen, nicht mit dem, der auf der Karte steht. */ name TEXT NOT NULL DEFAULT '', gruss TEXT NOT NULL DEFAULT '', person_id INTEGER REFERENCES personen(id) ON DELETE SET NULL, herkunft TEXT NOT NULL CHECK (herkunft IN ('hand','gemeldet','paypal')), stand TEXT NOT NULL DEFAULT 'offen' CHECK (stand IN ('offen','bestaetigt','abgelehnt')), /* Die Nummer der Zahlung bei PayPal. Nur gesetzt, wenn sie von dort kam -- und dann EINMALIG (siehe Index unten): Ein Webhook wird bei einer Stoerung wiederholt, und ohne diese Sperre stuende dieselbe Spende zweimal im Bild. */ fremd_id TEXT, erstellt TEXT NOT NULL, bestaetigt_am TEXT, bestaetigt_von INTEGER REFERENCES personen(id) ON DELETE SET NULL, /* Wann die Karte im Bild lief. Leer heisst: steht noch aus. Ohne dieses Feld liefe nach jedem Neuladen alles noch einmal -- und zwar alles auf einmal. */ gezeigt_am TEXT ); /* DIESELBE ZAHLUNG NUR EINMAL. die Bedingung „fremd_id IS NOT NULL“, damit die vielen Zeilen ohne Nummer (von Hand, gemeldet) einander nicht im Weg stehen. */ CREATE UNIQUE INDEX IF NOT EXISTS idx_spenden_fremd ON spenden (fremd_id) WHERE fremd_id IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_spenden_stand ON spenden (stand, id); /* Was bei welchem Betrag passiert. Eine Zeile je Stufe. */ CREATE TABLE IF NOT EXISTS spenden_stufen ( id INTEGER PRIMARY KEY AUTOINCREMENT, ab_cent INTEGER NOT NULL UNIQUE CHECK (ab_cent > 0), name TEXT NOT NULL, /* Entweder eine mitgelieferte Vorlage ("herz", "welle", "krone") oder der Pfad eines hochgeladenen Bildes. Ein Feld und nicht zwei: „welches von beidem gilt" waere sonst eine Frage, die jede Stelle einzeln beantworten muesste. */ vorlage TEXT NOT NULL DEFAULT 'herz', bild TEXT NOT NULL DEFAULT '', farbe TEXT NOT NULL DEFAULT '#8ec9ff', dauer_ms INTEGER NOT NULL DEFAULT 7000 CHECK (dauer_ms BETWEEN 2000 AND 30000), geaendert TEXT NOT NULL DEFAULT '' ); `); /* ==== NACHGEWACHSEN: DIE GROESSE ================================ `ALTER TABLE ... ADD COLUMN` und nicht „Tabelle neu bauen": Am 11.09.2026 hat genau dieses Umkopieren an anderer Stelle drei Spalten mit Inhalt verschluckt, ohne Fehlermeldung. */ const da = new Set(d.prepare("PRAGMA table_info(spenden_stufen)").all().map((z) => z.name)); if (!da.has("groesse")) { d.exec("ALTER TABLE spenden_stufen ADD COLUMN groesse REAL NOT NULL DEFAULT 1"); console.log("[spenden] Spalte groesse angelegt."); } /* DIE DREI STUFEN ANLEGEN, WENN ES NOCH KEINE GIBT. `INSERT OR IGNORE` je Zeile und nicht „erst zaehlen, dann schreiben": Zwei Anfragen beim ersten Start wuerden sonst beide „ist leer" lesen und beide schreiben. */ const nun = new Date().toISOString(); for (const s of STUFEN_START) { d.prepare(`INSERT OR IGNORE INTO spenden_stufen (ab_cent, name, vorlage, farbe, dauer_ms, groesse, geaendert) VALUES (?,?,?,?,?,?,?)`) .run(s.ab_cent, s.name, s.vorlage, s.farbe, s.dauer_ms, s.groesse, nun); } }