Post 9: Format 1:1 für die Anzeige und vier neue Varianten

- Format-Schalter 4:5 / 1:1 in Post 9, Stand auf 1:1 (Meta nimmt ein
  Carousel mit Video als Anzeige nur quadratisch), Video in 1:1
- animation.mjs baut das Format der Seite, Datei mit -1x1
- Variante 2/3: Kundengeschichte nach dem weclapp-Karussell, hell und dunkel
- Variante 4/5: Zahlen-Karussell nach HERO Insights mit KfW- und
  Bitkom-Zahlen, Noah als Teaser, hell und dunkel
- post9-varianten.html zeigt alle Varianten untereinander
- Menüs und Übersicht um die Varianten ergänzt, Regel zu 1:1 in rules.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
dejanandClaude Opus 5.5 committed 2026-11-02 18:55:03 +01:00
1 parent cafefc9f1b
commit 5b768537a2
21 files changed
+34393 -49

No files matched your search

+23 -6
View File
@@ -3,6 +3,7 @@
* *
* node animation.mjs post6.html alle animierten Slides * node animation.mjs post6.html alle animierten Slides
* node animation.mjs post6.html 5 nur Slide 6 (nullbasiert: 5) * node animation.mjs post6.html 5 nur Slide 6 (nullbasiert: 5)
* node animation.mjs post9.html animWebinar 1x1 im Format 1:1
* *
* WARUM MP4 UND NICHT GIF * WARUM MP4 UND NICHT GIF
* Instagram nimmt kein GIF an. Was im Feed als "kurzer animierter Beitrag" * Instagram nimmt kein GIF an. Was im Feed als "kurzer animierter Beitrag"
@@ -14,7 +15,11 @@
* DIE WERTE * DIE WERTE
* Behaelter MP4, H.264 High, yuv420p * Behaelter MP4, H.264 High, yuv420p
* Bild 1080 x 1350, also 4:5 wie unsere Standbilder -- ein Carousel * Bild 1080 x 1350, also 4:5 wie unsere Standbilder -- ein Carousel
* schneidet alle Elemente auf das Format des ersten zu * schneidet alle Elemente auf das Format des ersten zu.
* Kennt die Seite ein zweites Format (window.SLIDE_FORMAT, bisher
* nur Post 9 mit 1:1 fuer die Anzeige), gilt dessen Hoehe. Das
* Format kommt aus dem Projektstand oder als drittes Argument,
* die Datei traegt dann ein Kennzeichen (-1x1).
* Bildrate 30 fps * Bildrate 30 fps
* Laenge 3 s ist das Minimum, 60 s je Element das Maximum; wir liegen * Laenge 3 s ist das Minimum, 60 s je Element das Maximum; wir liegen
* bei 5 * bei 5
@@ -40,12 +45,16 @@ const SEITE = process.argv[2] || "post6.html";
Slide mehr und hat deshalb auch keine Position im Streifen, die sich Slide mehr und hat deshalb auch keine Position im Streifen, die sich
verschieben koennte. Ohne Angabe werden alle des Projekts gebaut. */ verschieben koennte. Ohne Angabe werden alle des Projekts gebaut. */
const NUR = process.argv[3] || null; const NUR = process.argv[3] || null;
/* Format, wenn die Seite mehrere kennt. Ohne Angabe gilt der Projektstand. */
const FORMAT = process.argv[4] || null;
const ZUSATZ = FORMAT ? "&format=" + encodeURIComponent(FORMAT) : "";
const PROJEKT = SEITE.replace(/\.html$/, ""); const PROJEKT = SEITE.replace(/\.html$/, "");
const CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"; const CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
const PORT = 9530; const PORT = 9530;
const FPS = 30; const FPS = 30;
const BREITE = 1080, HOEHE = 1350; const BREITE = 1080;
let HOEHE = 1350, DATEI_ZUSATZ = "";
const DATENRATE = "12M"; const DATENRATE = "12M";
const ZIEL = path.join(process.cwd(), "animation"); const ZIEL = path.join(process.cwd(), "animation");
@@ -138,8 +147,16 @@ try {
// Erst die Liste holen. Ohne k rendert der Modus nichts, das Skript laeuft // Erst die Liste holen. Ohne k rendert der Modus nichts, das Skript laeuft
// aber vollstaendig durch und window.ANIM_LISTE steht bereit. // aber vollstaendig durch und window.ANIM_LISTE steht bereit.
await laden(SEITE + "?nur=anim"); await laden(SEITE + "?nur=anim" + ZUSATZ);
const alle = await cdp.werte("window.ANIM_LISTE ? window.ANIM_LISTE() : null"); const alle = await cdp.werte("window.ANIM_LISTE ? window.ANIM_LISTE() : null");
/* Das Format der Seite. Posts ohne zweites Format melden nichts und
bleiben bei 4:5. */
const fmt = await cdp.werte("window.SLIDE_FORMAT ? window.SLIDE_FORMAT() : null");
if (fmt) {
HOEHE = fmt.hoehe; DATEI_ZUSATZ = fmt.datei || "";
await cdp.ruf("Emulation.setDeviceMetricsOverride",
{ width: BREITE, height: HOEHE, deviceScaleFactor: 1, mobile: false });
}
if (!alle) throw new Error(SEITE + " kennt keine Animationen (window.ANIM_LISTE fehlt)"); if (!alle) throw new Error(SEITE + " kennt keine Animationen (window.ANIM_LISTE fehlt)");
const liste = alle.filter(a => NUR == null || a.key === NUR); const liste = alle.filter(a => NUR == null || a.key === NUR);
if (!liste.length) throw new Error("keine Animation" + if (!liste.length) throw new Error("keine Animation" +
@@ -150,7 +167,7 @@ try {
for (const a of liste) { for (const a of liste) {
const anzahl = Math.round(a.dauer * FPS); const anzahl = Math.round(a.dauer * FPS);
await laden(SEITE + "?nur=anim&k=" + encodeURIComponent(a.key)); await laden(SEITE + "?nur=anim&k=" + encodeURIComponent(a.key) + ZUSATZ);
/* Nachsehen, dass wirklich ein Blatt dasteht und es die richtigen Masse /* Nachsehen, dass wirklich ein Blatt dasteht und es die richtigen Masse
hat. Ein leeres Fenster faellt sonst erst am fertigen Video auf. */ hat. Ein leeres Fenster faellt sonst erst am fertigen Video auf. */
@@ -188,7 +205,7 @@ try {
"UMLAUT_ERLAUBT. Einen Geviertstrich ersetzt ein Punkt oder ein Komma."); "UMLAUT_ERLAUBT. Einen Geviertstrich ersetzt ein Punkt oder ein Komma.");
const ordner = await mkdtemp(path.join(tmpdir(), "postgrid-bilder-")); const ordner = await mkdtemp(path.join(tmpdir(), "postgrid-bilder-"));
process.stdout.write(` ${a.key}: ${anzahl} Bilder `); process.stdout.write(` ${a.key}: ${anzahl} Bilder, ${BREITE} x ${HOEHE} `);
for (let n = 0; n < anzahl; n++) { for (let n = 0; n < anzahl; n++) {
/* Das letzte Bild ist nicht t = 1, sondern eins davor: bei einer /* Das letzte Bild ist nicht t = 1, sondern eins davor: bei einer
Schleife waeren t = 0 und t = 1 dasselbe Bild und es haekelte kurz. */ Schleife waeren t = 0 und t = 1 dasselbe Bild und es haekelte kurz. */
@@ -198,7 +215,7 @@ try {
Buffer.from(r.data, "base64")); Buffer.from(r.data, "base64"));
if (n % 15 === 0) process.stdout.write("."); if (n % 15 === 0) process.stdout.write(".");
} }
const datei = path.join(ZIEL, PROJEKT + "-" + a.key + ".mp4"); const datei = path.join(ZIEL, PROJEKT + "-" + a.key + DATEI_ZUSATZ + ".mp4");
process.stdout.write(" kodiere "); process.stdout.write(" kodiere ");
await kodieren(ordner, datei, anzahl); await kodieren(ordner, datei, anzahl);
await rm(ordner, { recursive: true, force: true }); await rm(ordner, { recursive: true, force: true });
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

+6 -1
View File
@@ -805,7 +805,12 @@ if (location.search.indexOf("nur=streifen") >= 0)
<button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button> <button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button>
<button type="button" data-gehe="./post7.html">Post 7 · Form Builder</button> <button type="button" data-gehe="./post7.html">Post 7 · Form Builder</button>
<button type="button" data-gehe="./post8.html">Post 8 · E-Mail-Marketing</button> <button type="button" data-gehe="./post8.html">Post 8 · E-Mail-Marketing</button>
<button type="button" data-gehe="./post9.html">Post 9 · Webinar</button> <button type="button" data-gehe="./post9.html">Post 9 · Variante 1 · Webinar-Fenster</button>
<button type="button" data-gehe="./post9-v2.html">Post 9 · Variante 2 · Kundengeschichte</button>
<button type="button" data-gehe="./post9-v3.html">Post 9 · Variante 3 · Kundengeschichte dunkel</button>
<button type="button" data-gehe="./post9-v4.html">Post 9 · Variante 4 · Zahlen</button>
<button type="button" data-gehe="./post9-v5.html">Post 9 · Variante 5 · Zahlen dunkel</button>
<button type="button" data-gehe="./post9-varianten.html">Post 9 · Varianten im Vergleich</button>
<hr> <hr>
<button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button> <button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button>
<hr> <hr>
+6 -1
View File
@@ -1108,7 +1108,12 @@ if (location.search.indexOf("nur=anim") >= 0)
<button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button> <button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button>
<button type="button" data-gehe="./post7.html">Post 7 · Form Builder</button> <button type="button" data-gehe="./post7.html">Post 7 · Form Builder</button>
<button type="button" data-gehe="./post8.html">Post 8 · E-Mail-Marketing</button> <button type="button" data-gehe="./post8.html">Post 8 · E-Mail-Marketing</button>
<button type="button" data-gehe="./post9.html">Post 9 · Webinar</button> <button type="button" data-gehe="./post9.html">Post 9 · Variante 1 · Webinar-Fenster</button>
<button type="button" data-gehe="./post9-v2.html">Post 9 · Variante 2 · Kundengeschichte</button>
<button type="button" data-gehe="./post9-v3.html">Post 9 · Variante 3 · Kundengeschichte dunkel</button>
<button type="button" data-gehe="./post9-v4.html">Post 9 · Variante 4 · Zahlen</button>
<button type="button" data-gehe="./post9-v5.html">Post 9 · Variante 5 · Zahlen dunkel</button>
<button type="button" data-gehe="./post9-varianten.html">Post 9 · Varianten im Vergleich</button>
<hr> <hr>
<button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button> <button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button>
<hr> <hr>
+6 -1
View File
@@ -1001,7 +1001,12 @@ if (location.search.indexOf("nur=anim") >= 0)
<button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button> <button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button>
<button type="button" data-gehe="./post7.html">Post 7 · Form Builder</button> <button type="button" data-gehe="./post7.html">Post 7 · Form Builder</button>
<button type="button" data-gehe="./post8.html">Post 8 · E-Mail-Marketing</button> <button type="button" data-gehe="./post8.html">Post 8 · E-Mail-Marketing</button>
<button type="button" data-gehe="./post9.html">Post 9 · Webinar</button> <button type="button" data-gehe="./post9.html">Post 9 · Variante 1 · Webinar-Fenster</button>
<button type="button" data-gehe="./post9-v2.html">Post 9 · Variante 2 · Kundengeschichte</button>
<button type="button" data-gehe="./post9-v3.html">Post 9 · Variante 3 · Kundengeschichte dunkel</button>
<button type="button" data-gehe="./post9-v4.html">Post 9 · Variante 4 · Zahlen</button>
<button type="button" data-gehe="./post9-v5.html">Post 9 · Variante 5 · Zahlen dunkel</button>
<button type="button" data-gehe="./post9-varianten.html">Post 9 · Varianten im Vergleich</button>
<hr> <hr>
<button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button> <button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button>
<hr> <hr>
+6 -1
View File
@@ -1021,7 +1021,12 @@ if (location.search.indexOf("nur=anim") >= 0)
<button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button> <button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button>
<button type="button" data-gehe="./post7.html">Post 7 · Form Builder</button> <button type="button" data-gehe="./post7.html">Post 7 · Form Builder</button>
<button type="button" data-gehe="./post8.html">Post 8 · E-Mail-Marketing</button> <button type="button" data-gehe="./post8.html">Post 8 · E-Mail-Marketing</button>
<button type="button" data-gehe="./post9.html">Post 9 · Webinar</button> <button type="button" data-gehe="./post9.html">Post 9 · Variante 1 · Webinar-Fenster</button>
<button type="button" data-gehe="./post9-v2.html">Post 9 · Variante 2 · Kundengeschichte</button>
<button type="button" data-gehe="./post9-v3.html">Post 9 · Variante 3 · Kundengeschichte dunkel</button>
<button type="button" data-gehe="./post9-v4.html">Post 9 · Variante 4 · Zahlen</button>
<button type="button" data-gehe="./post9-v5.html">Post 9 · Variante 5 · Zahlen dunkel</button>
<button type="button" data-gehe="./post9-varianten.html">Post 9 · Varianten im Vergleich</button>
<hr> <hr>
<button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button> <button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button>
<hr> <hr>
+6 -1
View File
@@ -1049,7 +1049,12 @@ if (location.search.indexOf("nur=anim") >= 0)
<button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button> <button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button>
<button type="button" data-gehe="./post7.html">Post 7 · Form Builder</button> <button type="button" data-gehe="./post7.html">Post 7 · Form Builder</button>
<button type="button" data-gehe="./post8.html">Post 8 · E-Mail-Marketing</button> <button type="button" data-gehe="./post8.html">Post 8 · E-Mail-Marketing</button>
<button type="button" data-gehe="./post9.html">Post 9 · Webinar</button> <button type="button" data-gehe="./post9.html">Post 9 · Variante 1 · Webinar-Fenster</button>
<button type="button" data-gehe="./post9-v2.html">Post 9 · Variante 2 · Kundengeschichte</button>
<button type="button" data-gehe="./post9-v3.html">Post 9 · Variante 3 · Kundengeschichte dunkel</button>
<button type="button" data-gehe="./post9-v4.html">Post 9 · Variante 4 · Zahlen</button>
<button type="button" data-gehe="./post9-v5.html">Post 9 · Variante 5 · Zahlen dunkel</button>
<button type="button" data-gehe="./post9-varianten.html">Post 9 · Varianten im Vergleich</button>
<hr> <hr>
<button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button> <button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button>
<hr> <hr>
+6 -1
View File
@@ -1049,7 +1049,12 @@ if (location.search.indexOf("nur=anim") >= 0)
<button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button> <button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button>
<button type="button" data-gehe="./post7.html">Post 7 · Form Builder</button> <button type="button" data-gehe="./post7.html">Post 7 · Form Builder</button>
<button type="button" data-gehe="./post8.html">Post 8 · E-Mail-Marketing</button> <button type="button" data-gehe="./post8.html">Post 8 · E-Mail-Marketing</button>
<button type="button" data-gehe="./post9.html">Post 9 · Webinar</button> <button type="button" data-gehe="./post9.html">Post 9 · Variante 1 · Webinar-Fenster</button>
<button type="button" data-gehe="./post9-v2.html">Post 9 · Variante 2 · Kundengeschichte</button>
<button type="button" data-gehe="./post9-v3.html">Post 9 · Variante 3 · Kundengeschichte dunkel</button>
<button type="button" data-gehe="./post9-v4.html">Post 9 · Variante 4 · Zahlen</button>
<button type="button" data-gehe="./post9-v5.html">Post 9 · Variante 5 · Zahlen dunkel</button>
<button type="button" data-gehe="./post9-varianten.html">Post 9 · Varianten im Vergleich</button>
<hr> <hr>
<button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button> <button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button>
<hr> <hr>
+6 -1
View File
@@ -1053,7 +1053,12 @@ if (location.search.indexOf("nur=anim") >= 0)
<button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button> <button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button>
<button type="button" data-gehe="./post7.html">Post 7 · Form Builder</button> <button type="button" data-gehe="./post7.html">Post 7 · Form Builder</button>
<button type="button" data-gehe="./post8.html">Post 8 · E-Mail-Marketing</button> <button type="button" data-gehe="./post8.html">Post 8 · E-Mail-Marketing</button>
<button type="button" data-gehe="./post9.html">Post 9 · Webinar</button> <button type="button" data-gehe="./post9.html">Post 9 · Variante 1 · Webinar-Fenster</button>
<button type="button" data-gehe="./post9-v2.html">Post 9 · Variante 2 · Kundengeschichte</button>
<button type="button" data-gehe="./post9-v3.html">Post 9 · Variante 3 · Kundengeschichte dunkel</button>
<button type="button" data-gehe="./post9-v4.html">Post 9 · Variante 4 · Zahlen</button>
<button type="button" data-gehe="./post9-v5.html">Post 9 · Variante 5 · Zahlen dunkel</button>
<button type="button" data-gehe="./post9-varianten.html">Post 9 · Varianten im Vergleich</button>
<hr> <hr>
<button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button> <button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button>
<hr> <hr>
+6 -1
View File
@@ -1063,7 +1063,12 @@ if (location.search.indexOf("nur=anim") >= 0)
<button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button> <button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button>
<button type="button" data-gehe="./post7.html">Post 7 · Form Builder</button> <button type="button" data-gehe="./post7.html">Post 7 · Form Builder</button>
<button type="button" data-gehe="./post8.html">Post 8 · E-Mail-Marketing</button> <button type="button" data-gehe="./post8.html">Post 8 · E-Mail-Marketing</button>
<button type="button" data-gehe="./post9.html">Post 9 · Webinar</button> <button type="button" data-gehe="./post9.html">Post 9 · Variante 1 · Webinar-Fenster</button>
<button type="button" data-gehe="./post9-v2.html">Post 9 · Variante 2 · Kundengeschichte</button>
<button type="button" data-gehe="./post9-v3.html">Post 9 · Variante 3 · Kundengeschichte dunkel</button>
<button type="button" data-gehe="./post9-v4.html">Post 9 · Variante 4 · Zahlen</button>
<button type="button" data-gehe="./post9-v5.html">Post 9 · Variante 5 · Zahlen dunkel</button>
<button type="button" data-gehe="./post9-varianten.html">Post 9 · Varianten im Vergleich</button>
<hr> <hr>
<button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button> <button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button>
<hr> <hr>
+6 -1
View File
@@ -1063,7 +1063,12 @@ if (location.search.indexOf("nur=anim") >= 0)
<button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button> <button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button>
<button type="button" data-gehe="./post7.html">Post 7 · Form Builder</button> <button type="button" data-gehe="./post7.html">Post 7 · Form Builder</button>
<button type="button" data-gehe="./post8.html">Post 8 · E-Mail-Marketing</button> <button type="button" data-gehe="./post8.html">Post 8 · E-Mail-Marketing</button>
<button type="button" data-gehe="./post9.html">Post 9 · Webinar</button> <button type="button" data-gehe="./post9.html">Post 9 · Variante 1 · Webinar-Fenster</button>
<button type="button" data-gehe="./post9-v2.html">Post 9 · Variante 2 · Kundengeschichte</button>
<button type="button" data-gehe="./post9-v3.html">Post 9 · Variante 3 · Kundengeschichte dunkel</button>
<button type="button" data-gehe="./post9-v4.html">Post 9 · Variante 4 · Zahlen</button>
<button type="button" data-gehe="./post9-v5.html">Post 9 · Variante 5 · Zahlen dunkel</button>
<button type="button" data-gehe="./post9-varianten.html">Post 9 · Varianten im Vergleich</button>
<hr> <hr>
<button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button> <button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button>
<hr> <hr>
+8403
View File
File diff suppressed because it is too large. Load diff
+8407
View File
File diff suppressed because it is too large. Load diff
+8584
View File
File diff suppressed because it is too large. Load diff
+8587
View File
File diff suppressed because it is too large. Load diff
+179
View File
@@ -0,0 +1,179 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PostGrid · Post 9 · Varianten</title>
<link rel="stylesheet" href="./assets/fonts/manrope.css">
<style>
/* ─── Post 9 im Vergleich ─────────────────────────────────────────────────
Die Varianten der Webinar-Anzeige untereinander, wie die Uebersicht aller
Projekte: jede Variante kommt als iframe im Modus ?nur=streifen und zeigt
genau, was der Generator zeigt. Variante 1 beginnt mit dem Bewegtbild.
Das steht nicht im Streifen (Animationen liegen ausserhalb), deshalb
laeuft davor das exportierte MP4 im passenden Format. */
* { box-sizing: border-box; }
:root { color-scheme: dark; }
html, body { height: 100%; }
body {
margin: 0; font-family: Manrope, system-ui, sans-serif;
background: #101116; color: #C7CCD6; overflow: hidden;
}
.menubar {
position: relative; z-index: 10; display: flex; align-items: center; gap: 2px;
height: 34px; padding: 0 10px; background: #16181F;
border-bottom: 1px solid #23262F; font-size: 13px; user-select: none;
}
.menubar .marke { font-weight: 800; color: #FFFFFF; margin-right: 14px; letter-spacing: -.01em; }
.menubar a {
color: #C7CCD6; text-decoration: none; padding: 5px 10px; border-radius: 6px;
}
.menubar a:hover { background: #23262F; color: #FFFFFF; }
.stage { position: absolute; inset: 34px 0 0; overflow-y: auto; overflow-x: hidden;
background-color: #101116;
background-image: radial-gradient(circle, #282B35 1.6px, transparent 1.6px);
background-size: 26px 26px;
}
.flaeche { padding: 48px 54px 80px; display: flex; flex-direction: column; gap: 56px; }
.kopf h1 { margin: 0; font-size: 26px; font-weight: 800; color: #FFFFFF; letter-spacing: -.01em; }
.kopf p { margin: 6px 0 0; font-size: 14px; color: #6E7480; }
.variante { display: flex; flex-direction: column; gap: 14px; }
.variante h2 {
margin: 0; font-size: 13px; font-weight: 800; letter-spacing: .14em;
text-transform: uppercase; color: #6E7480;
}
.variante h2 b { color: #F1B729; font-weight: 800; }
.variante h2 span { color: #FFFFFF; }
.variante p { margin: 0; max-width: 820px; font-size: 14px; line-height: 1.55; color: #9AA0AC; }
.reihe { display: flex; gap: 0; align-items: flex-start; }
.rahmen { overflow: hidden; background: #FFFFFF; border: 1px solid #2E313B; flex: none; }
.rahmen + .rahmen { border-left: 0; }
.rahmen iframe {
display: block; border: 0; transform-origin: 0 0; background: #FFFFFF; pointer-events: none;
}
.rahmen video { display: block; width: 100%; height: 100%; object-fit: cover; }
.marke-video {
font-size: 11px; color: #6E7480; letter-spacing: .06em; text-transform: uppercase; margin-top: 8px;
}
</style>
</head>
<body>
<div class="menubar">
<span class="marke">PostGrid</span>
<a href="./uebersicht.html">Alle Projekte</a>
<a href="./post9.html">Variante 1 bearbeiten</a>
<a href="./post9-v2.html">Variante 2 bearbeiten</a>
<a href="./post9-v3.html">Variante 3 bearbeiten</a>
<a href="./post9-v4.html">Variante 4 bearbeiten</a>
<a href="./post9-v5.html">Variante 5 bearbeiten</a>
</div>
<main class="stage" id="stage">
<div class="flaeche">
<div class="kopf">
<h1>Post 9 · Webinar-Anzeige</h1>
<p id="meta">Fünf Varianten derselben Anzeige. Lade Streifen ...</p>
</div>
<section class="variante">
<h2><b>Variante 1</b> · <span>Webinar-Fenster</span> · fertig seit 17.10.</h2>
<p>Das Bewegtbild zeigt das Webinar mit seinen vier Teilen und beiden Sprechern, danach die Anmeldung.
Zwei Elemente, das erste ist ein Video. Als Anzeige deshalb in 1:1.</p>
<div class="reihe" id="reihe1">
<div class="rahmen" id="video1"><video id="anim1" autoplay muted loop playsinline></video></div>
<div class="rahmen"><iframe id="f1" src="./post9.html?nur=streifen" loading="eager"></iframe></div>
</div>
</section>
<section class="variante">
<h2><b>Variante 2</b> · <span>Kundengeschichte</span> · angelehnt an weclapp</h2>
<p>Komposition wie das Karussell von weclapp zu PV Green (Werbebibliothek, ID 792638160291622): Logo mit Etikett
und Foto, Zitat mit freigestellter Person, Ausgangslage, mit HiveGrid, Anmeldung. Farben, Schrift und Formen
von uns: Gelb und Dunkel, Manrope, Waben, Chamfer. Fünf Bilder, kein Video, als Anzeige also auch in 4:5 möglich.</p>
<div class="reihe">
<div class="rahmen"><iframe id="f2" src="./post9-v2.html?nur=streifen" loading="eager"></iframe></div>
</div>
</section>
<section class="variante">
<h2><b>Variante 3</b> · <span>Kundengeschichte dunkel</span> · wie Variante 2</h2>
<p>Dieselben fünf Slides wie Variante 2 auf dem dunklen Grund des Posts. Texte in Weiß, Formen und Wabenband in
einem helleren Blau, Gelb bleibt. Was auf Gelb und im Geerkens-Chip steht, bleibt dunkel.</p>
<div class="reihe">
<div class="rahmen"><iframe id="f3" src="./post9-v3.html?nur=streifen" loading="eager"></iframe></div>
</div>
</section>
<section class="variante">
<h2><b>Variante 4</b> · <span>Zahlen</span> · angelehnt an HERO Insights</h2>
<p>Komposition wie das Karussell „HERO Insights“ (Werbebibliothek, ID 3818182885142025): dunkles Titelbild,
dann Karten mit Titel, schwarzem und gelbem Balken und einer Grafik. Titelbild mit Noah als Teaser fürs Webinar,
Zahlen als Wabenbild (100 Waben) und Ring statt Tabellen. Thema: Zeit, die Betriebe verlieren, und wie wenig KI bisher
genutzt wird. Zahlen aus KfW Research (April 2025) und Bitkom (504 Betriebe, 2025), Quelle auf jeder Karte.</p>
<div class="reihe">
<div class="rahmen"><iframe id="f4" src="./post9-v4.html?nur=streifen" loading="eager"></iframe></div>
</div>
</section>
<section class="variante">
<h2><b>Variante 5</b> · <span>Zahlen dunkel</span> · wie Variante 4</h2>
<p>Dieselben fünf Karten wie Variante 4 auf dem dunklen Grund des Posts.</p>
<div class="reihe">
<div class="rahmen"><iframe id="f5" src="./post9-v5.html?nur=streifen" loading="eager"></iframe></div>
</div>
</section>
</div>
</main>
<script>
(() => {
"use strict";
const stage = document.getElementById("stage");
const meta = document.getElementById("meta");
/* Breite und Hoehe melden die Streifen selbst, sobald sie stehen. */
const st = {
f1: { el: document.getElementById("f1"), w: 1080, h: 1080, da: false },
f2: { el: document.getElementById("f2"), w: 5400, h: 1080, da: false },
f3: { el: document.getElementById("f3"), w: 5400, h: 1080, da: false },
f4: { el: document.getElementById("f4"), w: 5400, h: 1080, da: false },
f5: { el: document.getElementById("f5"), w: 5400, h: 1080, da: false },
};
const video = document.getElementById("anim1"), videoRahmen = document.getElementById("video1");
window.addEventListener("message", e => {
const d = e.data;
if (!d || d.hg !== "streifen") return;
const r = Object.values(st).find(x => x.el.contentWindow === e.source);
if (!r) return;
r.w = d.w; r.h = d.h; r.da = true;
legen();
});
/* Alle Slides im selben Massstab, eingepasst an die laengste Reihe. */
function legen() {
const breit = Math.max(st.f1.w + 1080, st.f2.w, st.f3.w, st.f4.w, st.f5.w);
const k = Math.min((stage.clientWidth - 108) / breit, 0.42);
Object.values(st).forEach(r => {
r.el.style.width = r.w + "px";
r.el.style.height = r.h + "px";
r.el.style.transform = "scale(" + k + ")";
r.el.parentElement.style.width = Math.round(r.w * k) + "px";
r.el.parentElement.style.height = Math.round(r.h * k) + "px";
});
/* Das Video im Format der Variante 1: 1:1 oder 4:5. */
videoRahmen.style.width = Math.round(1080 * k) + "px";
videoRahmen.style.height = Math.round(st.f1.h * k) + "px";
const quelle = "./animation/post9-animWebinar" + (st.f1.h === 1080 ? "-1x1" : "") + ".mp4";
if (!video.src.endsWith(quelle.slice(1))) { video.src = quelle; video.play().catch(() => {}); }
const fertig = Object.values(st).every(r => r.da);
meta.textContent = fertig
? "Fünf Varianten derselben Anzeige · Variante 1: 1 Video, 1 Bild · Varianten 2 bis 5: je 5 Bilder · Format " +
Object.values(st).map(r => r.h === 1080 ? "1:1" : "4:5").join(", ")
: "Fünf Varianten derselben Anzeige. Lade Streifen ...";
}
window.addEventListener("resize", legen);
legen();
})();
</script>
</body>
</html>
+134 -32
View File
@@ -15,7 +15,7 @@ if (location.search.indexOf("nur=anim") >= 0)
</script> </script>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>PostGrid Post 9 · Webinar</title> <title>PostGrid Post 9 · Variante 1 · Webinar-Fenster</title>
<link rel="stylesheet" href="./assets/fonts/manrope.css"> <link rel="stylesheet" href="./assets/fonts/manrope.css">
<style> <style>
/* ─── HiveGrid design tokens (mirrored from app/globals.css) ───────────── /* ─── HiveGrid design tokens (mirrored from app/globals.css) ─────────────
@@ -247,6 +247,9 @@ if (location.search.indexOf("nur=anim") >= 0)
#el-inhalt input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } #el-inhalt input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
#el-inhalt input:focus { outline: none; border-color: var(--accent-yellow); } #el-inhalt input:focus { outline: none; border-color: var(--accent-yellow); }
#el-inhalt .hint { color: #6E7480; margin-top: 6px; } #el-inhalt .hint { color: #6E7480; margin-top: 6px; }
.group[data-grp="format"] .hint {
font-size: 11.5px; line-height: 1.5; color: #6E7480; margin-top: 8px;
}
#el-inhalt .row { margin-top: 0; } #el-inhalt .row { margin-top: 0; }
.std { margin-bottom: 22px; } .std { margin-bottom: 22px; }
.std summary { .std summary {
@@ -345,9 +348,11 @@ if (location.search.indexOf("nur=anim") >= 0)
.strip[data-view="einzeln"] .holder { display: none; } .strip[data-view="einzeln"] .holder { display: none; }
.strip[data-view="einzeln"] .holder.sel { display: block; } .strip[data-view="einzeln"] .holder.sel { display: block; }
/* ─── Slide base — exactly Instagram 4:5 (1080 × 1350) ───────────────── */ /* ─── Slide base — Instagram 4:5 (1080 × 1350) oder 1:1 (1080 × 1080) ──
Die Hoehe kommt aus --slide-h, das formatAnwenden() auf <html> setzt.
Ohne Wert bleibt es 4:5 wie in allen anderen Posts. */
.slide { .slide {
width: 1080px; height: 1350px; width: 1080px; height: var(--slide-h, 1350px);
position: relative; overflow: hidden; position: relative; overflow: hidden;
font-family: 'Manrope', system-ui, sans-serif; font-family: 'Manrope', system-ui, sans-serif;
} }
@@ -533,7 +538,7 @@ if (location.search.indexOf("nur=anim") >= 0)
--animskala: .215; --animskala: .215;
flex: none; position: relative; overflow: hidden; border-radius: 6px; flex: none; position: relative; overflow: hidden; border-radius: 6px;
width: calc(1080px * var(--animskala)); width: calc(1080px * var(--animskala));
height: calc(1350px * var(--animskala)); height: calc(var(--slide-h, 1350px) * var(--animskala));
box-shadow: 0 0 0 1px #2E313B; box-shadow: 0 0 0 1px #2E313B;
} }
.animbuehne > .slide { .animbuehne > .slide {
@@ -626,7 +631,7 @@ if (location.search.indexOf("nur=anim") >= 0)
left: calc(var(--slide-i, 0) * -1080px); left: calc(var(--slide-i, 0) * -1080px);
width: var(--sheet-w, 5400px); z-index: 1; width: var(--sheet-w, 5400px); z-index: 1;
} }
.page { position: absolute; top: 0; width: 1080px; height: 1350px; } .page { position: absolute; top: 0; width: 1080px; height: var(--slide-h, 1350px); }
/* Im Theme modern traegt das Badge wieder Platz, deshalb beginnt der Block /* Im Theme modern traegt das Badge wieder Platz, deshalb beginnt der Block
bei 92 -- dem Rand der Safe Zone -- statt bei den 59 des Themes notebook. */ bei 92 -- dem Rand der Safe Zone -- statt bei den 59 des Themes notebook. */
@@ -1053,7 +1058,12 @@ if (location.search.indexOf("nur=anim") >= 0)
<button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button> <button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button>
<button type="button" data-gehe="./post7.html">Post 7 · Form Builder</button> <button type="button" data-gehe="./post7.html">Post 7 · Form Builder</button>
<button type="button" data-gehe="./post8.html">Post 8 · E-Mail-Marketing</button> <button type="button" data-gehe="./post8.html">Post 8 · E-Mail-Marketing</button>
<button type="button" data-gehe="./post9.html">Post 9 · Webinar</button> <button type="button" data-gehe="./post9.html">Post 9 · Variante 1 · Webinar-Fenster</button>
<button type="button" data-gehe="./post9-v2.html">Post 9 · Variante 2 · Kundengeschichte</button>
<button type="button" data-gehe="./post9-v3.html">Post 9 · Variante 3 · Kundengeschichte dunkel</button>
<button type="button" data-gehe="./post9-v4.html">Post 9 · Variante 4 · Zahlen</button>
<button type="button" data-gehe="./post9-v5.html">Post 9 · Variante 5 · Zahlen dunkel</button>
<button type="button" data-gehe="./post9-varianten.html">Post 9 · Varianten im Vergleich</button>
<hr> <hr>
<button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button> <button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button>
<hr> <hr>
@@ -1115,7 +1125,7 @@ if (location.search.indexOf("nur=anim") >= 0)
<div class="arbeitsflaeche"> <div class="arbeitsflaeche">
<aside class="panel"> <aside class="panel">
<h1>Post 9 · Webinar</h1> <h1>Post 9 · Variante 1</h1>
<p class="sub"><span id="kopfzeile">Carousel · 1080 × 1350 px</span><span class="saved" id="saved"></span><br><button class="forget" id="forget">Projekt zurücksetzen</button></p> <p class="sub"><span id="kopfzeile">Carousel · 1080 × 1350 px</span><span class="saved" id="saved"></span><br><button class="forget" id="forget">Projekt zurücksetzen</button></p>
<div class="group"> <div class="group">
@@ -1145,6 +1155,17 @@ if (location.search.indexOf("nur=anim") >= 0)
Die Datei landet unverändert unter <b>assets/eigene/</b> und gehört mit in den Commit.</div> Die Datei landet unverändert unter <b>assets/eigene/</b> und gehört mit in den Commit.</div>
</details> </details>
<details class="group" data-grp="format" open>
<summary>Format</summary>
<div class="opts" id="formate">
<button data-g="4x5" aria-pressed="true">4:5&nbsp;·&nbsp;Beitrag</button>
<button data-g="1x1" aria-pressed="false">1:1&nbsp;·&nbsp;Anzeige</button>
</div>
<div class="hint">Meta nimmt ein Carousel mit Video als Anzeige nur in 1:1.
Beide Formate teilen Texte und Slides. In 1:1 sind die Karten kleiner, und
„Link in der Bio“ fehlt, weil die Anzeige einen eigenen Knopf hat.</div>
</details>
<details class="group" data-grp="variante"> <details class="group" data-grp="variante">
<summary>Variante</summary> <summary>Variante</summary>
<div class="opts" id="variants"> <div class="opts" id="variants">
@@ -1434,6 +1455,11 @@ if (location.search.indexOf("nur=anim") >= 0)
head: 'Live zuschauen,<br><span class="a">live fragen.</span>', head: 'Live zuschauen,<br><span class="a">live fragen.</span>',
body: "Was Automatisierung in deinem Betrieb wirklich macht. Mit dem Branchenprogramm, das du schon hast.", body: "Was Automatisierung in deinem Betrieb wirklich macht. Mit dem Branchenprogramm, das du schon hast.",
vis: "anmeldung", visW: 896, visLeft: 92, visBottom: 168, vis: "anmeldung", visW: 896, visLeft: 92, visBottom: 168,
/* 1:1 fuer die Anzeige: 270 px weniger Hoehe. Ueberschrift und Text
bleiben, die Karte wird kleiner und steht mittig, unten wie oben
92 px Rand. Gemessen am 02.11.: Text endet bei y 369, die Karte
beginnt 50 px darunter. */
quadrat: { visW: 736, visLeft: 172, visBottom: 92 },
}, },
]; ];
@@ -1456,6 +1482,10 @@ if (location.search.indexOf("nur=anim") >= 0)
/* 244 statt 226 wie in Post 8: die Karte ist hier niedriger, und der /* 244 statt 226 wie in Post 8: die Karte ist hier niedriger, und der
Abstand zur Ueberschrift soll derselbe bleiben, rund 118 px. */ Abstand zur Ueberschrift soll derselbe bleiben, rund 118 px. */
vis: "animWebinar", visW: 896, visLeft: 92, visBottom: 244, vis: "animWebinar", visW: 896, visLeft: 92, visBottom: 244,
/* 1:1: die Karte so breit wie auf der Anmelde-Slide, damit beim
Wischen nichts springt, und rund 90 px unter der Ueberschrift wie
in 4:5 (dort 88). */
quadrat: { visW: 736, visLeft: 172, visBottom: 106 },
}, },
]; ];
@@ -4826,9 +4856,14 @@ if (location.search.indexOf("nur=anim") >= 0)
g += '<path d="' + rundRechteck(0, y, WB_B, 68, 12) + '" fill="#24242B"/>'; g += '<path d="' + rundRechteck(0, y, WB_B, 68, 12) + '" fill="#24242B"/>';
g += uiText(WB_B / 2, y + 42, "Jetzt anmelden", 22, 800, "#FFFFFF", { anker: "middle" }); g += uiText(WB_B / 2, y + 42, "Jetzt anmelden", 22, 800, "#FFFFFF", { anker: "middle" });
y += 68 + 36; y += 68;
g += uiText(WB_B / 2, y, "Link in der Bio", 18, 600, CHAT_TEXT3, { anker: "middle" }); /* „Link in der Bio“ nur im Beitrag. Die Anzeige (1:1) traegt ihren
y += 6; eigenen Knopf zur Anmeldeseite, dort fuehrte die Zeile in die Irre. */
if (format !== "1x1") {
y += 36;
g += uiText(WB_B / 2, y, "Link in der Bio", 18, 600, CHAT_TEXT3, { anker: "middle" });
y += 6;
}
return wbRahmen(WB_KOPF + y + WB_RAND, g, { zeichen: "calendar-check", titel: "Live-Webinar" }); return wbRahmen(WB_KOPF + y + WB_RAND, g, { zeichen: "calendar-check", titel: "Live-Webinar" });
} }
@@ -4961,7 +4996,7 @@ if (location.search.indexOf("nur=anim") >= 0)
if (key === "mock") return SLIDES[i].mockW || 1090; if (key === "mock") return SLIDES[i].mockW || 1090;
if (key === "mock-2") return (SLIDES[i].media2 || {}).w || 400; if (key === "mock-2") return (SLIDES[i].media2 || {}).w || 400;
if (key === "note") return (SLIDES[i].note || {}).w || 320; if (key === "note") return (SLIDES[i].note || {}).w || 320;
if (key === "vis") return SLIDES[i].visW || 900; if (key === "vis") return visMass(SLIDES[i]).visW || 900;
return (STANDARD[key] || {}).size; return (STANDARD[key] || {}).size;
} }
@@ -4996,6 +5031,36 @@ if (location.search.indexOf("nur=anim") >= 0)
Textblock darueber steht dann sichtbar aus der Achse. */ Textblock darueber steht dann sichtbar aus der Achse. */
let bandTon = "orange", rasterAn = "punkte", textMitte = "an"; let bandTon = "orange", rasterAn = "punkte", textMitte = "an";
/* ─── Format ──────────────────────────────────────────────────────────────
4:5 ist das Format der Beitraege. 1:1 ist das Format der Anzeige: Meta
nimmt ein Carousel, in dem ein Video steckt, im Instagram-Feed nur
quadratisch. Wortlaut im Ads Guide (abgerufen 02.11.2026): „Derzeit wird
bei Carousels, die Videos enthalten, nur das Format 1:1 unterstützt.“
Reine Bild-Carousels duerften 4:5 bleiben.
Die Breite bleibt 1080, nur die Hoehe wechselt. Texte, Slides und
Verschiebungen teilen sich beide Formate; was sich je Format
unterscheidet, steht an der Slide unter `quadrat` (Groesse und Lage der
Karte). Die URL kann das Format vorgeben (?format=1x1), damit
animation.mjs jedes Format bauen kann, ohne den Stand umzustellen. */
const FORMATE = {
"4x5": { h: 1350, name: "4:5", datei: "" },
"1x1": { h: 1080, name: "1:1", datei: "-1x1" },
};
let format = "4x5";
let SLIDE_H = FORMATE[format].h;
const FORMAT_URL = (f => FORMATE[f] ? f : null)(new URLSearchParams(location.search).get("format"));
function formatAnwenden() {
if (!FORMATE[format]) format = "4x5";
SLIDE_H = FORMATE[format].h;
document.documentElement.style.setProperty("--slide-h", SLIDE_H + "px");
document.documentElement.dataset.format = format;
}
/* Die Karte je Format. Fuer 4:5 gilt, was an der Slide steht; fuer 1:1
ueberschreibt `quadrat` Breite, Abstand links und Abstand unten. */
const visMass = sl => (format === "1x1" && sl.quadrat) ? Object.assign({}, sl, sl.quadrat) : sl;
window.SLIDE_FORMAT = () => ({ format, breite: 1080, hoehe: SLIDE_H, datei: FORMATE[format].datei });
/* ─── Projektstand ──────────────────────────────────────────────────────── /* ─── Projektstand ────────────────────────────────────────────────────────
Alles, was am Projekt hängt, wird gespeichert: Einstellungen, Layouts, Alles, was am Projekt hängt, wird gespeichert: Einstellungen, Layouts,
Muster und die Verschiebungen im Canvas. Das Werkzeug soll den Stand Muster und die Verschiebungen im Canvas. Das Werkzeug soll den Stand
@@ -5021,7 +5086,7 @@ if (location.search.indexOf("nur=anim") >= 0)
function currentState() { function currentState() {
return { return {
v: STATE_V, variant, tilt, superSample, view, tagOut, frameStyle, bandTon, v: STATE_V, variant, tilt, superSample, view, tagOut, frameStyle, bandTon,
rasterAn, textMitte, rasterAn, textMitte, format,
current, layoutState, bgState, edits, current, layoutState, bgState, edits,
// Welche Slides der Post ueberhaupt hat. Ohne das waere eine selbst // Welche Slides der Post ueberhaupt hat. Ohne das waere eine selbst
// angelegte Slide nach dem Neuladen weg. // angelegte Slide nach dem Neuladen weg.
@@ -5169,6 +5234,7 @@ if (location.search.indexOf("nur=anim") >= 0)
press("rasters", "r", rasterAn); press("rasters", "r", rasterAn);
press("mitten", "m", textMitte); press("mitten", "m", textMitte);
press("views", "w", view); press("views", "w", view);
press("formate", "g", format);
// Nach einem Schritt, der Slides hinzugefuegt, geloescht oder umsortiert // Nach einem Schritt, der Slides hinzugefuegt, geloescht oder umsortiert
// hat, stimmt der Aufbau im Canvas nicht mehr mit dem Stand ueberein. // hat, stimmt der Aufbau im Canvas nicht mehr mit dem Stand ueberein.
if (deck.querySelectorAll(".holder").length !== SLIDES.length) { if (deck.querySelectorAll(".holder").length !== SLIDES.length) {
@@ -5180,6 +5246,8 @@ if (location.search.indexOf("nur=anim") >= 0)
applyEditsAll(); applyEditsAll();
} }
anzahlNachziehen(); anzahlNachziehen();
animLeisteBauen();
fitStrip();
show(current); show(current);
if (picked) pick(picked.slide, picked.key); else syncEditPanel(); if (picked) pick(picked.slide, picked.key); else syncEditPanel();
} finally { restoring = false; } } finally { restoring = false; }
@@ -5305,6 +5373,8 @@ if (location.search.indexOf("nur=anim") >= 0)
bandTon = d.bandTon || bandTon; bandTon = d.bandTon || bandTon;
rasterAn = d.rasterAn || rasterAn; rasterAn = d.rasterAn || rasterAn;
textMitte = d.textMitte || textMitte; textMitte = d.textMitte || textMitte;
format = FORMAT_URL || (FORMATE[d.format] ? d.format : format);
formatAnwenden();
current = Number.isInteger(d.current) ? Math.min(d.current, SLIDES.length - 1) : 0; current = Number.isInteger(d.current) ? Math.min(d.current, SLIDES.length - 1) : 0;
} }
@@ -5350,7 +5420,7 @@ if (location.search.indexOf("nur=anim") >= 0)
const scroller = document.getElementById("scroller"); const scroller = document.getElementById("scroller");
const availW = Math.max(320, scroller.clientWidth - 120); const availW = Math.max(320, scroller.clientWidth - 120);
const availH = Math.max(240, scroller.clientHeight - 120); const availH = Math.max(240, scroller.clientHeight - 120);
return Math.min(availW / stripWidth(), availH / 1350, 0.45); return Math.min(availW / stripWidth(), availH / SLIDE_H, 0.45);
} }
/* Die Uebersicht muss wissen, wie breit dieser Streifen ist -- die Zahl /* Die Uebersicht muss wissen, wie breit dieser Streifen ist -- die Zahl
@@ -5359,7 +5429,7 @@ if (location.search.indexOf("nur=anim") >= 0)
Interna der Post-Seite unabhaengig. */ Interna der Post-Seite unabhaengig. */
function groesseMelden() { function groesseMelden() {
if (!NUR_STREIFEN || parent === window) return; if (!NUR_STREIFEN || parent === window) return;
parent.postMessage({ hg: "streifen", w: stripWidth(), h: 1350, parent.postMessage({ hg: "streifen", w: stripWidth(), h: SLIDE_H,
titel: document.title }, "*"); titel: document.title }, "*");
} }
@@ -5368,7 +5438,7 @@ if (location.search.indexOf("nur=anim") >= 0)
const k = zoom || autoFit(); const k = zoom || autoFit();
deck.style.transform = "scale(" + k + ")"; deck.style.transform = "scale(" + k + ")";
wrap.style.width = Math.round(stripWidth() * k) + "px"; wrap.style.width = Math.round(stripWidth() * k) + "px";
wrap.style.height = Math.round(1350 * k) + "px"; wrap.style.height = Math.round(SLIDE_H * k) + "px";
const lvl = document.getElementById("zoom-fit"); const lvl = document.getElementById("zoom-fit");
if (lvl) lvl.textContent = zoom ? Math.round(k * 100) + " %" : "Fit"; if (lvl) lvl.textContent = zoom ? Math.round(k * 100) + " %" : "Fit";
groesseMelden(); groesseMelden();
@@ -5490,7 +5560,7 @@ if (location.search.indexOf("nur=anim") >= 0)
rechten Kante der vorletzten Slide -- zwei Pixel innerhalb der Naht, rechten Kante der vorletzten Slide -- zwei Pixel innerhalb der Naht,
weil genau auf der Kante ein Antialiasing-Splitter auf der weil genau auf der Kante ein Antialiasing-Splitter auf der
Nachbarslide stehen bleibt. */ Nachbarslide stehen bleibt. */
const H = 1350; const H = SLIDE_H;
const A0 = BAND_VON * 1080 + 2; // Anker der Kurve const A0 = BAND_VON * 1080 + 2; // Anker der Kurve
const A1 = Math.max(A0, (SLIDES.length - BAND_BIS) * 1080 - 2); const A1 = Math.max(A0, (SLIDES.length - BAND_BIS) * 1080 - 2);
const X0 = Math.min(A1, BAND_VON * 1080 + BAND_ANSCHNITT); // ab hier gezeichnet const X0 = Math.min(A1, BAND_VON * 1080 + BAND_ANSCHNITT); // ab hier gezeichnet
@@ -5684,9 +5754,10 @@ if (location.search.indexOf("nur=anim") >= 0)
an der linken Slide-Kante beginnt und der Pfeil von Slide 4 dort an der linken Slide-Kante beginnt und der Pfeil von Slide 4 dort
ankommt. Mit || waere die 0 als fehlender Wert durchgegangen und die ankommt. Mit || waere die 0 als fehlender Wert durchgegangen und die
Flaeche haette weiter bei 92 gesessen. */ Flaeche haette weiter bei 92 gesessen. */
const m = visMass(sl);
v.style.cssText = v.style.cssText =
"--vis-w:" + (sl.visW ?? 900) + "px;--vis-left:" + (sl.visLeft ?? 92) + "--vis-w:" + (m.visW ?? 900) + "px;--vis-left:" + (m.visLeft ?? 92) +
"px;--vis-bottom:" + (sl.visBottom ?? 96) + "px"; "px;--vis-bottom:" + (m.visBottom ?? 96) + "px";
v.innerHTML = VIS[sl.vis](j, animT(j)); v.innerHTML = VIS[sl.vis](j, animT(j));
page.appendChild(v); page.appendChild(v);
} }
@@ -5817,9 +5888,10 @@ if (location.search.indexOf("nur=anim") >= 0)
const v = document.createElement("div"); const v = document.createElement("div");
v.className = "vis vis-" + a.vis; v.className = "vis vis-" + a.vis;
const m = visMass(a);
v.style.cssText = v.style.cssText =
"--vis-w:" + (a.visW ?? 900) + "px;--vis-left:" + (a.visLeft ?? 92) + "--vis-w:" + (m.visW ?? 900) + "px;--vis-left:" + (m.visLeft ?? 92) +
"px;--vis-bottom:" + (a.visBottom ?? 96) + "px"; "px;--vis-bottom:" + (m.visBottom ?? 96) + "px";
v.innerHTML = VIS[a.vis](i, animT(i)); v.innerHTML = VIS[a.vis](i, animT(i));
page.appendChild(v); page.appendChild(v);
@@ -5886,6 +5958,9 @@ if (location.search.indexOf("nur=anim") >= 0)
zeile.textContent = "Chrome startet …"; zeile.textContent = "Chrome startet …";
zeile.dataset.ton = ""; zeile.dataset.ton = "";
try { try {
/* animation.mjs liest das Format aus der Datei im Repo. Ein gerade
umgeschaltetes Format haengt womoeglich noch im Timer: erst schreiben. */
if (pushTimer) { clearTimeout(pushTimer); await pushState(currentState()); }
const an = await fetch("/anim", { const an = await fetch("/anim", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
@@ -5953,7 +6028,7 @@ if (location.search.indexOf("nur=anim") >= 0)
kopf.innerHTML = ANIM_PFEIL + "<b>Animation</b><span>" + kopf.innerHTML = ANIM_PFEIL + "<b>Animation</b><span>" +
(liste.length (liste.length
? liste.length + (liste.length === 1 ? " Element" : " Elemente") + ? liste.length + (liste.length === 1 ? " Element" : " Elemente") +
" &middot; 1080 &times; 1350 &middot; 30 fps &middot; MP4, getrennt vom Bildexport" " &middot; 1080 &times; " + SLIDE_H + " &middot; 30 fps &middot; MP4, getrennt vom Bildexport"
: "noch keine in diesem Projekt") + "</span>"; : "noch keine in diesem Projekt") + "</span>";
leiste.innerHTML = ""; leiste.innerHTML = "";
leiste.appendChild(kopf); leiste.appendChild(kopf);
@@ -5984,7 +6059,7 @@ if (location.search.indexOf("nur=anim") >= 0)
rechts.innerHTML = rechts.innerHTML =
'<div class="animtitel">' + a.titel + '</div>' + '<div class="animtitel">' + a.titel + '</div>' +
'<div class="animwerte">' + a.dauer.toFixed(1) + " s &middot; " + '<div class="animwerte">' + a.dauer.toFixed(1) + " s &middot; " +
Math.round(a.dauer * 30) + " Bilder &middot; 1080 &times; 1350</div>" + Math.round(a.dauer * 30) + " Bilder &middot; 1080 &times; " + SLIDE_H + "</div>" +
'<input class="animschieber" type="range" min="0" max="1000" value="0" ' + '<input class="animschieber" type="range" min="0" max="1000" value="0" ' +
'data-animschieber="' + a.i + '" aria-label="Zeitpunkt">' + 'data-animschieber="' + a.i + '" aria-label="Zeitpunkt">' +
'<div class="animknoepfe">' + '<div class="animknoepfe">' +
@@ -5992,7 +6067,7 @@ if (location.search.indexOf("nur=anim") >= 0)
'<button type="button" class="animexport">MP4 exportieren</button>' + '<button type="button" class="animexport">MP4 exportieren</button>' +
"</div>" + "</div>" +
'<p class="animzeile"><code>node animation.mjs ' + PROJECT + ".html " + '<p class="animzeile"><code>node animation.mjs ' + PROJECT + ".html " +
a.key + "</code></p>"; a.key + (format === "4x5" ? "" : " " + format) + "</code></p>";
karte.appendChild(rechts); karte.appendChild(rechts);
reihe.appendChild(karte); reihe.appendChild(karte);
@@ -6256,12 +6331,12 @@ if (location.search.indexOf("nur=anim") >= 0)
const k = superSample; const k = superSample;
return { return {
width: 1080 * k, width: 1080 * k,
height: 1350 * k, height: SLIDE_H * k,
pixelRatio: 1, pixelRatio: 1,
fontEmbedCSS: fontEmbedCss, fontEmbedCSS: fontEmbedCss,
style: { style: {
width: "1080px", width: "1080px",
height: "1350px", height: SLIDE_H + "px",
transform: k === 1 ? "none" : "scale(" + k + ")", transform: k === 1 ? "none" : "scale(" + k + ")",
transformOrigin: "top left", transformOrigin: "top left",
}, },
@@ -6283,7 +6358,8 @@ if (location.search.indexOf("nur=anim") >= 0)
} }
// Numeric prefix keeps the upload order intact in the file picker. // Numeric prefix keeps the upload order intact in the file picker.
const fileName = i => String(i + 1).padStart(2, "0") + "-" + SLIDES[i].key + "-" + variant + ".png"; const fileName = i => String(i + 1).padStart(2, "0") + "-" + SLIDES[i].key + "-" + variant +
FORMATE[format].datei + ".png";
/* html-to-image serialises the slide into a data:image/svg+xml URL and paints /* html-to-image serialises the slide into a data:image/svg+xml URL and paints
that as a single image. Raster children of a foreignObject decode that as a single image. Raster children of a foreignObject decode
@@ -6652,7 +6728,7 @@ if (location.search.indexOf("nur=anim") >= 0)
// Die Ausgabegröße hängt an der Renderqualität und wird mitgesagt, damit // Die Ausgabegröße hängt an der Renderqualität und wird mitgesagt, damit
// sichtbar ist, was tatsächlich auf der Platte landet. // sichtbar ist, was tatsächlich auf der Platte landet.
const ausgabeGroesse = () => (1080 * superSample) + " × " + (1350 * superSample) + " px"; const ausgabeGroesse = () => (1080 * superSample) + " × " + (SLIDE_H * superSample) + " px";
async function downloadOne() { async function downloadOne() {
setStatus("Rendere Slide " + (current + 1) + " …"); setStatus("Rendere Slide " + (current + 1) + " …");
@@ -6715,7 +6791,8 @@ if (location.search.indexOf("nur=anim") >= 0)
const n = SLIDES.length; const n = SLIDES.length;
const kopf = document.getElementById("kopfzeile"); const kopf = document.getElementById("kopfzeile");
if (kopf) kopf.textContent = if (kopf) kopf.textContent =
(n === 1 ? "Einzelbild" : "Carousel, " + n + " Slides") + " · 1080 × 1350 px"; (n === 1 ? "Einzelbild" : "Carousel, " + n + " Slides") + " · 1080 × " + SLIDE_H +
" px · " + FORMATE[format].name;
const zip = document.getElementById("dl-all"); const zip = document.getElementById("dl-all");
if (zip) { if (zip) {
zip.textContent = "Alle " + n + " Slides als ZIP"; zip.textContent = "Alle " + n + " Slides als ZIP";
@@ -7264,8 +7341,8 @@ if (location.search.indexOf("nur=anim") >= 0)
} }
if (senk) { if (senk) {
const ziel = senk === "oben" ? RAND const ziel = senk === "oben" ? RAND
: senk === "mitte" ? (1350 - h) / 2 : senk === "mitte" ? (SLIDE_H - h) / 2
: 1350 - RAND - h; : SLIDE_H - RAND - h;
set.dy = Math.round((set.dy || 0) + (ziel - o)); set.dy = Math.round((set.dy || 0) + (ziel - o));
} }
applyEditsAll(); applyEditsAll();
@@ -7740,7 +7817,7 @@ if (location.search.indexOf("nur=anim") >= 0)
html += "</table>"; html += "</table>";
} }
html += '<h4>Fest</h4><table>' + html += '<h4>Fest</h4><table>' +
'<tr><td>Slide</td><td>1080 × 1350</td></tr>' + '<tr><td>Slide</td><td>1080 × 1350 (4:5) oder 1080 × 1080 (1:1)</td></tr>' +
'<tr><td>Innenabstand</td><td>92 px</td></tr>' + '<tr><td>Innenabstand</td><td>92 px</td></tr>' +
'<tr><td>Tag ausgerückt</td><td>−20 px</td></tr>' + '<tr><td>Tag ausgerückt</td><td>−20 px</td></tr>' +
'<tr><td>Mockup</td><td>1090 px · 16:10</td></tr>' + '<tr><td>Mockup</td><td>1090 px · 16:10</td></tr>' +
@@ -7882,6 +7959,25 @@ if (location.search.indexOf("nur=anim") >= 0)
if (b) { tilt = b.dataset.t; press("tilts", "t", tilt); applyAll(); saveState(); } if (b) { tilt = b.dataset.t; press("tilts", "t", tilt); applyAll(); saveState(); }
}); });
/* Formatwechsel: Hoehe setzen, alle Slides und das Bewegtbild neu bauen.
Die Karten bekommen dabei ihre Masse fuer das Format (visMass). */
document.getElementById("formate").addEventListener("click", e => {
const b = e.target.closest("button");
if (!b || !FORMATE[b.dataset.g] || b.dataset.g === format) return;
format = b.dataset.g;
formatAnwenden();
press("formate", "g", format);
picked = null;
deckNeuBauen();
anzahlNachziehen();
animLeisteBauen();
zoom = null;
show(current);
syncEditPanel();
saveState();
setStatus("Format " + FORMATE[format].name + " · 1080 × " + SLIDE_H + " px", "ok");
});
document.getElementById("quality").addEventListener("click", e => { document.getElementById("quality").addEventListener("click", e => {
const b = e.target.closest("button"); const b = e.target.closest("button");
if (b) { superSample = Number(b.dataset.q); press("quality", "q", b.dataset.q); saveState(); } if (b) { superSample = Number(b.dataset.q); press("quality", "q", b.dataset.q); saveState(); }
@@ -7958,6 +8054,11 @@ if (location.search.indexOf("nur=anim") >= 0)
await bilderNachladen(); await bilderNachladen();
await preloadAssets(); await preloadAssets();
const restored = await loadState(); const restored = await loadState();
format = FORMAT_URL || format;
formatAnwenden();
/* Der Rendermodus fuer animation.mjs darf den Stand nicht anfassen:
Mit ?format=1x1 wuerde er sonst das Format in die Datei schreiben. */
if (document.documentElement.dataset.nur === "anim") stateLocked = true;
/* Verweise auf Bilder, die es nicht mehr gibt, hier aufloesen statt sie /* Verweise auf Bilder, die es nicht mehr gibt, hier aufloesen statt sie
bis in den Export zu schleppen. Sie entstehen, wenn eine importierte bis in den Export zu schleppen. Sie entstehen, wenn eine importierte
Datei geloescht oder nicht mitgepusht wurde. */ Datei geloescht oder nicht mitgepusht wurde. */
@@ -7982,6 +8083,7 @@ if (location.search.indexOf("nur=anim") >= 0)
press("tagouts", "o", tagOut); press("tagouts", "o", tagOut);
press("frames", "f", frameStyle); press("frames", "f", frameStyle);
press("views", "w", view); press("views", "w", view);
press("formate", "g", format);
anzahlNachziehen(); anzahlNachziehen();
show(current); show(current);
animLeisteBauen(); animLeisteBauen();
@@ -7993,7 +8095,7 @@ if (location.search.indexOf("nur=anim") >= 0)
fontEmbedCss = await htmlToImage.getFontEmbedCSS(deck.querySelector(".slide")); fontEmbedCss = await htmlToImage.getFontEmbedCSS(deck.querySelector(".slide"));
["dl-png", "dl-all"].forEach(id => document.getElementById(id).disabled = false); ["dl-png", "dl-all"].forEach(id => document.getElementById(id).disabled = false);
if (checkCollisions()) setStatus("Bereit. " + SLIDES.length + if (checkCollisions()) setStatus("Bereit. " + SLIDES.length +
(SLIDES.length === 1 ? " Slide" : " Slides") + ", 1080 × 1350 px", "ok"); (SLIDES.length === 1 ? " Slide" : " Slides") + ", 1080 × " + SLIDE_H + " px", "ok");
/* Ganz zum Schluss: der Rendermodus raeumt die Seite leer, und alles, /* Ganz zum Schluss: der Rendermodus raeumt die Seite leer, und alles,
was danach noch ein Element sucht, faende keines mehr. Ein Aufruf was danach noch ein Element sucht, faende keines mehr. Ein Aufruf
weiter oben hat genau das ausgeloest -- "Cannot set properties of weiter oben hat genau das ausgeloest -- "Cannot set properties of
+10
View File
@@ -482,6 +482,16 @@ behalten die Originalauflösung, alles außerhalb wird beschnitten.
im Grid fehlen links und rechts je ~33 px. Für die drei angepinnten Cover im Grid fehlen links und rechts je ~33 px. Für die drei angepinnten Cover
relevant, im Feed nicht relevant, im Feed nicht
**1080 × 1080 px (1:1) für Anzeigen mit Video im Carousel.** Ein normaler
Beitrag darf ein Video im 4:5-Carousel haben. Als Anzeige nicht: Meta schreibt
im Ads Guide für den Instagram-Feed „Derzeit wird bei Carousels, die Videos
enthalten, nur das Format 1:1 unterstützt.“ Reine Bild-Carousels dürfen 4:5
bleiben. In Reels laufen nur Bild-Carousels (9:16). Abgerufen 02.11.2026.
Post 9 hat dafür den Schalter „Format“ (4:5 · Beitrag, 1:1 · Anzeige), die
Karten tragen ihre 1:1-Maße unter `quadrat`, und
`node animation.mjs post9.html animWebinar 1x1` baut das Video in 1:1
(Datei mit `-1x1` im Namen).
### Layout-Template: Tag, Überschrift, Beschreibung, Bild ### Layout-Template: Tag, Überschrift, Beschreibung, Bild
Jede Feature-Slide besteht aus denselben vier Bausteinen in derselben Reihenfolge. Jede Feature-Slide besteht aus denselben vier Bausteinen in derselben Reihenfolge.
+1
View File
@@ -16,6 +16,7 @@
"edits": [ "edits": [
{} {}
], ],
"format": "1x1",
"frameStyle": "chamfer", "frameStyle": "chamfer",
"layoutState": [ "layoutState": [
"standard" "standard"
+11 -2
View File
@@ -112,7 +112,12 @@
<button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button> <button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button>
<button type="button" data-gehe="./post7.html">Post 7 · Form Builder</button> <button type="button" data-gehe="./post7.html">Post 7 · Form Builder</button>
<button type="button" data-gehe="./post8.html">Post 8 · E-Mail-Marketing</button> <button type="button" data-gehe="./post8.html">Post 8 · E-Mail-Marketing</button>
<button type="button" data-gehe="./post9.html">Post 9 · Webinar</button> <button type="button" data-gehe="./post9.html">Post 9 · Variante 1 · Webinar-Fenster</button>
<button type="button" data-gehe="./post9-v2.html">Post 9 · Variante 2 · Kundengeschichte</button>
<button type="button" data-gehe="./post9-v3.html">Post 9 · Variante 3 · Kundengeschichte dunkel</button>
<button type="button" data-gehe="./post9-v4.html">Post 9 · Variante 4 · Zahlen</button>
<button type="button" data-gehe="./post9-v5.html">Post 9 · Variante 5 · Zahlen dunkel</button>
<button type="button" data-gehe="./post9-varianten.html">Post 9 · Varianten im Vergleich</button>
<hr> <hr>
<button type="button" data-klick="neu">Streifen neu laden</button> <button type="button" data-klick="neu">Streifen neu laden</button>
</div> </div>
@@ -142,7 +147,11 @@
{ datei: "./post6.html", name: "Post 6 · Team-Chat", theme: "modern" }, { datei: "./post6.html", name: "Post 6 · Team-Chat", theme: "modern" },
{ datei: "./post7.html", name: "Post 7 · Form Builder", theme: "modern" }, { datei: "./post7.html", name: "Post 7 · Form Builder", theme: "modern" },
{ datei: "./post8.html", name: "Post 8 · E-Mail-Marketing", theme: "modern" }, { datei: "./post8.html", name: "Post 8 · E-Mail-Marketing", theme: "modern" },
{ datei: "./post9.html", name: "Post 9 · Webinar", theme: "modern" }, { datei: "./post9.html", name: "Post 9 · Variante 1 · Webinar-Fenster", theme: "modern" },
{ datei: "./post9-v2.html", name: "Post 9 · Variante 2 · Kundengeschichte", theme: "modern" },
{ datei: "./post9-v3.html", name: "Post 9 · Variante 3 · Kundengeschichte dunkel", theme: "modern" },
{ datei: "./post9-v4.html", name: "Post 9 · Variante 4 · Zahlen", theme: "modern" },
{ datei: "./post9-v5.html", name: "Post 9 · Variante 5 · Zahlen dunkel", theme: "modern" },
]; ];
const flaeche = document.getElementById("flaeche"); const flaeche = document.getElementById("flaeche");