Post 9: Folien im Look der Webinar-Slides, ruhigerer Sprecherwechsel

Teil 1 dunkel mit G-Waben wie die Kapitel-Slides, die drei danach hell mit
dem zweiten Wabenmuster. Die Zeichen sitzen als grosse gelbe Wabe im Muster,
je ein Wort im Titel in Gelb. Die Live-Marke entfaellt, der Titel sagt es
schon. Noah spricht bei Teil 1 und 2, David bei 3 und 4. Video neu exportiert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
dejanandClaude Opus 5.5 committed 2026-10-20 17:04:53 +02:00
1 parent 78a6fcba78
commit cd74224880
2 files changed
+128 -66

No files matched your search

Binary file not shown.
+128 -66
View File
@@ -4552,52 +4552,34 @@ if (location.search.indexOf("nur=anim") >= 0)
{ kuerzel: "DB", name: "David Bertram", rolle: "Produkt & Design" },
];
/* Die vier Teile, Wort fuer Wort von „Was heute kommt“. Die Zeichen sind
die Phosphor-Gegenstuecke zu den Lucide-Zeichen auf derselben Slide:
/* Die vier Teile, Wort fuer Wort von „Was heute kommt“. akzent ist das
Wort in Gelb, wie auf den Kapitel-Slides des Webinars. Die Zeichen sind
die Phosphor-Gegenstuecke zu den Lucide-Zeichen auf „Was heute kommt“:
Kanban fuer die Pipelines, ein Ablauf fuer die Automation, zwei Blasen
fuer den Chat, das Geschenk fuer die Ueberraschung. */
const WB_TEILE = [
{ rubrik: "Teil 1", zeichen: "kanban", zeilen: ["Von der Anfrage", "zum Auftrag"] },
{ rubrik: "Teil 2", zeichen: "tree-structure", zeilen: ["Das Herzstück:", "Automatisierung"] },
{ rubrik: "Teil 3", zeichen: "chats-circle", zeilen: ["Alles drumherum: Chat,", "Personal, Passwörter"] },
{ rubrik: "Zum Schluss", zeichen: "gift", zeilen: ["Eine Überraschung"] },
{ rubrik: "Teil 1", zeichen: "kanban", akzent: "Auftrag", zeilen: ["Von der Anfrage", "zum Auftrag"] },
{ rubrik: "Teil 2", zeichen: "tree-structure", akzent: "Automatisierung", zeilen: ["Das Herzstück:", "Automatisierung"] },
{ rubrik: "Teil 3", zeichen: "chats-circle", akzent: "drumherum:", zeilen: ["Alles drumherum: Chat,", "Personal, Passwörter"] },
{ rubrik: "Zum Schluss", zeichen: "gift", akzent: "Überraschung", zeilen: ["Eine Überraschung"] },
];
/* Rot nur fuer die Live-Marke. Aus ui/theme.css von hivegrid-cloud:
red-100, red-200, red-700 nach dem Muster der Marken in Post 8, der
Punkt ist red-500. */
const WB_ROT = { flaeche: "#FEE2E2", rand: "#FDCBCB", zeichen: "#C52020", punkt: "#ED4646" };
/* Das Sechseck der Webinar-Slides: Gelb #FCE48A, Tinte als Rand. */
const WB_WABE = "#FCE48A";
/* Markengelb fuer die Wabe in der Live-Marke, wie --accent-yellow. */
const WB_GELB = "#F1B729";
/* Die Farben der Kapitel-Slides im Webinar (quelle/slides/teil1.html):
dunkler Grund, Titel fast weiss, die Rubrik grau. */
const WB_DUNKEL = "#24242B", WB_HELL = "#FAFAFA", WB_GRAU = "#9D9DA0";
const WB_B = 720, WB_RAND = 34, WB_KOPF = 92;
let wbNr = 0;
/* Die Live-Marke: Punkt, Wort, und um den Punkt ein Ring, der einmal pro
Sekunde aufgeht. puls laeuft von 0 bis 1; ohne puls steht der Punkt
still. */
function wbLive(x, ym, puls) {
const gr = 14, h = gr + 13, b = messen("Live", gr, 700) + gr * 1.9 + 14;
let g = '<rect x="' + n1(x) + '" y="' + n1(ym - h / 2) + '" width="' + n1(b) +
'" height="' + h + '" rx="' + n1(h / 2) + '" fill="' + WB_ROT.flaeche +
'" stroke="' + WB_ROT.rand + '" stroke-width="1.2"/>';
const px = x + 15;
if (puls !== undefined && puls > 0 && puls < 1) {
g += '<circle cx="' + n1(px) + '" cy="' + n1(ym) + '" r="' + n1(4 + 6 * raus(puls)) +
'" fill="' + WB_ROT.punkt + '" opacity="' + n1(.35 * (1 - puls)) + '"/>';
}
g += '<circle cx="' + n1(px) + '" cy="' + n1(ym) + '" r="4" fill="' + WB_ROT.punkt + '"/>';
g += uiText(px + 11, ym + gr * .36, "Live", gr, 700, WB_ROT.zeichen);
return g;
}
/* Der Rahmen: Zeichen, Titel, Live-Marke, Trennlinie. Wie emRahmen in
Post 8, nur ohne Reiter. */
/* Der Rahmen: Zeichen, Titel, Trennlinie. Wie emRahmen in Post 8, nur
ohne Reiter. Eine eigene Live-Marke gibt es nicht mehr: Der Titel sagt
schon „Live-Webinar“ (Dejan, 20.10.). */
function wbRahmen(h, inhalt, o) {
const B = WB_B + 2 * WB_RAND;
let g = ikon(o.zeichen, WB_RAND + 14, 33, 20, CHAT_TEXT);
g += uiText(WB_RAND + 32, 40, o.titel, 20, 800, CHAT_TEXT);
g += wbLive(WB_RAND + 46 + messen(o.titel, 20, 800), 33, o.puls);
/* Rechts der Termin. Ohne ihn sagt das erste Element nur, worum es geht,
und nicht, wann. */
if (o.rechts) {
@@ -4610,28 +4592,105 @@ if (location.search.indexOf("nur=anim") >= 0)
return stapelKasten(B, h, 22, g);
}
/* Das Sechseck mit Zeichen, wie auf „Was heute kommt“. */
function wbWabe(x, y, s, zeichen) {
const k = s / 56;
const p = [[3, 28], [15.5, 6.4], [40.5, 6.4], [53, 28], [40.5, 49.6], [15.5, 49.6]]
.map(q => n1(x + q[0] * k) + "," + n1(y + q[1] * k)).join(" ");
return '<polygon points="' + p + '" fill="' + WB_WABE + '" stroke="' + CHAT_TEXT +
'" stroke-width="1.6" stroke-linejoin="round"/>' +
ikon(zeichen, x + s / 2, y + s / 2, s * .42, CHAT_TEXT);
/* Das Wabenmuster der Kapitel-Slides (bilder/wabe-ecke.png im Webinar):
gelbe G-Waben, oben rechts am kraeftigsten, nach links unten auslaufend.
Hier als Vektoren nachgebaut, damit jedes Videobild gleich scharf ist
und kein Bild nachladen muss. Die Waben stehen einmal fest; das Auslaufen
macht eine Maske mit Verlauf, die maske() je Bild neu benennt. */
const WB_MUSTER = (() => {
const G = "384.5,166.5 336.4,83.2 192.2,166.5 288.3,166.5 240.3,249.7 144.2,249.7 96.1,166.5 144.2,83.2 336.4,83.2 298,16.8 288.3,0 96.1,0 0,166.5 96.1,332.9 288.3,332.9";
const B = 50, S = B / 384.5, H = 332.9 * S, L = 7;
const DX = 0.75 * B + L, DY = H + L * 1.1;
let g = "";
for (let sp = 0; sp * DX < 720 + B; sp++) {
for (let z = -1; z * DY < 430; z++) {
const x = 720 - B - sp * DX, y = z * DY + (sp % 2 ? DY / 2 : 0);
if (x < 200 - B || y > 320) continue;
g += '<polygon points="' + G + '" transform="translate(' + n1(x) + ' ' + n1(y) + ') scale(' + S.toFixed(5) + ')"/>';
}
}
return g;
})();
/* Das zweite Muster der Webinar-Slides (waben.py, Slides „noah“ und
„problem“): weisse Waben mit grauem Rand auf hellem Grund, dazwischen
einzelne gelbe. Fuer die Folien nach Teil 1, damit die Titelfolie
dunkel bleibt und die anderen sich davon abheben. Spitze Waben wie
dort, die gelben stehen ausserhalb der Maske und bleiben voll. */
const WB_WABEN_HELL = (() => {
const R = 26, DX = Math.sqrt(3) * R, DY = 1.5 * R;
const ecken = (x, y) => [-90, -30, 30, 90, 150, 210]
.map(w => n1(x + R * Math.cos(w * Math.PI / 180)) + "," + n1(y + R * Math.sin(w * Math.PI / 180))).join(" ");
const zellen = [];
for (let j = 0; j * DY < 330; j++) {
for (let i = 0; i * DX < 720; i++) {
const x = 720 - i * DX - (j % 2 ? DX / 2 : 0), y = j * DY;
if (x > 230) zellen.push([x, y]);
}
}
const naechste = (zx, zy) => zellen.reduce((a, z) => Math.hypot(z[0] - zx, z[1] - zy) < Math.hypot(a[0] - zx, a[1] - zy) ? z : a);
const gelb = [naechste(470, 40), naechste(400, 140), naechste(690, 315)];
return {
waben: zellen.filter(z => !gelb.includes(z)).map(z => '<polygon points="' + ecken(z[0], z[1]) + '"/>').join(""),
gelb: gelb.map(z => '<polygon points="' + ecken(z[0], z[1]) + '"/>').join(""),
};
})();
function wbMaske(id) {
const verlauf = (vid, staerke) => '<linearGradient id="' + vid + '" gradientUnits="userSpaceOnUse" x1="720" y1="0" x2="400" y2="300">' +
'<stop offset="0" stop-color="#FFFFFF" stop-opacity="' + staerke + '"/><stop offset="1" stop-color="#FFFFFF" stop-opacity="0"/>' +
'</linearGradient>';
const maske = (mid, vid) => '<mask id="' + mid + '" maskUnits="userSpaceOnUse" x="0" y="0" width="720" height="' + WB_BUEHNE_H + '">' +
'<rect width="720" height="' + WB_BUEHNE_H + '" fill="url(#' + vid + ')"/></mask>';
return verlauf(id + "v", ".45") + maske(id, id + "v") + verlauf(id + "hv", "1") + maske(id + "h", id + "hv");
}
/* Eine Folie auf der Buehne. Sechseck oben rechts, Rubrik oben links,
der Titel unten links: dieselbe Diagonale wie auf den Kapitel-Slides
des Webinars. */
/* Das Zeichen des Teils in einer grossen gelben Wabe oben rechts. Sie
sitzt im Muster wie eine hervorgehobene Zelle: Ein Rand in der Farbe
des Grunds raeumt die Waben darunter frei. Auf der dunklen Folie flach
wie die G-Waben, auf den hellen spitz wie die Zellen dort. */
function wbIkonWabe(zeichen, hell) {
const cx = 620, cy = 118, R = 58, rand = 9;
const winkel = hell ? [-90, -30, 30, 90, 150, 210] : [0, 60, 120, 180, 240, 300];
const ecken = r => winkel.map(w => n1(cx + r * Math.cos(w * Math.PI / 180)) + "," +
n1(cy + r * Math.sin(w * Math.PI / 180))).join(" ");
return '<polygon points="' + ecken(R + rand) + '" fill="' + (hell ? FL_GRUND : WB_DUNKEL) + '"/>' +
'<polygon points="' + ecken(R) + '" fill="' + WB_GELB + '"/>' +
ikon(zeichen, cx, cy, R * .86, CHAT_TEXT);
}
/* Eine Titelzeile, das Akzentwort in Gelb. */
function wbZeile(x, y, text, akzent, gr, farbe) {
const i = akzent ? text.indexOf(akzent) : -1;
const stueck = (s, farbe) => s ? '<tspan fill="' + farbe + '">' + wfEsc(s) + '</tspan>' : "";
const inhalt = i < 0 ? wfEsc(text) : stueck(text.slice(0, i), farbe) + stueck(akzent, WB_GELB) +
stueck(text.slice(i + akzent.length), farbe);
return '<text x="' + n1(x) + '" y="' + n1(y) + '" font-family="Manrope, sans-serif" font-size="' + gr +
'" font-weight="800" fill="' + farbe + '">' + inhalt + '</text>';
}
/* Eine Folie auf der Buehne, im Look der Webinar-Slides: oben rechts das
Wabenmuster, oben links die Rubrik, unten links der Titel mit einem Wort
in Gelb. Teil 1 ist die Titelfolie, dunkel mit G-Waben wie die
Kapitel-Slides. Die drei danach sind hell mit dem zweiten Muster
(Dejan, 20.10.). */
const WB_BUEHNE_H = 430;
function wbFolie(x0, teil) {
let g = wbWabe(x0 + WB_B - 44 - 124, 36, 124, teil.zeichen);
g += uiText(x0 + 48, 78, teil.rubrik.toUpperCase(), 17, 800, CHAT_TEXT3, { sperr: "0.13em" });
function wbFolie(x0, teil, maske, hell) {
let g = '<g transform="translate(' + n1(x0) + ',0)">';
if (hell) {
g += '<rect width="' + WB_B + '" height="' + WB_BUEHNE_H + '" fill="' + FL_GRUND + '"/>';
g += '<g mask="url(#' + maske + 'h)" fill="#FFFFFF" stroke="#D6D6D9" stroke-width="1.5">' + WB_WABEN_HELL.waben + '</g>';
g += '<g fill="' + WB_GELB + '">' + WB_WABEN_HELL.gelb + '</g>';
} else {
g += '<rect width="' + WB_B + '" height="' + WB_BUEHNE_H + '" fill="' + WB_DUNKEL + '"/>';
g += '<g mask="url(#' + maske + ')" fill="' + WB_GELB + '">' + WB_MUSTER + '</g>';
}
g += wbIkonWabe(teil.zeichen, hell);
g += uiText(48, 78, teil.rubrik.toUpperCase(), 17, 800, hell ? CHAT_TEXT3 : WB_GRAU, { sperr: "0.13em" });
const zh = 56, unten = WB_BUEHNE_H - 94;
teil.zeilen.forEach((z, i) => {
g += uiText(x0 + 48, unten - (teil.zeilen.length - 1 - i) * zh, z, 48, 800, CHAT_TEXT);
g += wbZeile(48, unten - (teil.zeilen.length - 1 - i) * zh, z, teil.akzent, 48, hell ? CHAT_TEXT : WB_HELL);
});
return g;
return g + '</g>';
}
/* Eine Kachel fuer einen Sprecher: Foto, Name, Rolle. spricht blendet den
@@ -4668,22 +4727,19 @@ if (location.search.indexOf("nur=anim") >= 0)
sich je Teil ein Abschnitt der Leiste.
Unter der Buehne die beiden Sprecher. Wer redet, hat den dunklen Rand.
Die Reihenfolge ist die aus dem Skript: Jedes Kapitel eroeffnet Noah,
dann uebernimmt David.
Noah die erste Haelfte, David die zweite.
Alles, was sich wiederholt, laeuft in ganzen Perioden ueber die acht
Sekunden: der Puls der Live-Marke achtmal, die Balken drei-, vier- und
fuenfmal je Teil. Ein Video, das sich auf Instagram
wiederholt, springt dann nur bei der Folie und nicht beim Puls. */
Sekunden, die Balken drei-, vier- und fuenfmal je Teil. Ein Video, das
sich auf Instagram wiederholt, springt dann nur bei der Folie. */
function animWebinar(j, t) {
const n = WB_TEILE.length, abschnitt = 1 / n, schub = .045;
/* Welche Folie steht, und wie weit der Schub zur naechsten ist. */
let g = "";
const id = "wbbuehne" + (++wbNr);
const id = "wbbuehne" + (++wbNr), maske = id + "m";
g += '<clipPath id="' + id + '"><path d="' + rundRechteck(0, 0, WB_B, WB_BUEHNE_H, 14) +
'"/></clipPath>';
g += '<path d="' + rundRechteck(0, 0, WB_B, WB_BUEHNE_H, 14) + '" fill="' + FL_GRUND +
'" stroke="' + CHAT_RAND + '" stroke-width="1.2"/>';
'"/></clipPath>' + wbMaske(maske);
g += '<path d="' + rundRechteck(0, 0, WB_B, WB_BUEHNE_H, 14) + '" fill="' + WB_DUNKEL + '"/>';
let folien = "";
for (let i = 0; i < n; i++) {
@@ -4693,27 +4749,33 @@ if (location.search.indexOf("nur=anim") >= 0)
const raus_ = i === n - 1 ? 0 : daempfen(stufe(t, (i + 1) * abschnitt - schub, (i + 1) * abschnitt + schub));
const x0 = (1 - rein) * WB_B - raus_ * WB_B;
if (x0 >= WB_B || x0 <= -WB_B) continue;
folien += wbFolie(x0, WB_TEILE[i]);
folien += wbFolie(x0, WB_TEILE[i], maske, i > 0);
}
g += '<g clip-path="url(#' + id + ')">' + folien + '</g>';
/* Die Leiste: vier Abschnitte, jeder fuellt sich, solange sein Teil
steht. */
const LX = 48, LB = WB_B - 96, LL = 8, sb = (LB - (n - 1) * LL) / n, ly = WB_BUEHNE_H - 40;
/* Die Spur ist auf der dunklen Titelfolie weiss und durchscheinend, auf
den hellen grau. Sie wechselt mit dem Schub zu Teil 2. */
const hellAnteil = daempfen(stufe(t, abschnitt - schub, abschnitt + schub));
for (let i = 0; i < n; i++) {
const sx = LX + i * (sb + LL);
g += '<rect x="' + n1(sx) + '" y="' + ly + '" width="' + n1(sb) +
'" height="6" rx="3" fill="#DADADC"/>';
'" height="6" rx="3" fill="#FFFFFF" fill-opacity="' + (.16 * (1 - hellAnteil)).toFixed(3) + '"/>';
if (hellAnteil > 0) g += '<rect x="' + n1(sx) + '" y="' + ly + '" width="' + n1(sb) +
'" height="6" rx="3" fill="#DADADC" fill-opacity="' + hellAnteil.toFixed(3) + '"/>';
const p = stufe(t, i * abschnitt, (i + 1) * abschnitt);
if (p > 0) g += '<rect x="' + n1(sx) + '" y="' + ly + '" width="' + n1(Math.max(6, sb * p)) +
'" height="6" rx="3" fill="' + CHAT_TEXT + '"/>';
'" height="6" rx="3" fill="' + WB_GELB + '"/>';
}
/* Die Sprecher. Im Abschnitt eines Teils redet erst Noah, ab etwas
ueber der Haelfte David. Der Wechsel blendet ueber, damit nicht beide
/* Die Sprecher. Noah redet bei Teil 1 und 2, David bei Teil 3 und 4.
Gewechselt wird nur einmal, mit dem Schub zur dritten Folie, und am
Ende des Loops zurueck. Vorher wechselte es in jedem Teil, das war zu
unruhig (Dejan, 20.10.). Der Wechsel blendet ueber, damit nicht beide
Raender gleichzeitig hart umspringen. */
const imTeil = (t % abschnitt) / abschnitt;
const david = stufe(imTeil, .52, .62);
const david = daempfen(stufe(t, .5 - schub, .5 + schub));
const noah = 1 - david;
const sy = WB_BUEHNE_H + 20, kb = (WB_B - 16) / 2;
const phase = t * n;
@@ -4721,7 +4783,7 @@ if (location.search.indexOf("nur=anim") >= 0)
g += wbSprecher(kb + 16, sy, kb, WB_SPRECHER[1], david, phase);
const h = WB_KOPF + WB_BUEHNE_H + 20 + 84 + WB_RAND;
return wbRahmen(h, g, { zeichen: "video-camera", titel: "Live-Webinar", puls: (t * 8) % 1,
return wbRahmen(h, g, { zeichen: "video-camera", titel: "Live-Webinar",
rechts: WEBINAR.datum + ", " + WEBINAR.uhrzeit + " Uhr" });
}