diff --git a/server/pruef-start-ansicht.mjs b/server/pruef-start-ansicht.mjs index b2a09ea9..134fbfb7 100644 --- a/server/pruef-start-ansicht.mjs +++ b/server/pruef-start-ansicht.mjs @@ -1163,6 +1163,71 @@ console.log("\n=== Ohne feinen Zeiger bleibt es aus"); await kontext.close(); } +/* ======================================================================= + MARKE UND TITEL VERRATEN NICHT DAS FALSCHE HAUS (27.09.2026) + + In start.html steht die Vorgabe des AGENTURHAUSES: "Spicy Media" und + "Zentrale". Das Teamhaus bekommt eigene Worte -- aber erst, wenn + /api/ich geantwortet hat. Bis dahin stand bei jedem Modi und jedem + Community-Mitglied die Marke der Agentur in der groessten Schrift + der Seite. + + Gefunden hat das keine Pruefung, sondern ein Einzelbild aus einer + Videoaufnahme -- die Seite sah danach ja richtig aus, und genau so + lange hat niemand hingesehen. Deshalb steht es ab jetzt hier. + + GEMESSEN WIRD IN BEIDE RICHTUNGEN: Am Ende muessen beide Zeilen + sichtbar sein (sonst haette die Reparatur die groesste Schrift der + Seite dauerhaft weggenommen), und mit gesetztem `data-wartet` + muessen sie unsichtbar sein (sonst tut die Regel gar nichts, und + die Pruefung darueber waere wertlos). */ +console.log("\n=== Marke und Titel warten, bis das Haus bekannt ist"); +{ + const kontext = await browser.newContext({ viewport: { width: 1440, height: 1200 } }); + const seite = await anmelden(kontext, "admin", "CODE-DOGI-0001"); + /* Gewartet wird auf das Merkmal, nicht auf die Uhr: Wie lange + /api/ich braucht, haengt vom Rechner ab. Mit einer festen Pause + war dieselbe Pruefung mal gruen und mal rot, ohne dass sich + etwas geaendert hatte. */ + const t0 = Date.now(); + await seite.waitForFunction( + () => !document.querySelector(".zmarke[data-wartet], #ztitel[data-wartet]"), + null, { timeout: 5000 }).catch(() => {}); + const dauerte = Date.now() - t0; + const r = await seite.evaluate(() => { + const m = document.querySelector(".zmarke"); + const t = document.getElementById("ztitel"); + const lies = (e) => (e ? Number(getComputedStyle(e).opacity) : -1); + /* OHNE UEBERGANG MESSEN, UND ZWAR VOR DER ERSTEN ABLESUNG. + `opacity` blendet in 180 ms, und getComputedStyle liefert + waehrenddessen den laufenden Zwischenwert. Das hat diese + Pruefung zweimal getaeuscht: einmal meldete die Gegenprobe + "nicht verdeckt", einmal die Messung davor "nicht sichtbar" -- + beide Male war die Regel in Ordnung und nur die Uhr im Weg. + Ohne Uebergang steht sofort der Zielwert da. */ + for (const e of [m, t]) if (e) e.style.transition = "none"; + const jetzt = { marke: lies(m), titel: lies(t) }; + m?.setAttribute("data-wartet", "ja"); + t?.setAttribute("data-wartet", "ja"); + const gedeckt = { marke: lies(m), titel: lies(t) }; + m?.removeAttribute("data-wartet"); + t?.removeAttribute("data-wartet"); + for (const e of [m, t]) if (e) e.style.transition = ""; + return { jetzt, gedeckt, breit: (t?.getBoundingClientRect().width || 0) }; + }); + ok(r.jetzt.marke === 1 && r.jetzt.titel === 1, + "nach dem Laden sind beide sichtbar " + + `(Marke ${r.jetzt.marke}, Titel ${r.jetzt.titel}, nach ${dauerte} ms)`); + ok(r.gedeckt.marke === 0 && r.gedeckt.titel === 0, + "Gegenprobe: mit data-wartet sind beide verdeckt " + + `(Marke ${r.gedeckt.marke}, Titel ${r.gedeckt.titel})`); + /* Der Platz muss auch im Wartezustand stehen -- sonst springt die + halbe Kachel, sobald das Wort ankommt. `opacity` tut das, + `display: none` taete es nicht. */ + ok(r.breit > 40, `der Titel nimmt Platz ein und rueckt nichts (${Math.round(r.breit)} px)`); + await kontext.close(); +} + await browser.close(); console.log(`\n${fehler === 0 ? "Alles in Ordnung." : fehler + " Prüfung(en) fehlgeschlagen."}`); try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ } diff --git a/server/tiktok-videos.mjs b/server/tiktok-videos.mjs index d4e81c59..45f523a2 100644 --- a/server/tiktok-videos.mjs +++ b/server/tiktok-videos.mjs @@ -26,7 +26,7 @@ node server/tiktok-videos.mjs alle vier node server/tiktok-videos.mjs 2 nur das zweite Für Video 2 (die Nachtruhe) muss der Chat schlafen: - TREFF_NACHT_AB=0 TREFF_NACHT_BIS=24 node server/tiktok-videos.mjs 2 + TREFF_NACHT_AB=12 TREFF_NACHT_BIS=6 node server/tiktok-videos.mjs 2 Das Ergebnis liegt in videos/ und ist git-ignoriert. @@ -143,6 +143,7 @@ function roh(pfad, { method = "GET", keks = "", rumpf = null } = {}) { /* ---------- Leute anlegen --------------------------------------------- Erfundene Namen, erfundene Beiträge. Nichts davon stammt aus dem echten Haus, und nichts davon geht dorthin zurück. */ +const { istNachtruhe } = await import("./workspace-treffchat.js"); const { DatabaseSync } = await import("node:sqlite"); const { scryptSync, randomBytes, createHmac } = await import("node:crypto"); const d = new DatabaseSync(process.env.WORKSPACE_DB); @@ -233,7 +234,7 @@ console.log("Fuelle die Bretter …"); soll, muss vorher drei Wuensche geschrieben haben -- sonst gilt dieselbe Sperre wie beim ersten Versuch. */ await eintrag("DogFather", "anschlag", "ansage", - "Freitag 20 Uhr — der grosse Stream", "Bringt Zeit mit. Es gibt was zu feiern."); + "Freitag 20 Uhr — der große Stream", "Bringt Zeit mit. Es gibt was zu feiern."); await eintrag("DogFather", "anschlag", "regel", "Keine Handynummern im Chat", "Auch nicht privat. Wir passen hier aufeinander auf."); await eintrag("VanVan", "anschlag", "danke", @@ -243,7 +244,7 @@ await eintrag("DogFather", "ansteht", "stream", "Freitag 20:00 — Rudel-Abend", "Der lange. Mit allem."); await eintrag("DogFather", "ansteht", "event", "Sonntag 18:00 — Fragerunde", "Ihr fragt, ich antworte. Ohne Filter."); -await eintrag("Miss Kira", "ansteht", "turnier", "Mittwoch — Turnier, 16 Plaetze", +await eintrag("Miss Kira", "ansteht", "turnier", "Mittwoch — Turnier, 16 Plätze", "Anmelden im Chat. Wer zuerst kommt."); await eintrag("Mo", "wunsch", "stream", "Nochmal die Horror-Nacht", @@ -252,33 +253,66 @@ await eintrag("Mo", "wunsch", "gemeinsam", "Gemeinsames Kochen im Live", "Jeder kocht mit, am Ende zeigen alle den Teller."); await eintrag("Mo", "wunsch", "technik", "Bitte eine zweite Kamera", "Man sieht Casper nie. Casper ist wichtig."); -await eintrag("Nachtfalter", "wunsch", "stream", "Rueckblick auf das erste Jahr", +await eintrag("Nachtfalter", "wunsch", "stream", "Rückblick auf das erste Jahr", "Mit den alten Clips. Ich heul dann halt."); await eintrag("Nachtfalter", "wunsch", "gemeinsam", "Spieleabend mit dem Rudel", "Zehn Leute, ein Spiel, offene Kamera."); -await eintrag("Nachtfalter", "wunsch", "sonstiges", "Merch in Groesse XXL", - "Ich bin gross. Wir sind mehr, als ihr denkt."); +await eintrag("Nachtfalter", "wunsch", "sonstiges", "Merch in Größe XXL", + "Ich bin groß. Wir sind mehr, als ihr denkt."); await eintrag("Sanja", "wunsch", "stream", "Ein Stream nur mit Musik", "Einfach reden und Platten auflegen."); -await eintrag("Sanja", "wunsch", "technik", "Untertitel fuer die Aufzeichnungen", - "Meine Schwester hoert schlecht und schaut trotzdem jede Folge."); +await eintrag("Sanja", "wunsch", "technik", "Untertitel für die Aufzeichnungen", + "Meine Schwester hört schlecht und schaut trotzdem jede Folge."); await eintrag("Sanja", "wunsch", "gemeinsam", "Rudel-Treffen in echt", "Einmal im Jahr. Irgendwo in der Mitte."); +/* ---------- „Will ich auch" --------------------------------------------- + + Video 4 sagt: „Und die anderen sehen, wer mitwill." Im ersten Lauf + stand an jedem Wunsch „Will ich auch · 0" -- der Untertitel + behauptete etwas, das auf dem Bildschirm widerlegt wurde. Das ist + schlimmer als ein leeres Brett: Es ist ein Versprechen, das das + Bild gleich wieder einkassiert. + + Gestimmt wird ueber die echte Route, mit echten Zugaengen. Wie + viele mitwollen, ist je Wunsch verschieden -- alle gleich waere + sichtbar gebaut. */ +{ + const brett = await roh("/workspace/api/bereich/wunsch", { keks: keks.DogFather }); + const liste = brett.j?.eintraege || brett.j?.liste || []; + const WAEHLER = ["Nachtfalter", "Lenny", "Sanja", "Tobi", "Mo", "Elif", "Miss Kira"]; + let stimmen = 0; + for (const [i, e] of liste.entries()) { + /* 6, 5, 4 … absteigend: Die Liste sortiert nach Zuspruch, und eine + Reihenfolge, die man versteht, sieht echter aus als Zufall. */ + const wie_viele = Math.max(0, 6 - i); + for (const wer of WAEHLER.slice(0, wie_viele)) { + const a = await roh(`/workspace/api/treff/eintrag/${e.id}/willich`, + { method: "PUT", keks: keks[wer], rumpf: {} }); + if (a.code === 200) stimmen++; + } + } + console.log(`Will ich auch: ${stimmen} Stimmen auf ${liste.length} Wuensche`); + if (stimmen < 10) { + console.error("Zu wenige Stimmen -- der Untertitel wuerde luegen. Es wird nicht gefilmt."); + process.exit(7); + } +} + /* Jetzt reichen die drei fuer „Stamm" -- und duerfen Highlights. */ await eintrag("Mo", "highlight", "clip", "Der Moment, als der Ton wegflog", "Zwanzig Sekunden Stille und dann alle am Lachen."); await eintrag("Nachtfalter", "highlight", "moment", "1000 Follower — live erlebt", - "Wir haben alle mitgezaehlt."); + "Wir haben alle mitgezählt."); await eintrag("Sanja", "highlight", "fanart", "Gezeichnet: Casper", "Zwei Abende gebraucht. Er guckt genau so."); await eintrag("DogFather", "mitmachen", "gesucht", "Wir suchen zwei Modis", - "Du musst nichts koennen. Du musst da sein wollen."); -await eintrag("Mo", "mitmachen", "lust", "Ich haette Lust auf Moderation", + "Du musst nichts können. Du musst da sein wollen."); +await eintrag("Mo", "mitmachen", "lust", "Ich hätte Lust auf Moderation", "Bin fast jeden Abend da. Traue mich langsam."); await eintrag("Sanja", "mitmachen", "frage", "Wie viel Zeit kostet das?", - "Ich wuerde gern, hab aber Schichtdienst."); + "Ich würde gern, hab aber Schichtdienst."); if (schiefgegangen) { console.error(`${schiefgegangen} Beitraege sind nicht entstanden. Es wird nicht gefilmt.`); @@ -303,15 +337,103 @@ const CHAT = [ ["Tobi", "Kann jemand den Link zur Fragerunde... ach nee, steht ja unter Was ansteht"], ["Nachtfalter", "Genau deswegen liebe ich das hier"], ]; +let imChat = 0; +let imRegal = 0; if (treffRaum) { - for (const [wer, text] of CHAT) { - await roh(`/workspace/api/chat/raeume/${treffRaum}/nachrichten`, { - method: "POST", keks: keks[wer], rumpf: { text }, - }); - await new Promise((r) => setTimeout(r, 60)); - } + /* GESCHRIEBEN WIRD AN DER SCHNITTSTELLE VORBEI -- UND ZWAR ABSICHTLICH + (27.09.2026). + + Fuer Video 2 laeuft die Nachtruhe. Sie verbietet das Schreiben + ALLEN, auch dieser Vorbereitung -- der erste Lauf hat deshalb + einen leeren Chat gefilmt. Ein Sonderweg in der Nachtruhe waere + genau das Loch, das der Film bewirbt; also wird hier nichts + aufgeweicht, sondern die Buehne direkt aufgebaut. Dieselbe Sorte + Eingriff wie das Anlegen der Statisten ein paar Zeilen weiter + oben, und ebenfalls nur in der Wegwerf-Datenbank. + + Dass es angekommen ist, wird gleich danach an der Schnittstelle + NACHGELESEN -- lesen darf man auch nachts. */ + const db = new DatabaseSync(process.env.WORKSPACE_DB); + const wannBasis = Date.now() - CHAT.length * 90_000; + let letzte = null; + CHAT.forEach(([wer, text], i) => { + const wann = new Date(wannBasis + i * 90_000).toISOString(); + db.prepare("INSERT INTO chat_nachrichten (raum_id, person_id, text, erstellt) VALUES (?,?,?,?)") + .run(treffRaum, ID[wer], text, wann); + letzte = wann; + }); + db.prepare("UPDATE chat_raeume SET letzte_am = ? WHERE id = ?").run(letzte, treffRaum); + db.close(); + /* NACHGEZAEHLT STATT ANGENOMMEN (27.09.2026). + Der erste Lauf von Video 2 hat einen LEEREN Chat gefilmt. Die + Nachtruhe war fuer diesen Film eingeschaltet -- und sie verbietet + nicht nur den Zuschauern das Schreiben, sondern auch dieser + Vorbereitung. Geschrieben wurde nichts, gemeldet wurde + „gefuellt", weil nur geprueft war, dass es den Raum GIBT. + Gelesen werden darf immer; deshalb faellt es hier auf. */ + const nach = await roh(`/workspace/api/chat/raeume/${treffRaum}/nachrichten`, + { keks: keks.Nachtfalter }); + imChat = (nach.j?.nachrichten || nach.j?.liste || []).length; +} +console.log(`Chat: ${treffRaum ? `${imChat} von ${CHAT.length} Nachrichten` : "Raum nicht gefunden"}`); + +/* ---------- Dogi-Media --------------------------------------------------- + + Video 3 handelt von Material zum Mitnehmen. Der erste Lauf filmte + "Gerade ist nichts frei. Schau spaeter noch einmal rein." -- eine + leere Seite, drei mal vier Sekunden lang. Das Brett war gefuellt, + das Medienregal nicht, und niemand hatte danach gefragt. + + Eingestellt wird an der Schnittstelle vorbei, aus demselben Grund + wie beim Chat: Das Hochladen erwartet einen Rumpf aus Bytes mit + passendem Typ, und die Dateien liegen ohnehin schon im Haus. Die + Namen sind echte Marken-Bilder -- kein Platzhalter, der im Video + wie ein Platzhalter aussieht. */ +{ + const { mkdirSync: mk, copyFileSync, statSync } = await import("node:fs"); + const mOrdner = join(ordner, "material"); + mk(mOrdner, { recursive: true }); + const STUECKE = [ + ["dogfather-husky.png", "Dogi-Kopf freigestellt", "Für Storys und Kommentare. Hintergrund frei."], + ["hasidog-hase.png", "Hasi Dog freigestellt", "Passt zu allem, was mit dem Rudel zu tun hat."], + ["lounge.png", "Lounge als Hintergrund", "Für eigene Clips, hochkant beschnitten."], + ["logo.png", "Team-Dogi-Logo", "Wenn du das Rudel erwähnst."], + ["gate-spicy-dogfather.jpeg", "Studio-Standbild", "Aus dem letzten Stream. Freigegeben zum Posten."], + ["logo-kopf-frei.png", "Kopf klein für die Ecke", "Klein, transparent, für die Bildecke."], + ]; + /* DIE TABELLE GIBT ES ERST NACH DEM ERSTEN ZUGRIFF. Material legt + sein Schema traege an -- ein direktes INSERT davor scheitert mit + "no such table: material". Ein Abruf reicht, um sie zu bauen. */ + await roh("/workspace/api/material", { keks: keks.DogFather }); + const db2 = new DatabaseSync(process.env.WORKSPACE_DB); + let n = 0; + STUECKE.forEach(([quelle, name, text], i) => { + const von = join(WURZEL, "assets", "img", "marke", quelle); + const endung = quelle.slice(quelle.lastIndexOf(".")); + const datei = `tk-${i}${endung}`; + let bytes = 0; + try { + copyFileSync(von, join(mOrdner, datei)); + bytes = statSync(join(mOrdner, datei)).size; + } catch { return; } + db2.prepare(`INSERT INTO material (datei, dateiname, typ, art, groesse, text, + von_id, erstellt, haus) VALUES (?,?,?,?,?,?,?,?,?)`) + .run(datei, name + endung, endung === ".jpeg" ? "image/jpeg" : "image/png", + "bild", bytes, text, ID.DogFather, + new Date(Date.now() - (6 - i) * 3600_000).toISOString(), "crew"); + n++; + }); + /* Zwei sind schon weg -- das ist die Aussage des Videos, und sie + muss auf dem Bildschirm zu sehen sein, nicht nur im Untertitel. */ + const genommen = db2.prepare("SELECT id FROM material ORDER BY id LIMIT 2").all(); + for (const g of genommen) { + db2.prepare("UPDATE material SET genommen_von = ?, genommen_am = ? WHERE id = ?") + .run(ID.Lenny, new Date(Date.now() - 7200_000).toISOString(), g.id); + } + db2.close(); + imRegal = n; + console.log(`Dogi-Media: ${n} Stuecke, davon 2 schon genommen`); } -console.log(`Chat: ${treffRaum ? "gefuellt" : "Raum nicht gefunden"}`); /* ---------- NACHGESEHEN, NICHT GEHOFFT -------------------------------- @@ -324,8 +446,9 @@ console.log(`Chat: ${treffRaum ? "gefuellt" : "Raum nicht gefunden"}`); const n = (probe.j?.eintraege || probe.j?.liste || []).length; const ich = await roh("/workspace/api/ich", { keks: keks.Nachtfalter }); console.log(`Gegenprobe: angemeldet als ${ich.j?.name || "?"} (${ich.j?.rolle || "?"}), ` - + `${n} Wuensche auf dem Brett, ${treffRaum ? CHAT.length : 0} Nachrichten im Chat`); - if (ich.code !== 200 || n < 3 || !treffRaum) { + + `${n} Wuensche auf dem Brett, ${imChat} Nachrichten im Chat, ` + + `${imRegal} Stuecke in Dogi-Media`); + if (ich.code !== 200 || n < 3 || !treffRaum || imChat < CHAT.length || imRegal < 4) { console.error("Die Buehne steht nicht. Es wird nicht gefilmt."); process.exit(5); } @@ -452,7 +575,7 @@ function buehneFuer(seite) { return { auf, /** Eine Vollbild-Karte. `` hebt ein Wort hervor. */ - async karte(html, ms = 2000) { + async karte(html, ms = 2000, { bleibt = false } = {}) { await auf(); await seite.evaluate((t) => { const k = document.getElementById("tk-karte"); @@ -460,6 +583,11 @@ function buehneFuer(seite) { k.classList.add("an"); }, html); await warte(ms); + /* `bleibt` ist fuer den Schluss. Ohne das nimmt die Karte sich + nach ihrer Zeit selbst weg -- und die letzte knappe Sekunde + des Videos zeigte wieder die App statt der Aufforderung. + Das Letzte, was haengenbleibt, soll der Satz sein. */ + if (bleibt) return; await seite.evaluate(() => document.getElementById("tk-karte").classList.remove("an")); await warte(320); }, @@ -503,6 +631,41 @@ function buehneFuer(seite) { return true; }, /** Sanft scrollen — in kleinen Schritten, damit es fließt. */ + /* Rollen INNERHALB eines Kastens. + + `window.scrollBy` bewegt die Seite. Im Chat rollt aber nicht die + Seite, sondern der Verlauf in seinem eigenen Kasten -- der + Aufruf lief dort ins Leere, und drei Bilder hintereinander sahen + gleich aus. Gemerkt habe ich es am Kontaktbogen, nicht am + Quelltext. */ + async rollenIn(wahl, px, ms = 1600) { + const schritte = Math.max(8, Math.round(ms / 40)); + for (let i = 0; i < schritte; i++) { + await seite.evaluate(([w, p]) => { + document.querySelector(w)?.scrollBy(0, p); + }, [wahl, px / schritte]); + await warte(ms / schritte); + } + }, + /* Sanft zu einem Element rollen -- und die Antwort messen, statt + eine Pixelzahl zu raten. + + Video 4 hat beim ersten Lauf den KATALOG der fertigen + Vorschlaege gefilmt statt der Wuensche, weil „480 Pixel nach + unten" zufaellig dort endete. Wie hoch ein Block ist, haengt + davon ab, was darin steht -- eine feste Zahl ist eine Rechnung + von gestern. */ + async zu(wahl, ms = 1400, rand = 150) { + const y = await seite.evaluate((w) => { + const e = document.querySelector(w); + if (!e) return null; + return e.getBoundingClientRect().top + window.scrollY; + }, wahl); + if (y === null) return false; + const von = await seite.evaluate(() => window.scrollY); + await this.rollen(Math.round(y - rand - von), ms); + return true; + }, async rollen(px, ms = 1600) { const schritte = Math.max(8, Math.round(ms / 40)); for (let i = 0; i < schritte; i++) { @@ -530,16 +693,16 @@ const FILME = { await b.karte("Also hab ich dem Rudel
ein Zuhause gebaut.", 2100); await seite.goto(`${BASIS}/workspace/start.html`, { waitUntil: "networkidle" }); await warte(800); - await b.satz("Vierzehn Tueren. Kein Discord.", 1900); + await b.satz("Vierzehn Türen. Kein Discord.", 1900); await b.rollen(560, 2100); await b.satz("Chat. Anschlagbrett. Was ansteht.", 1900); await b.rollen(600, 2100); - await b.satz("Eure Clips. Eure Wuensche.", 1900); + await b.satz("Eure Clips. Eure Wünsche.", 1900); await b.rollen(620, 2100); await b.satz("Und ein Weg, wenn was nicht stimmt.", 2000); await b.rollen(420, 1500); await b.karte("Das ist nicht mein Kanal.
Das ist unsers.", 2200); - await b.karte(CTA, 2400); + await b.karte(CTA, 2600, { bleibt: true }); }, }, @@ -552,13 +715,14 @@ const FILME = { await b.karte("Meine schickt euch
ins Bett
.", 2000); await seite.goto(`${BASIS}/workspace/chat.html?raum=treff`, { waitUntil: "networkidle" }); await warte(1600); - await b.satz("Null bis sechs schlaeft der Rudel-Chat.", 2300); - await b.rollen(240, 1300); - await b.satz("Fuer alle. Auch fuer mich.", 2100); + await b.rollenIn(".chat__verlauf", -260, 1200); + await b.satz("Null bis sechs schläft der Rudel-Chat.", 2300); + await b.rollenIn(".chat__verlauf", 200, 1300); + await b.satz("Für alle. Auch für mich.", 2100); await warte(500); await b.satz("Lesen geht. Schreiben nicht.", 2100); await b.karte("Weil ihr morgen auch noch
ein Leben habt.", 2400); - await b.karte(CTA, 2400); + await b.karte(CTA, 2600, { bleibt: true }); }, }, @@ -566,7 +730,7 @@ const FILME = { datei: "3-jedes-nur-einmal", titel: "Jedes nur einmal", async lauf(seite, b) { - await b.karte("Ich hab Clips und Bilder
fuer euch zum Posten.", 2100); + await b.karte("Ich hab Clips und Bilder
für euch zum Posten.", 2100); await b.karte("Aber jeder kriegt
nur eins.", 1900); await seite.goto(`${BASIS}/workspace/material.html`, { waitUntil: "networkidle" }); await warte(1100); @@ -575,7 +739,7 @@ const FILME = { await b.satz("Wer eins genommen hat, sieht es nicht mehr.", 2300); await b.rollen(420, 1700); await b.karte("Damit nicht einer alles nimmt.
Damit alle was haben.", 2400); - await b.karte(CTA, 2400); + await b.karte(CTA, 2600, { bleibt: true }); }, }, @@ -583,20 +747,22 @@ const FILME = { datei: "4-dazugehoeren", titel: "Dazugehoeren", async lauf(seite, b) { - await b.karte("Du wuenschst dir was im Live.", 2000); + await b.karte("Du wünschst dir was im Live.", 2000); await b.karte("Und bis morgen
hab ich es vergessen.", 2000); await seite.goto(`${BASIS}/workspace/bereich.html?b=wunsch`, { waitUntil: "networkidle" }); await warte(1100); + await b.zu(".eintrag-karte", 1700); await b.satz("Jetzt steht es da. Und geht nicht unter.", 2200); - await b.rollen(480, 2100); + await b.rollen(360, 1800); await b.satz("Und die anderen sehen, wer mitwill.", 2100); - await b.rollen(380, 1500); + await b.rollen(340, 1500); await seite.goto(`${BASIS}/workspace/bereich.html?b=mitmachen`, { waitUntil: "networkidle" }); await warte(1000); + await b.zu(".eintrag-karte", 1500); await b.satz("Wer mehr will als zuschauen:", 2000); - await b.rollen(340, 1500); - await b.karte("Du musst nichts koennen.
Du musst da sein wollen.", 2400); - await b.karte(CTA, 2400); + await b.rollen(300, 1400); + await b.karte("Du musst nichts können.
Du musst da sein wollen.", 2400); + await b.karte(CTA, 2600, { bleibt: true }); }, }, }; @@ -619,10 +785,25 @@ const browser = await pw.chromium.launch({ const fertig = []; for (const nr of liste) { const film = FILME[nr]; - if (film.nachtruhe && !(process.env.TREFF_NACHT_AB === "0" && process.env.TREFF_NACHT_BIS === "24")) { + /* GEFRAGT WIRD, OB DER CHAT SCHLAEFT -- NICHT NACH ZWEI ZAHLEN + (27.09.2026). + + Hier stand ein Vergleich auf `TREFF_NACHT_AB === "0" && + TREFF_NACHT_BIS === "24"`. Das ist die Abschrift einer + Einstellung und keine Frage nach dem Zustand: Jedes andere + Fenster, das den Chat genauso schlafen legt, wurde abgewiesen. + `istNachtruhe()` ist dieselbe Funktion, die auch die Seite + befragt -- damit kann die Antwort hier nicht von dem abweichen, + was im Bild steht. + + Empfohlen wird 12 bis 6: Dann sagt das Band auf der Seite "Ab + 06:00 Uhr geht es weiter" -- also genau das, was ein Mensch um + drei Uhr nachts auch lesen wuerde. Mit 0 bis 24 stand dort + "24:00", und das ergibt fuer einen Zuschauer keinen Sinn. */ + if (film.nachtruhe && !istNachtruhe()) { console.log(`\n--- Video ${nr} (${film.titel}) uebersprungen.`); console.log(" Dafuer muss der Chat schlafen. Noch einmal starten mit:"); - console.log(` TREFF_NACHT_AB=0 TREFF_NACHT_BIS=24 node server/tiktok-videos.mjs ${nr}`); + console.log(` TREFF_NACHT_AB=12 TREFF_NACHT_BIS=6 node server/tiktok-videos.mjs ${nr}`); continue; } console.log(`\n--- Video ${nr}: ${film.titel}`); diff --git a/tools/.commit-video.txt b/tools/.commit-video.txt new file mode 100644 index 00000000..c4b26f87 --- /dev/null +++ b/tools/.commit-video.txt @@ -0,0 +1,114 @@ +Beim Laden stand "SPICY MEDIA - Zentrale" auf jeder Startseite + +WAS DAS BILD GEZEIGT HAT + +Beim Durchsehen der Einzelbilder einer Videoaufnahme -- vier +Sekunden lang, gleich nach dem Laden: + + ---- SPICY MEDIA ---- + Zentrale + WIRD GELADEN + +Und zwar auf der Startseite eines COMMUNITY-MITGLIEDS. + +In start.html steht die Vorgabe des Agenturhauses. Das Teamhaus +bekommt eigene Worte ("Team Dogi" / "Die IrrenAnstalt"), aber erst +wenn /api/ich geantwortet hat. Bis dahin las jeder Modi und jedes +Community-Mitglied die Marke der Agentur in der groessten Schrift +der Seite. Auf einem langsamen Telefon sekundenlang, bei jedem +Aufruf. + +Das ist kein Schoenheitsfehler. Spicy Media ist der Betrieb hinter +der Agentur, und seit dem 24.09. sind die beiden Haeuser getrennt. +Genau diese Zeile hat die Trennung bei jedem Seitenaufruf kurz +aufgehoben -- und niemandem ist es aufgefallen, weil die Seite +DANACH richtig aussah. Wer hinsieht, sieht den Endzustand. + +GELOEST OHNE SPRUNG + +`data-wartet` macht die zwei Zeilen durchsichtig, nicht leer -- der +Platz bleibt stehen, es ruckt nichts. start.js nimmt das Merkmal +weg, sobald es weiss, in welchem Haus es ist; gemessen dauert das +351 ms. + +Eine Notbremse in der Seite nimmt es nach vier Sekunden notfalls +selbst weg. Ohne sie waere die groesste Schrift der Seite fuer immer +unsichtbar, falls start.js gar nicht erst laeuft -- und "unsichtbar" +waere schlimmer als das falsche Wort. + +DIE PRUEFUNG DAZU -- UND ZWEI MESSFEHLER DARIN + +pruef-start-ansicht misst ab jetzt beide Richtungen: nach dem Laden +muessen beide Zeilen sichtbar sein, mit gesetztem `data-wartet` +unsichtbar. Ohne die zweite Haelfte koennte die Regel spurlos +verschwinden, ohne dass etwas rot wird. + +Die Pruefung selbst hat mich zweimal getaeuscht, und beide Male +lehrreich: + + 1. Sie mass 900 ms nach der Anmeldung -- eine feste Pause. Ob + /api/ich in dieser Zeit geantwortet hat, haengt vom Rechner ab. + Dieselbe Pruefung war einmal gruen und einmal rot, bei + unveraendertem Code. Gewartet wird jetzt auf das Merkmal selbst, + und wie lange es gedauert hat, steht im Meldetext. + + 2. `opacity` hat einen Uebergang von 180 ms, und getComputedStyle + liefert waehrenddessen den laufenden Zwischenwert statt des + Ziels. Erst meldete die Gegenprobe "nicht verdeckt", dann die + Messung davor "nicht sichtbar" -- beide Male war die Regel in + Ordnung und nur die Animation im Weg. Fuer die Messung wird der + Uebergang jetzt abgeschaltet. + +DAS VIDEOWERKZEUG + +server/tiktok-videos.mjs nimmt die vier Clips auf. Sechs Aenderungen, +jede aus einem Einzelbild: + + - DIE NACHTRUHE HAT DIE VORBEREITUNG MITBLOCKIERT. Video 2 zeigte + einen leeren Chat. Gemeldet wurde "gefuellt", weil nur geprueft + war, dass es den RAUM gibt. Jetzt werden die Nachrichten + zurueckgelesen und gezaehlt; unter acht wird nicht gefilmt. + - DOGI-MEDIA WAR LEER -- ein Video ueber Material zum Mitnehmen, + und auf dem Bildschirm stand "Gerade ist nichts frei". Sechs + echte Marken-Bilder werden eingestellt, zwei davon schon + genommen, damit die Aussage des Films auch im Bild steht. + - "WILL ICH AUCH - 0" an jedem Wunsch, waehrend der Untertitel + sagte "die anderen sehen, wer mitwill". Ein Versprechen, das das + Bild gleich wieder einkassiert, ist schlimmer als ein leeres + Brett. Jetzt wird ueber die echte Route gestimmt, absteigend + 6/5/4 -- unter zehn Stimmen wird nicht gefilmt. + - DER WAECHTER FUER VIDEO 2 verglich zwei feste Zahlen + (TREFF_NACHT_AB === "0"). Das ist die Abschrift einer Einstellung + und keine Frage nach dem Zustand: Jedes andere Fenster, das den + Chat genauso schlafen legt, wurde abgewiesen. Gefragt wird jetzt + `istNachtruhe()` -- dieselbe Funktion, die auch die Seite + befragt. Mit 12 bis 6 steht im Bild "Ab 06:00 Uhr geht es + weiter" statt "Ab 24:00 Uhr", und das ergibt fuer einen + Zuschauer ueberhaupt erst Sinn. + - ROLLEN IM KASTEN STATT IN DER SEITE. `window.scrollBy` bewegt + die Seite; im Chat rollt aber der Verlauf in seinem eigenen + Kasten. Drei Bilder hintereinander sahen gleich aus. + - EINE FESTE PIXELZAHL TRAF DEN FALSCHEN BLOCK. Video 4 filmte + den Katalog der fertigen Vorschlaege statt der Wuensche, weil + "480 nach unten" zufaellig dort endete. `b.zu(wahl)` misst, wo + das Element steht. + +Dazu: Umlaute in allen Bildtexten ("Tueren" stand im Video), der +Abspann bleibt am Ende stehen (vorher sah man die letzte Sekunde +wieder die App), und die Dateigroessen im Regal sind echt. + +KEIN LINK, UND ZWAR GEMESSEN + +Nach jedem Seitenwechsel wird der SICHTBARE Text nach Adressen +abgesucht -- dogfather-universe, https://, www., jeder Domainname. +Bei einem Fund bricht die Aufnahme ab, und es wird keine Datei +geschrieben. Das mit dem Auge zu pruefen waere genau die Sorte +Kontrolle, die beim vierten Video nachlaesst. + +Mit PROBE_LECK=ja schiebt die Suche selbst eine Adresse ins Bild -- +nachgefahren, Rueckgabewert 2. Eine Suche, die nur "nichts gefunden" +sagen kann, hat nichts bewiesen. + +Gemessen: pruef-start-ansicht 160/0 (vorher 157), pruef-buehne +242/0, pruef-handy 177/0, pruef-kachelraster 24/0, +pruef-css-klassen 33/0. diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index c23f447b..191f2208 100644 --- a/workspace/anruf-probe.html +++ b/workspace/anruf-probe.html @@ -16,8 +16,8 @@ verdeckt. Gefunden hat es pruef-struktur. Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. --> - - + + - +