Die Buehne der Videos ist gestaltet statt hingestellt

Filipe, 27.09.2026: „die graphik sieht scheisse aus und so. mach das
hoch profissionel und nicht so eine halbe geschissene scheisse."

Er hat recht gehabt, und der Grund war nicht Geschmack.

DIE FARBE GEHOERTE NIRGENDWO HIN

Karten und Untertitel standen in #9cde90 -- das ist der Ton der
NOTIZEN-Kachel aus dem Teambereich. In diesen Videos ist die
Community-Seite zu sehen, und die laeuft auf --violett #a98bff und
--akzent #8ec9ff ueber --tinte #07060f (crew-haus.css). Eine
Einblendung in einer Farbe, die im Bild gar nicht vorkommt, sieht
immer aufgeklebt aus, egal wie sauber sie gebaut ist. Das war der
eigentliche Befund; alles andere kam obendrauf.

WAS JETZT STEHT

Die Karte: Zierzeile „- TEAM DOGI -" wie ueber dem Titel in der App,
Ueberschrift mit ausgeglichenen Zeilen, langsame Kamerafahrt von drei
Prozent ueber zweieinhalb Sekunden, feines Korn gegen Streifen in
dunklen Flaechen. Der Abspann bekommt eine eigene hellere Flaeche und
einen Strich -- er ist das Einzige, was der Mensch danach noch tun
soll.

Der Untertitel: Glasflaeche mit Farbstreifen links. Rechts bleiben
17 % frei, dort liegt auf TikTok die Knopfreihe; unten bleibt Platz
fuer Beschreibung und Ton. Vorher war es ein graues Kaestchen mit
gruenem Rand ueber die ganze Breite.

Der Vorhang: Jeder Seitenwechsel laeuft dahinter. Vorher war jeder
Wechsel im Bild -- „Eintraege werden geladen ...", halb aufgebaute
Raster, ein kurzes Aufblitzen. Zusammen sah das nach
Bildschirmaufnahme aus und nicht nach Film.

EINE FUNKTION STATT EINER ZEICHENKETTE

Die Buehne war ein Text, der im Browser ausgewertet wurde, und darin
noch ein Text fuer das Stilblatt -- zwei Ebenen Anfuehrungszeichen, in
denen jedes Backtick von Hand entschaerft werden musste. Dabei ist
jede zweite Aenderung kaputtgegangen. Playwright nimmt auch eine echte
Funktion und reicht ein Argument durch; die ganze Schicht faellt weg.

VIER FEHLER, DIE NUR DAS EINZELBILD GEZEIGT HAT

  1. WAISEN IN DER ZEILE. „Ein Kommentar ist nach / nach / drei
     Sekunden weg." -- ein von Hand gesetztes <br> traf eine Zeile,
     die ohnehin umbrach. Ein Umbruch, den jemand vor drei Tagen
     gesetzt hat, weiss nichts von der Schriftgroesse von heute.
     `text-wrap: balance` rechnet ihn aus; die elf Handumbrueche
     sind weg.
  2. DER STRICH UNTER DEM ABSPANN WAR NIE ZU SEHEN. Er steht in
     einem <span>, und ein span ist inline -- Hoehe, Breite und
     margin:auto gelten dort nicht. Gebaut, unsichtbar, und ohne das
     Bild haette ich weiter geglaubt, er sei da.
  3. DER VORHANG GING ZU FRUEH AUF, dreimal aus drei Gruenden. Erst
     standen 700 ms -- eine Zahl. Dann eine einmalige Frage „steht
     irgendwo, dass geladen wird?" -- und die traf den Moment, BEVOR
     die Seite ihren Ladehinweis gezeichnet hatte. Dann 450 ms Ruhe
     -- zu kurz, weil eine Seite ihre Abschnitte nacheinander laedt
     und es zwischen zweien kurz still ist. Jetzt: 650 ms am Stueck
     ohne Hinweis, ein Aufflackern setzt die Uhr zurueck.
  4. DAS LEUCHTEN AUF DEN BLAUEN WOERTERN war zu breit und hat die
     Buchstabenkanten gefressen; im Video wurde daraus Matsch.

