/* ===================================================================== DIE WERBEEINBLENDUNG (29.09.2026) Filipe: „ich will dass du auch perfektionierst dass ich einen banner durchlaufen kann, oder erscheinen lassen kann mit werbung drauf … und ich will auch das dogfather logo oben rechts links unten rechts links genau wie oben unten mittig, so dass ich es wegmachen kann und hinzufügen kann und genau so auch den hasen mit dem rabattcode." --------------------------------------------------------------------- WARUM DAS EIN EIGENES STÜCK IST Dasselbe Band läuft an zwei Orten: in der Reaction-Seite, wo das Rudel zusieht, und in der Bühnenquelle, die OBS abfilmt. Zweimal gebaut hieße: Bei der nächsten Änderung sieht es an einem der beiden Orte anders aus — und gemerkt wird das im Livestream. Genau wie bei der Spendenkarte hängt hier deshalb nichts an der Reaction. Wer die Einblendung zeigen will, gibt ihr einen Kasten. --------------------------------------------------------------------- DREI ARTEN, UND JEDE HAT IHREN FALL aus nichts. Die Vorgabe. laufband der Text zieht durch. Fällt auf, ist schwer zu merken. wechsel eine Botschaft steht, blendet aus, die nächste kommt. EIN RABATTCODE GEHÖRT IN DEN WECHSEL. „DOGI10" hinterherzulesen, während es nach links wandert, ist genau das, woran Codes scheitern — man sieht sie und hat sie nicht. --------------------------------------------------------------------- WER NICHT MAG, DASS SICH ETWAS BEWEGT `prefers-reduced-motion` schaltet das Laufband auf Wechsel um. Nicht auf „aus": Die Botschaft ist der Zweck, die Bewegung nur ihr Transportmittel. Wer eine Einstellung für weniger Bewegung setzt, will weniger Bewegung — nicht weniger Inhalt. ===================================================================== */ (() => { "use strict"; /* Die zwei Bilder. Sie liegen unter `assets/img/` und werden ohne Anmeldung ausgeliefert — die OBS-Quelle hat keine. */ const BILDER = { dogi: { datei: "werbung-dogi.webp", wort: "Dogfather Universe" }, hase: { datei: "werbung-hase.webp", wort: "Häkelhase von VanVan" }, }; /* Wo ein Bild stehen kann. Der Schlüssel steht auch am Element als `data-platz` — das Stilblatt rechnet daraus die Lage, nicht das Programm. Eine Position in JavaScript zu setzen hieße, sie in zwei Sprachen zu pflegen. */ const PLAETZE = new Set(["ol", "om", "or", "ul", "um", "ur"]); /* EIN RABATTCODE WIRD HERVORGEHOBEN — automatisch, aber eng. Die Regel ist bewusst streng: vier bis zwölf Zeichen, nur Großbuchstaben und Ziffern, und MINDESTENS EINE ZIFFER. Damit trifft sie „DOGI10" und nicht „VANVAN", nicht „TIKTOK", nicht „AGB". Eine weichere Regel würde irgendwann ein gewöhnliches Wort einrahmen, und das sähe nach Fehler aus. Warum überhaupt automatisch: Ein eigenes Feld „Code" wäre ehrlicher und ein Feld mehr beim Eintragen. Der Code steht ohnehin im Satz; ihn dort zu erkennen kostet den, der tippt, gar nichts. */ const CODE = /\b([A-ZÄÖÜ]{2,}[A-ZÄÖÜ0-9]*[0-9][A-ZÄÖÜ0-9]*)\b/; const el = (art, klasse, text) => { const k = document.createElement(art); if (klasse) k.className = klasse; if (text !== undefined) k.textContent = text; return k; }; /* Eine Zeile des Bandes: Bild, Text, Unterzeile. DER TEXT WIRD NICHT ALS HTML EINGESETZT. Er kommt aus einem Eingabefeld; als `innerHTML` wäre jedes spitze Zeichen darin ein offenes Tor. Der Code-Rahmen entsteht deshalb aus echten Elementen und nicht aus zusammengebautem Text. */ function zeile(b) { const k = el("div", "werbung__stueck"); if (b.bild && BILDER[b.bild]) { const bild = el("img", "werbung__bild"); bild.src = `assets/img/${BILDER[b.bild].datei}`; bild.alt = ""; // die Aussage steht im Text daneben bild.decoding = "async"; k.append(bild); } const texte = el("div", "werbung__texte"); const haupt = el("div", "werbung__text"); const treffer = CODE.exec(b.text || ""); if (treffer) { haupt.append(document.createTextNode((b.text || "").slice(0, treffer.index))); haupt.append(el("span", "werbung__code", treffer[1])); haupt.append(document.createTextNode( (b.text || "").slice(treffer.index + treffer[1].length))); } else { haupt.textContent = b.text || ""; } texte.append(haupt); if (b.unterzeile) { const unter = el("div", "werbung__unter"); const t2 = CODE.exec(b.unterzeile); if (t2) { unter.append(document.createTextNode(b.unterzeile.slice(0, t2.index))); unter.append(el("span", "werbung__code", t2[1])); unter.append(document.createTextNode( b.unterzeile.slice(t2.index + t2[1].length))); } else { unter.textContent = b.unterzeile; } texte.append(unter); } k.append(texte); return k; } /* Hängt die Einblendung in einen Kasten und gibt einen Griff zurück, mit dem man sie versorgt. */ function einhaengen(kasten) { const wurzel = el("div", "werbung"); /* `aria-hidden`: Für ein Vorleseprogramm ist ein wanderndes Band eine Zumutung -- es liest bei jedem Durchlauf neu vor. Die Botschaften sind Werbung und keine Bedienung; wer die Seite hört, verliert nichts, was er braucht. */ wurzel.setAttribute("aria-hidden", "true"); const band = el("div", "werbung__band"); const lauf = el("div", "werbung__lauf"); band.append(lauf); wurzel.append(band); const ecken = {}; for (const welches of ["dogi", "hase"]) { const e = el("div", `werbung__ecke werbung__ecke--${welches}`); const bild = el("img"); bild.src = `assets/img/${BILDER[welches].datei}`; bild.alt = ""; bild.decoding = "async"; e.append(bild); e.hidden = true; ecken[welches] = e; wurzel.append(e); } kasten.append(wurzel); let uhr = null; let zuletzt = ""; function aufraeumen() { if (uhr) { clearInterval(uhr); uhr = null; } } /* Was gerade zu sehen ist. * * NICHTS WIRD NEU GEBAUT, WENN SICH NICHTS GEÄNDERT HAT. Der * Stand kommt bei jedem Rundruf -- und ein Laufband, das dabei * jedes Mal von vorn anfinge, spränge mitten im Satz zurück. * Verglichen wird der ganze Inhalt, nicht nur die Anzahl: Wer * eine Botschaft ändert, will sie geändert sehen. */ function setzen(w) { const daten = w || {}; const band_ = Array.isArray(daten.band) ? daten.band : []; const art = ["laufband", "wechsel"].includes(daten.art) ? daten.art : "aus"; const tempo = Number(daten.tempo) > 0 ? Number(daten.tempo) : 16; /* Die Ecken -- unabhängig vom Band. Wer nur ein Wasserzeichen will, soll kein Band einschalten müssen. */ const gross = Number(daten.ecke_gross) > 0 ? Number(daten.ecke_gross) : 1; wurzel.style.setProperty("--ecke-gross", gross); for (const [welches, feld] of [["dogi", "ecke_dogi"], ["hase", "ecke_hase"]]) { const platz = String(daten[feld] || "aus"); const e = ecken[welches]; if (PLAETZE.has(platz)) { e.dataset.platz = platz; e.hidden = false; } else { e.hidden = true; delete e.dataset.platz; } } const kennung = JSON.stringify([art, tempo, band_]); if (kennung === zuletzt) return; zuletzt = kennung; aufraeumen(); if (art === "aus" || band_.length === 0) { band.hidden = true; lauf.textContent = ""; wurzel.dataset.art = "aus"; return; } band.hidden = false; wurzel.dataset.art = art; lauf.textContent = ""; /* Weniger Bewegung? Dann wird aus dem Laufband ein Wechsel -- nicht „nichts". Siehe der Absatz oben im Kopf. */ const ruhig = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; const wirklich = (art === "laufband" && ruhig) ? "wechsel" : art; wurzel.dataset.art = wirklich; if (wirklich === "laufband") { /* ZWEIMAL DIESELBE FOLGE. Das Band wandert um genau die Breite EINER Folge; die zweite steht dann schon da, wo die erste war. Ohne sie entstünde am Ende jedes Durchlaufs eine leere Fläche, und das sieht aus, als sei die Einblendung abgestürzt. */ for (let runde = 0; runde < 2; runde++) { const folge = el("div", "werbung__folge"); band_.forEach((b) => folge.append(zeile(b))); folge.setAttribute("aria-hidden", "true"); lauf.append(folge); } lauf.style.setProperty("--dauer", `${tempo}s`); return; } /* Wechsel: alle Botschaften liegen übereinander, eine ist sichtbar. Übereinander statt nacheinander eingesetzt, damit die Höhe nicht springt -- die höchste Botschaft bestimmt sie, und zwar von Anfang an. */ band_.forEach((b, i) => { const z = zeile(b); z.classList.add("werbung__stueck--wechsel"); z.dataset.dran = i === 0 ? "ja" : "nein"; lauf.append(z); }); if (band_.length < 2) return; let i = 0; uhr = setInterval(() => { const alle = lauf.children; alle[i].dataset.dran = "nein"; i = (i + 1) % alle.length; alle[i].dataset.dran = "ja"; }, tempo * 1000); } return { setzen, wurzel, aufraeumen }; } window.Werbung = { einhaengen, BILDER, CODE }; })();