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" }, { kuerzel: "DB", name: "David Bertram", rolle: "Produkt & Design" },
]; ];
/* Die vier Teile, Wort fuer Wort von „Was heute kommt“. Die Zeichen sind /* Die vier Teile, Wort fuer Wort von „Was heute kommt“. akzent ist das
die Phosphor-Gegenstuecke zu den Lucide-Zeichen auf derselben Slide: 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 Kanban fuer die Pipelines, ein Ablauf fuer die Automation, zwei Blasen
fuer den Chat, das Geschenk fuer die Ueberraschung. */ fuer den Chat, das Geschenk fuer die Ueberraschung. */
const WB_TEILE = [ const WB_TEILE = [
{ rubrik: "Teil 1", zeichen: "kanban", zeilen: ["Von der Anfrage", "zum Auftrag"] }, { rubrik: "Teil 1", zeichen: "kanban", akzent: "Auftrag", zeilen: ["Von der Anfrage", "zum Auftrag"] },
{ rubrik: "Teil 2", zeichen: "tree-structure", zeilen: ["Das Herzstück:", "Automatisierung"] }, { rubrik: "Teil 2", zeichen: "tree-structure", akzent: "Automatisierung", zeilen: ["Das Herzstück:", "Automatisierung"] },
{ rubrik: "Teil 3", zeichen: "chats-circle", zeilen: ["Alles drumherum: Chat,", "Personal, Passwörter"] }, { rubrik: "Teil 3", zeichen: "chats-circle", akzent: "drumherum:", zeilen: ["Alles drumherum: Chat,", "Personal, Passwörter"] },
{ rubrik: "Zum Schluss", zeichen: "gift", zeilen: ["Eine Überraschung"] }, { rubrik: "Zum Schluss", zeichen: "gift", akzent: "Überraschung", zeilen: ["Eine Überraschung"] },
]; ];
/* Rot nur fuer die Live-Marke. Aus ui/theme.css von hivegrid-cloud: /* Markengelb fuer die Wabe in der Live-Marke, wie --accent-yellow. */
red-100, red-200, red-700 nach dem Muster der Marken in Post 8, der const WB_GELB = "#F1B729";
Punkt ist red-500. */ /* Die Farben der Kapitel-Slides im Webinar (quelle/slides/teil1.html):
const WB_ROT = { flaeche: "#FEE2E2", rand: "#FDCBCB", zeichen: "#C52020", punkt: "#ED4646" }; dunkler Grund, Titel fast weiss, die Rubrik grau. */
/* Das Sechseck der Webinar-Slides: Gelb #FCE48A, Tinte als Rand. */ const WB_DUNKEL = "#24242B", WB_HELL = "#FAFAFA", WB_GRAU = "#9D9DA0";
const WB_WABE = "#FCE48A";
const WB_B = 720, WB_RAND = 34, WB_KOPF = 92; const WB_B = 720, WB_RAND = 34, WB_KOPF = 92;
let wbNr = 0; let wbNr = 0;
/* Die Live-Marke: Punkt, Wort, und um den Punkt ein Ring, der einmal pro /* Der Rahmen: Zeichen, Titel, Trennlinie. Wie emRahmen in Post 8, nur
Sekunde aufgeht. puls laeuft von 0 bis 1; ohne puls steht der Punkt ohne Reiter. Eine eigene Live-Marke gibt es nicht mehr: Der Titel sagt
still. */ schon „Live-Webinar“ (Dejan, 20.10.). */
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. */
function wbRahmen(h, inhalt, o) { function wbRahmen(h, inhalt, o) {
const B = WB_B + 2 * WB_RAND; const B = WB_B + 2 * WB_RAND;
let g = ikon(o.zeichen, WB_RAND + 14, 33, 20, CHAT_TEXT); 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 += 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, /* Rechts der Termin. Ohne ihn sagt das erste Element nur, worum es geht,
und nicht, wann. */ und nicht, wann. */
if (o.rechts) { if (o.rechts) {
@@ -4610,28 +4592,105 @@ if (location.search.indexOf("nur=anim") >= 0)
return stapelKasten(B, h, 22, g); return stapelKasten(B, h, 22, g);
} }
/* Das Sechseck mit Zeichen, wie auf „Was heute kommt“. */ /* Das Wabenmuster der Kapitel-Slides (bilder/wabe-ecke.png im Webinar):
function wbWabe(x, y, s, zeichen) { gelbe G-Waben, oben rechts am kraeftigsten, nach links unten auslaufend.
const k = s / 56; Hier als Vektoren nachgebaut, damit jedes Videobild gleich scharf ist
const p = [[3, 28], [15.5, 6.4], [40.5, 6.4], [53, 28], [40.5, 49.6], [15.5, 49.6]] und kein Bild nachladen muss. Die Waben stehen einmal fest; das Auslaufen
.map(q => n1(x + q[0] * k) + "," + n1(y + q[1] * k)).join(" "); macht eine Maske mit Verlauf, die maske() je Bild neu benennt. */
return '<polygon points="' + p + '" fill="' + WB_WABE + '" stroke="' + CHAT_TEXT + const WB_MUSTER = (() => {
'" stroke-width="1.6" stroke-linejoin="round"/>' + 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";
ikon(zeichen, x + s / 2, y + s / 2, s * .42, CHAT_TEXT); 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, /* Das Zeichen des Teils in einer grossen gelben Wabe oben rechts. Sie
der Titel unten links: dieselbe Diagonale wie auf den Kapitel-Slides sitzt im Muster wie eine hervorgehobene Zelle: Ein Rand in der Farbe
des Webinars. */ 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; const WB_BUEHNE_H = 430;
function wbFolie(x0, teil) { function wbFolie(x0, teil, maske, hell) {
let g = wbWabe(x0 + WB_B - 44 - 124, 36, 124, teil.zeichen); let g = '<g transform="translate(' + n1(x0) + ',0)">';
g += uiText(x0 + 48, 78, teil.rubrik.toUpperCase(), 17, 800, CHAT_TEXT3, { sperr: "0.13em" }); 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; const zh = 56, unten = WB_BUEHNE_H - 94;
teil.zeilen.forEach((z, i) => { 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 /* 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. sich je Teil ein Abschnitt der Leiste.
Unter der Buehne die beiden Sprecher. Wer redet, hat den dunklen Rand. Unter der Buehne die beiden Sprecher. Wer redet, hat den dunklen Rand.
Die Reihenfolge ist die aus dem Skript: Jedes Kapitel eroeffnet Noah, Noah die erste Haelfte, David die zweite.
dann uebernimmt David.
Alles, was sich wiederholt, laeuft in ganzen Perioden ueber die acht Alles, was sich wiederholt, laeuft in ganzen Perioden ueber die acht
Sekunden: der Puls der Live-Marke achtmal, die Balken drei-, vier- und Sekunden, die Balken drei-, vier- und fuenfmal je Teil. Ein Video, das
fuenfmal je Teil. Ein Video, das sich auf Instagram sich auf Instagram wiederholt, springt dann nur bei der Folie. */
wiederholt, springt dann nur bei der Folie und nicht beim Puls. */
function animWebinar(j, t) { function animWebinar(j, t) {
const n = WB_TEILE.length, abschnitt = 1 / n, schub = .045; const n = WB_TEILE.length, abschnitt = 1 / n, schub = .045;
/* Welche Folie steht, und wie weit der Schub zur naechsten ist. */ /* Welche Folie steht, und wie weit der Schub zur naechsten ist. */
let g = ""; 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) + g += '<clipPath id="' + id + '"><path d="' + rundRechteck(0, 0, WB_B, WB_BUEHNE_H, 14) +
'"/></clipPath>'; '"/></clipPath>' + wbMaske(maske);
g += '<path d="' + rundRechteck(0, 0, WB_B, WB_BUEHNE_H, 14) + '" fill="' + FL_GRUND + g += '<path d="' + rundRechteck(0, 0, WB_B, WB_BUEHNE_H, 14) + '" fill="' + WB_DUNKEL + '"/>';
'" stroke="' + CHAT_RAND + '" stroke-width="1.2"/>';
let folien = ""; let folien = "";
for (let i = 0; i < n; i++) { 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 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; const x0 = (1 - rein) * WB_B - raus_ * WB_B;
if (x0 >= WB_B || x0 <= -WB_B) continue; 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>'; g += '<g clip-path="url(#' + id + ')">' + folien + '</g>';
/* Die Leiste: vier Abschnitte, jeder fuellt sich, solange sein Teil /* Die Leiste: vier Abschnitte, jeder fuellt sich, solange sein Teil
steht. */ steht. */
const LX = 48, LB = WB_B - 96, LL = 8, sb = (LB - (n - 1) * LL) / n, ly = WB_BUEHNE_H - 40; 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++) { for (let i = 0; i < n; i++) {
const sx = LX + i * (sb + LL); const sx = LX + i * (sb + LL);
g += '<rect x="' + n1(sx) + '" y="' + ly + '" width="' + n1(sb) + 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); 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)) + 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 /* Die Sprecher. Noah redet bei Teil 1 und 2, David bei Teil 3 und 4.
ueber der Haelfte David. Der Wechsel blendet ueber, damit nicht beide 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. */ Raender gleichzeitig hart umspringen. */
const imTeil = (t % abschnitt) / abschnitt; const david = daempfen(stufe(t, .5 - schub, .5 + schub));
const david = stufe(imTeil, .52, .62);
const noah = 1 - david; const noah = 1 - david;
const sy = WB_BUEHNE_H + 20, kb = (WB_B - 16) / 2; const sy = WB_BUEHNE_H + 20, kb = (WB_B - 16) / 2;
const phase = t * n; 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); g += wbSprecher(kb + 16, sy, kb, WB_SPRECHER[1], david, phase);
const h = WB_KOPF + WB_BUEHNE_H + 20 + 84 + WB_RAND; 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" }); rechts: WEBINAR.datum + ", " + WEBINAR.uhrzeit + " Uhr" });
} }