NICHT `networkidle` ABGEWARTET, UND ZWAR ABSICHTLICH: Der Chat haelt
einen Ereignisstrom offen, der nie endet. Ein Warten darauf liefe dort
immer in die Frist und haenge jedem Seitenwechsel neun Sekunden an --
dieselbe Falle wie am 06.09.2026 bei `await antwort.text()` auf einen
Stream.

Alle vier Videos neu gedreht: 1080x1920, 24 bis 33 s, Lecksuche
unveraendert scharf.
This commit is contained in:
2026-09-27 19:05:48 +02:00
parent d465c89289
commit a28a8ea2e3
+301 -59
View File
@@ -516,48 +516,207 @@ const BASIS = `https://${CREW}:${HTTPS_PORT}`;
die App (das Video sieht aus wie ein Stück), und Umlaute sind in
ffmpegs Textfilter auf Windows eine eigene Wissenschaft.
======================================================================= */
const BUEHNE = `
(() => {
if (document.getElementById("tk-buehne")) return;
const s = document.createElement("style");
s.textContent = \`
#tk-buehne{position:fixed;inset:0;z-index:2147483000;pointer-events:none;
font-family:system-ui,"Segoe UI",sans-serif}
#tk-karte{position:absolute;inset:0;display:grid;place-items:center;
background:#05070d;opacity:0;transition:opacity .28s ease;padding:0 34px}
#tk-karte.an{opacity:1}
#tk-karte p{margin:0;text-align:center;font-weight:800;font-size:27px;
line-height:1.24;color:#fff;letter-spacing:-.01em;
text-shadow:0 0 30px rgba(158,222,144,.28)}
#tk-karte p b{color:#9cde90}
#tk-satz{position:absolute;left:16px;right:16px;bottom:210px;
opacity:0;transform:translateY(8px);transition:opacity .22s ease,transform .22s ease}
#tk-satz.an{opacity:1;transform:none}
#tk-satz span{display:inline-block;background:rgba(5,7,13,.86);
border:1px solid rgba(158,222,144,.32);border-radius:12px;
padding:9px 13px;font-weight:700;font-size:18px;line-height:1.32;color:#fff;
box-shadow:0 10px 30px -12px #000}
#tk-finger{position:absolute;width:34px;height:34px;margin:-17px 0 0 -17px;
border-radius:50%;background:rgba(255,255,255,.22);
border:2px solid rgba(255,255,255,.75);opacity:0;
transition:left .45s cubic-bezier(.4,0,.2,1),top .45s cubic-bezier(.4,0,.2,1),
opacity .2s ease,transform .18s ease}
#tk-finger.an{opacity:1}
#tk-finger.tipp{transform:scale(.7);background:rgba(158,222,144,.55)}
\`;
document.head.appendChild(s);
/* =======================================================================
DIE BUEHNE — was ueber der App liegt
EINE FUNKTION STATT EINER ZEICHENKETTE: Vorher stand hier ein
Text, der im Browser ausgewertet wurde, und darin noch einmal ein
Text fuer das Stilblatt. Zwei Ebenen Anfuehrungszeichen, in denen
jedes Backtick und jedes ${ von Hand entschaerft werden musste --
dabei ist jede zweite Aenderung kaputtgegangen. Playwright nimmt
auch eine echte Funktion und reicht ein Argument durch; damit
faellt die ganze Schicht weg.
DIE FARBEN KOMMEN AUS DEM HAUS, DAS IM BILD IST. crew-haus.css
fuehrt --akzent #8ec9ff und --violett #a98bff auf --tinte #07060f.
Vorher stand hier #9cde90 -- der Ton der Notizen-Kachel aus dem
Teambereich, der in diesen Videos gar nicht vorkommt. Eine
Einblendung in einer Farbe, die im Bild fehlt, sieht aufgeklebt
aus, egal wie sauber sie gebaut ist.
GEMESSEN WIRD IN 360x640 UND GEZEIGT IN 1080x1920: Der Browser
laeuft mit dreifacher Geraeteskalierung. Jede Zahl hier ist also
ein Drittel dessen, was am Ende zu sehen ist -- 30px Schrift sind
90px im Video.
======================================================================= */
function BUEHNE(blendeZu) {
if (document.getElementById("tk-buehne")) {
/* Nach einem Seitenwechsel steht sie schon -- dann nur den
Vorhang in den richtigen Zustand bringen. */
const v = document.getElementById("tk-blende");
if (v) v.className = blendeZu ? "zu" : "";
return;
}
const AKZENT = "#8ec9ff";
const VIOLETT = "#a98bff";
const TINTE = "#07060f";
/* Feines Korn. Ohne das wirken grosse dunkle Flaechen im Video
flach und zeigen Streifen, weil der Kodierer wenig Information
hat, an der er sich festhalten kann. */
const KORN = "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' "
+ "width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' "
+ "baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' "
+ "filter='url(%23n)'/%3E%3C/svg%3E\")";
const css = [
'#tk-buehne{position:fixed;inset:0;z-index:2147483000;pointer-events:none;',
' font-family:"Segoe UI",system-ui,-apple-system,"Helvetica Neue",Arial,sans-serif;',
' -webkit-font-smoothing:antialiased}',
/* ---- Der Schleier ueber der App ------------------------------
Nimmt den Raendern Helligkeit, damit Schrift darauf immer
liest -- und gibt dem Bild eine Mitte. Sehr leicht: Es soll
niemandem auffallen, es soll nur wirken. */
'#tk-vig{position:absolute;inset:0;background:',
' radial-gradient(125% 85% at 50% 42%, rgba(0,0,0,0) 52%, rgba(4,3,10,.52) 100%)}',
/* ---- Die Vollbild-Karte --------------------------------------- */
'#tk-karte{position:absolute;inset:0;display:grid;place-items:center;',
' padding:0 30px;opacity:0;transition:opacity .30s ease}',
'#tk-karte.an{opacity:1}',
'#tk-karte::before{content:"";position:absolute;inset:0;background:',
' radial-gradient(115% 62% at 16% 6%, rgba(169,139,255,.26), rgba(0,0,0,0) 62%),',
' radial-gradient(105% 58% at 88% 96%, rgba(142,201,255,.18), rgba(0,0,0,0) 64%),',
' linear-gradient(180deg,#0c0a1b 0%,' + TINTE + ' 58%,#050409 100%)}',
'#tk-karte::after{content:"";position:absolute;inset:0;opacity:.05;',
' background-image:' + KORN + ';background-size:140px 140px}',
/* Der Inhalt kommt langsam naeher. Eine Karte, die exakt
stillsteht, sieht aus wie ein Bild; drei Prozent Bewegung ueber
zweieinhalb Sekunden sieht aus wie Film. */
'#tk-karte .innen{position:relative;text-align:center;max-width:300px;',
' transform:scale(1.035);transition:transform 2.6s cubic-bezier(.16,.8,.3,1)}',
'#tk-karte.an .innen{transform:scale(1)}',
/* Die Zierzeile -- dieselbe Idee wie ueber dem Titel der App:
zwei Striche, ein Wort dazwischen, weit gesperrt. */
'#tk-karte .kicker{display:flex;align-items:center;justify-content:center;gap:9px;',
' font-size:9px;font-weight:700;letter-spacing:.38em;text-transform:uppercase;',
' color:' + VIOLETT + ';opacity:0;transform:translateY(10px);',
' transition:opacity .42s ease .04s,transform .42s cubic-bezier(.16,.8,.3,1) .04s}',
'#tk-karte .kicker i{display:block;width:22px;height:1px;',
' background:linear-gradient(90deg,rgba(169,139,255,0),' + VIOLETT + ')}',
'#tk-karte .kicker i + i{background:linear-gradient(90deg,' + VIOLETT + ',rgba(169,139,255,0))}',
'#tk-karte.an .kicker{opacity:.92;transform:none}',
/* `text-wrap: balance` verteilt die Woerter selbst auf die Zeilen.
Von Hand gesetzte Umbrueche koennen das nicht: Sie kennen die
Schriftgroesse nicht, mit der sie spaeter gelesen werden --
genau so entstand „... ist nach / nach / drei Sekunden weg." */
'#tk-karte p{margin:14px 0 0;font-size:31px;font-weight:800;line-height:1.10;',
' letter-spacing:-.024em;color:#f1edff;text-wrap:balance;',
' opacity:0;transform:translateY(14px);',
' transition:opacity .46s ease .16s,transform .46s cubic-bezier(.16,.8,.3,1) .16s;',
' text-shadow:0 2px 26px rgba(0,0,0,.6)}',
'#tk-karte.an p{opacity:1;transform:none}',
/* Wenig Schein, dafuer ein enger dunkler Schatten: Ein breiter
Schein frisst die Kanten der Buchstaben, und im Video wird
daraus Matsch. */
'#tk-karte p b{color:' + AKZENT + ';font-weight:800;',
' text-shadow:0 1px 2px rgba(3,2,8,.85),0 0 16px rgba(142,201,255,.30)}',
/* Der Abspann bekommt mehr Gewicht als die Zwischenkarten: Er ist
das Einzige, was der Mensch danach noch tun soll. */
'#tk-karte.ende::before{background:',
' radial-gradient(100% 52% at 50% 6%, rgba(169,139,255,.36), rgba(0,0,0,0) 66%),',
' radial-gradient(130% 62% at 50% 94%, rgba(142,201,255,.22), rgba(0,0,0,0) 64%),',
' linear-gradient(180deg,#120e27 0%,#0a0817 56%,#050409 100%)}',
'#tk-karte.ende p{font-size:32px}',
/* `display:block` ist nicht Zierde: Ein <span> ist inline, und
dort gelten Hoehe, Breite und margin:auto nicht. Der Strich war
gebaut und trotzdem nie im Bild. */
'#tk-karte .strich{display:block;height:2px;width:0;margin:22px auto 0;border-radius:2px;',
' background:linear-gradient(90deg,' + VIOLETT + ',' + AKZENT + ');',
' transition:width .7s cubic-bezier(.16,.8,.3,1) .42s;opacity:0}',
'#tk-karte.ende .strich{opacity:1}',
'#tk-karte.ende.an .strich{width:132px}',
/* ---- Der Untertitel -------------------------------------------
Rechts bleibt Platz: Dort liegt auf TikTok die Knopfreihe
(Herz, Kommentar, Teilen). Ein Untertitel, der bis an den
rechten Rand geht, liegt im fertigen Beitrag halb darunter.
Unten bleibt Platz fuer Beschreibung und Ton. */
'#tk-satz{position:absolute;left:18px;right:62px;bottom:176px;',
' opacity:0;transform:translateY(12px);',
' transition:opacity .26s ease,transform .26s cubic-bezier(.16,.8,.3,1)}',
'#tk-satz.an{opacity:1;transform:none}',
'#tk-satz span{display:flex;align-items:stretch;gap:11px;',
' background:rgba(10,8,22,.80);backdrop-filter:blur(14px) saturate(1.25);',
' -webkit-backdrop-filter:blur(14px) saturate(1.25);',
' border:1px solid rgba(169,139,255,.26);border-radius:15px;padding:11px 14px;',
' box-shadow:0 20px 44px -20px #000,inset 0 1px 0 rgba(255,255,255,.07)}',
'#tk-satz i{flex:none;width:3px;border-radius:3px;',
' background:linear-gradient(180deg,' + VIOLETT + ',' + AKZENT + ')}',
'#tk-satz em{font-style:normal;font-weight:700;font-size:19px;line-height:1.32;',
' letter-spacing:-.006em;color:#ece8fa}',
/* ---- Der Vorhang ----------------------------------------------
Deckt den Seitenwechsel zu. Ohne ihn stehen „Eintraege werden
geladen ...", leere Geruste und ein kurzes Aufblitzen im
fertigen Video -- das sieht nach Bildschirmaufnahme aus und
nicht nach Film. */
'#tk-blende{position:absolute;inset:0;transform:translateY(102%);',
' background:linear-gradient(180deg,' + VIOLETT + ' 0%,#4b3fa8 34%,#0b0918 78%,' + TINTE + ' 100%)}',
'#tk-blende.zu{transform:translateY(0);',
' transition:transform .34s cubic-bezier(.66,0,.34,1)}',
'#tk-blende.auf{transform:translateY(-102%);',
' transition:transform .40s cubic-bezier(.66,0,.34,1)}',
/* ---- Der Finger ------------------------------------------------ */
'#tk-finger{position:absolute;width:34px;height:34px;margin:-17px 0 0 -17px;',
' border-radius:50%;background:rgba(142,201,255,.20);',
' border:2px solid rgba(236,232,250,.78);opacity:0;',
' box-shadow:0 0 22px rgba(142,201,255,.35);',
' transition:left .45s cubic-bezier(.4,0,.2,1),top .45s cubic-bezier(.4,0,.2,1),',
' opacity .2s ease,transform .18s ease}',
'#tk-finger.an{opacity:1}',
'#tk-finger.tipp{transform:scale(.68);background:rgba(169,139,255,.55)}',
].join("");
const st = document.createElement("style");
st.textContent = css;
document.head.appendChild(st);
const b = document.createElement("div");
b.id = "tk-buehne";
b.innerHTML = '<div id="tk-karte"><p></p></div>'
+ '<div id="tk-satz"><span></span></div><div id="tk-finger"></div>';
b.innerHTML =
'<div id="tk-vig"></div>'
+ '<div id="tk-karte"><div class="innen">'
+ '<span class="kicker"><i></i>Team Dogi<i></i></span>'
+ '<p></p><span class="strich"></span>'
+ '</div></div>'
+ '<div id="tk-satz"><span><i></i><em></em></span></div>'
+ '<div id="tk-finger"></div>'
+ '<div id="tk-blende" class="' + (blendeZu ? "zu" : "") + '"></div>';
document.body.appendChild(b);
})();`;
}
const pw = await import(
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
const warte = (ms) => new Promise((r) => setTimeout(r, ms));
/** Wartet, bis die Seite 650 ms am Stueck nicht mehr „laedt".
*
* Gibt zurueck, ob das gelungen ist -- der Aufrufer soll wissen, ob
* er gerade etwas filmt, das noch nicht fertig ist. */
async function ruhig(seite, frist = 9000) {
const bis = Date.now() + frist;
let seit = 0;
while (Date.now() < bis) {
const laedt = await seite.evaluate(
() => /wird geladen|werden geladen|einen moment/i.test(document.body?.innerText || ""),
).catch(() => false);
if (laedt) seit = 0;
else if (!seit) seit = Date.now();
if (seit && Date.now() - seit >= 650) return true;
await warte(110);
}
return false;
}
/* =======================================================================
DIE LECKSUCHE — kein Link darf ins Bild
@@ -608,17 +767,66 @@ ABBRUCH -- auf ${woher} steht etwas im Bild, was nicht ins Video darf:`);
}
function buehneFuer(seite) {
const auf = () => seite.evaluate(BUEHNE);
/* Ob der Vorhang zu ist, muss die Buehne beim Wiederaufbau wissen.
Ein Seitenwechsel wirft das ganze Dokument weg -- und wenn die
Buehne danach OHNE Vorhang zurueckkommt, sieht man genau das,
was sie zudecken sollte. Der Zustand lebt deshalb hier und nicht
im Browser. */
let vorhangZu = false;
const auf = () => seite.evaluate(BUEHNE, vorhangZu);
/* Der Wiederaufbau haengt hier und nicht beim Aufrufer: Er braucht
`vorhangZu`, und ein Zustand, den zwei Stellen fuehren, laeuft
auseinander. */
seite.on("load", () => { seite.evaluate(BUEHNE, vorhangZu).catch(() => {}); });
return {
auf,
/** Eine Vollbild-Karte. `<b>` hebt ein Wort hervor. */
async karte(html, ms = 2000, { bleibt = false } = {}) {
/** Vorhang zu. Danach darf navigiert werden. */
async zu(ms = 380) {
await auf();
await seite.evaluate((t) => {
await seite.evaluate(() => {
const v = document.getElementById("tk-blende");
v.className = "";
void v.offsetWidth; /* Neuzeichnen erzwingen */
v.className = "zu";
});
vorhangZu = true;
await warte(ms);
},
/** Vorhang auf. */
async aufziehen(ms = 440) {
await auf();
await seite.evaluate(() => {
const v = document.getElementById("tk-blende");
v.className = "auf";
});
vorhangZu = false;
await warte(ms);
},
/** Eine Vollbild-Karte. `<b>` hebt ein Wort hervor.
*
* `ende: true` ist fuer den Abspann: andere Flaeche, groessere
* Schrift, ein Strich darunter. Er ist das Einzige, was der
* Mensch danach noch tun soll -- und soll sich deshalb von den
* Zwischenkarten unterscheiden. */
async karte(html, ms = 2000, { bleibt = false, ende = false } = {}) {
await auf();
await seite.evaluate(([t, istEnde]) => {
const k = document.getElementById("tk-karte");
k.querySelector("p").innerHTML = t;
k.classList.toggle("ende", !!istEnde);
/* Erst die Klasse weg, dann ein Neuzeichnen erzwingen, dann
wieder hin: Sonst laeuft die Bewegung bei der zweiten Karte
nicht noch einmal, weil sich am Zustand nichts geaendert
hat. Genau so sah die erste Karte lebendig aus und jede
weitere wie ein Standbild. */
k.classList.remove("an");
void k.offsetWidth;
k.classList.add("an");
}, html);
}, [html, ende]);
await warte(ms);
/* `bleibt` ist fuer den Schluss. Ohne das nimmt die Karte sich
nach ihrer Zeit selbst weg -- und die letzte knappe Sekunde
@@ -633,7 +841,7 @@ function buehneFuer(seite) {
await auf();
await seite.evaluate((t) => {
const s = document.getElementById("tk-satz");
s.querySelector("span").textContent = t;
s.querySelector("em").textContent = t;
s.classList.add("an");
}, text);
await warte(ms);
@@ -726,8 +934,8 @@ const FILME = {
datei: "1-zuhause",
titel: "Das Zuhause",
async lauf(seite, b) {
await b.karte("Ein Kommentar ist nach<br>drei Sekunden weg.", 2100);
await b.karte("Also hab ich dem Rudel<br>ein <b>Zuhause</b> gebaut.", 2100);
await b.karte("Ein Kommentar ist nach drei Sekunden weg.", 2100);
await b.karte("Also hab ich dem Rudel ein <b>Zuhause</b> gebaut.", 2100);
await seite.goto(`${BASIS}/workspace/start.html`, { waitUntil: "networkidle" });
await warte(800);
await b.satz("Vierzehn Türen. Kein Discord.", 1900);
@@ -738,8 +946,8 @@ const FILME = {
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.<br>Das ist <b>unsers</b>.", 2200);
await b.karte(CTA, 2600, { bleibt: true });
await b.karte("Das ist nicht mein Kanal. Das ist <b>unsers</b>.", 2200);
await b.karte(CTA, 2800, { bleibt: true, ende: true });
},
},
@@ -748,8 +956,8 @@ const FILME = {
titel: "Der Chat, der schlaeft",
nachtruhe: true,
async lauf(seite, b) {
await b.karte("Jede App will, dass du<br>um drei noch wach bist.", 2200);
await b.karte("Meine <b>schickt euch<br>ins Bett</b>.", 2000);
await b.karte("Jede App will, dass du um drei noch wach bist.", 2200);
await b.karte("Meine <b>schickt euch ins Bett</b>.", 2000);
await seite.goto(`${BASIS}/workspace/chat.html?raum=treff`, { waitUntil: "networkidle" });
await warte(1600);
await b.rollenIn(".chat__verlauf", -260, 1200);
@@ -758,8 +966,8 @@ const FILME = {
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<br>ein <b>Leben</b> habt.", 2400);
await b.karte(CTA, 2600, { bleibt: true });
await b.karte("Weil ihr morgen auch noch ein <b>Leben</b> habt.", 2400);
await b.karte(CTA, 2800, { bleibt: true, ende: true });
},
},
@@ -767,16 +975,16 @@ const FILME = {
datei: "3-jedes-nur-einmal",
titel: "Jedes nur einmal",
async lauf(seite, b) {
await b.karte("Ich hab Clips und Bilder<br>für euch zum Posten.", 2100);
await b.karte("Aber jeder kriegt<br><b>nur eins</b>.", 1900);
await b.karte("Ich hab Clips und Bilder für euch zum Posten.", 2100);
await b.karte("Aber jeder kriegt <b>nur eins</b>.", 1900);
await seite.goto(`${BASIS}/workspace/material.html`, { waitUntil: "networkidle" });
await warte(1100);
await b.satz("Dogi-Media. Fertig zum Hochladen.", 2000);
await b.rollen(500, 2200);
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.<br>Damit <b>alle</b> was haben.", 2400);
await b.karte(CTA, 2600, { bleibt: true });
await b.karte("Damit nicht einer alles nimmt. Damit <b>alle</b> was haben.", 2400);
await b.karte(CTA, 2800, { bleibt: true, ende: true });
},
},
@@ -785,7 +993,7 @@ const FILME = {
titel: "Dazugehoeren",
async lauf(seite, b) {
await b.karte("Du wünschst dir was im Live.", 2000);
await b.karte("Und bis morgen<br>hab ich es vergessen.", 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);
@@ -798,8 +1006,8 @@ const FILME = {
await b.zu(".eintrag-karte", 1500);
await b.satz("Wer mehr will als zuschauen:", 2000);
await b.rollen(300, 1400);
await b.karte("Du musst nichts können.<br>Du musst <b>da sein wollen</b>.", 2400);
await b.karte(CTA, 2600, { bleibt: true });
await b.karte("Du musst nichts können. Du musst <b>da sein wollen</b>.", 2400);
await b.karte(CTA, 2800, { bleibt: true, ende: true });
},
},
};
@@ -853,20 +1061,54 @@ for (const nr of liste) {
return { name: n, value: r.join("="), domain: CREW, path: "/" };
}));
const seite = await kontext.newPage();
await seite.setContent('<body style="margin:0;background:#05070d"></body>');
await seite.setContent('<body style="margin:0;background:#07060f"></body>');
const b = buehneFuer(seite);
await b.auf();
/* Jeder Seitenwechsel wird abgesucht, bevor die Kamera ihn zeigt. */
/* ==== JEDER SEITENWECHSEL LAEUFT HINTER DEM VORHANG ===============
(27.09.2026, nachdem Filipe die Videos gesehen hat.)
Vorher war jeder Wechsel im Bild: „Eintraege werden geladen ...",
leere Geruste, ein halb aufgebautes Raster, manchmal ein kurzes
Aufblitzen. Zusammen sah das nach Bildschirmaufnahme aus und
nicht nach Film -- und genau das war der Unterschied zwischen
„geht schon" und „sieht gut aus".
Der Ablauf ist immer derselbe und steht deshalb HIER und nicht in
den vier Drehbuechern: zu, laden, warten bis die Seite wirklich
steht, auf. Wer einen Schritt vergisst, sieht es erst im
fertigen Video.
ABGESUCHT WIRD, WAEHREND DER VORHANG NOCH ZU IST: Faende die
Lecksuche etwas, waere es nie im Bild gewesen. */
const gotoEcht = seite.goto.bind(seite);
seite.goto = async (...a) => {
await b.zu();
const r = await gotoEcht(...a);
await b.auf();
await lecksuche(seite, String(a[0]).replace(/^https?:\/\/[^/]+/, ""));
/* GEWARTET WIRD AUF RUHE, NICHT AUF DIE UHR UND NICHT AUF EINE
EINZELNE ANTWORT.
Erst standen hier 700 ms -- eine Zahl. Dann eine einmalige
Frage „steht irgendwo, dass geladen wird?" -- und die traf den
Moment, BEVOR die Seite ihren Ladehinweis gezeichnet hatte.
Im Video stand danach trotzdem „Eintraege werden geladen ...".
`ruhig()` verlangt 650 ms am Stueck ohne Hinweis. Eine Seite
laedt ihre Abschnitte nacheinander, und zwischen zweien ist
es kurz still -- 450 ms reichten nicht, der Vorhang ging in
dieser Stille auf. Kommt der Hinweis
doch noch, faengt die Uhr von vorn an. */
const still = await ruhig(seite);
if (!still) {
console.warn(` Hinweis: ${String(a[0]).replace(/^https?:\/\/[^/]+/, "")} `
+ "laedt laenger als acht Sekunden -- der Vorhang geht trotzdem auf.");
}
await warte(300);
await b.aufziehen();
return r;
};
/* Die Bühne muss nach JEDEM Seitenwechsel neu hinein -- sie lebt im
Dokument, und das wird beim Laden ausgetauscht. */
seite.on("load", () => { seite.evaluate(BUEHNE).catch(() => {}); });
await film.lauf(seite, b);
await warte(400);