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 5 nur Slide 6 (nullbasiert: 5)
* node animation.mjs post9.html animWebinar 1x1 im Format 1:1
*
* WARUM MP4 UND NICHT GIF
* Instagram nimmt kein GIF an. Was im Feed als "kurzer animierter Beitrag"
@@ -14,7 +15,11 @@
* DIE WERTE
* Behaelter MP4, H.264 High, yuv420p
* 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
* Laenge 3 s ist das Minimum, 60 s je Element das Maximum; wir liegen
* 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
verschieben koennte. Ohne Angabe werden alle des Projekts gebaut. */
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 CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
const PORT = 9530;
const FPS = 30;
const BREITE = 1080, HOEHE = 1350;
const BREITE = 1080;
let HOEHE = 1350, DATEI_ZUSATZ = "";
const DATENRATE = "12M";
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
// 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");
/* 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)");
const liste = alle.filter(a => NUR == null || a.key === NUR);
if (!liste.length) throw new Error("keine Animation" +
@@ -150,7 +167,7 @@ try {
for (const a of liste) {
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
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.");
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++) {
/* 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. */
@@ -198,7 +215,7 @@ try {
Buffer.from(r.data, "base64"));
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 ");
await kodieren(ordner, datei, anzahl);
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="./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="./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>
<button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button>
<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="./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="./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>
<button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button>
<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="./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="./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>
<button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button>
<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="./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="./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>
<button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button>
<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="./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="./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>
<button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button>
<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="./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="./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>
<button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button>
<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="./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="./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>
<button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button>
<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="./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="./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>
<button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button>
<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="./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="./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>
<button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button>
<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>
<meta charset="utf-8">
<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">
<style>
/* ─── 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:focus { outline: none; border-color: var(--accent-yellow); }
#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; }
.std { margin-bottom: 22px; }
.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.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 {
width: 1080px; height: 1350px;
width: 1080px; height: var(--slide-h, 1350px);
position: relative; overflow: hidden;
font-family: 'Manrope', system-ui, sans-serif;
}
@@ -533,7 +538,7 @@ if (location.search.indexOf("nur=anim") >= 0)
--animskala: .215;
flex: none; position: relative; overflow: hidden; border-radius: 6px;
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;
}
.animbuehne > .slide {
@@ -626,7 +631,7 @@ if (location.search.indexOf("nur=anim") >= 0)
left: calc(var(--slide-i, 0) * -1080px);
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
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="./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="./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>
<button type="button" data-gehe="./uebersicht.html">Alle Projekte im Streifen</button>
<hr>
@@ -1115,7 +1125,7 @@ if (location.search.indexOf("nur=anim") >= 0)
<div class="arbeitsflaeche">
<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>
<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>
</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">
<summary>Variante</summary>
<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>',
body: "Was Automatisierung in deinem Betrieb wirklich macht. Mit dem Branchenprogramm, das du schon hast.",
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
Abstand zur Ueberschrift soll derselbe bleiben, rund 118 px. */
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 += uiText(WB_B / 2, y + 42, "Jetzt anmelden", 22, 800, "#FFFFFF", { anker: "middle" });
y += 68 + 36;
g += uiText(WB_B / 2, y, "Link in der Bio", 18, 600, CHAT_TEXT3, { anker: "middle" });
y += 6;
y += 68;
/* „Link in der Bio“ nur im Beitrag. Die Anzeige (1:1) traegt ihren
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" });
}
@@ -4961,7 +4996,7 @@ if (location.search.indexOf("nur=anim") >= 0)
if (key === "mock") return SLIDES[i].mockW || 1090;
if (key === "mock-2") return (SLIDES[i].media2 || {}).w || 400;
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;
}
@@ -4996,6 +5031,36 @@ if (location.search.indexOf("nur=anim") >= 0)
Textblock darueber steht dann sichtbar aus der Achse. */
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 ────────────────────────────────────────────────────────
Alles, was am Projekt hängt, wird gespeichert: Einstellungen, Layouts,
Muster und die Verschiebungen im Canvas. Das Werkzeug soll den Stand
@@ -5021,7 +5086,7 @@ if (location.search.indexOf("nur=anim") >= 0)
function currentState() {
return {
v: STATE_V, variant, tilt, superSample, view, tagOut, frameStyle, bandTon,
rasterAn, textMitte,
rasterAn, textMitte, format,
current, layoutState, bgState, edits,
// Welche Slides der Post ueberhaupt hat. Ohne das waere eine selbst
// angelegte Slide nach dem Neuladen weg.
@@ -5169,6 +5234,7 @@ if (location.search.indexOf("nur=anim") >= 0)
press("rasters", "r", rasterAn);
press("mitten", "m", textMitte);
press("views", "w", view);
press("formate", "g", format);
// Nach einem Schritt, der Slides hinzugefuegt, geloescht oder umsortiert
// hat, stimmt der Aufbau im Canvas nicht mehr mit dem Stand ueberein.
if (deck.querySelectorAll(".holder").length !== SLIDES.length) {
@@ -5180,6 +5246,8 @@ if (location.search.indexOf("nur=anim") >= 0)
applyEditsAll();
}
anzahlNachziehen();
animLeisteBauen();
fitStrip();
show(current);
if (picked) pick(picked.slide, picked.key); else syncEditPanel();
} finally { restoring = false; }
@@ -5305,6 +5373,8 @@ if (location.search.indexOf("nur=anim") >= 0)
bandTon = d.bandTon || bandTon;
rasterAn = d.rasterAn || rasterAn;
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;
}
@@ -5350,7 +5420,7 @@ if (location.search.indexOf("nur=anim") >= 0)
const scroller = document.getElementById("scroller");
const availW = Math.max(320, scroller.clientWidth - 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
@@ -5359,7 +5429,7 @@ if (location.search.indexOf("nur=anim") >= 0)
Interna der Post-Seite unabhaengig. */
function groesseMelden() {
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 }, "*");
}
@@ -5368,7 +5438,7 @@ if (location.search.indexOf("nur=anim") >= 0)
const k = zoom || autoFit();
deck.style.transform = "scale(" + k + ")";
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");
if (lvl) lvl.textContent = zoom ? Math.round(k * 100) + " %" : "Fit";
groesseMelden();
@@ -5490,7 +5560,7 @@ if (location.search.indexOf("nur=anim") >= 0)
rechten Kante der vorletzten Slide -- zwei Pixel innerhalb der Naht,
weil genau auf der Kante ein Antialiasing-Splitter auf der
Nachbarslide stehen bleibt. */
const H = 1350;
const H = SLIDE_H;
const A0 = BAND_VON * 1080 + 2; // Anker der Kurve
const A1 = Math.max(A0, (SLIDES.length - BAND_BIS) * 1080 - 2);
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
ankommt. Mit || waere die 0 als fehlender Wert durchgegangen und die
Flaeche haette weiter bei 92 gesessen. */
const m = visMass(sl);
v.style.cssText =
"--vis-w:" + (sl.visW ?? 900) + "px;--vis-left:" + (sl.visLeft ?? 92) +
"px;--vis-bottom:" + (sl.visBottom ?? 96) + "px";
"--vis-w:" + (m.visW ?? 900) + "px;--vis-left:" + (m.visLeft ?? 92) +
"px;--vis-bottom:" + (m.visBottom ?? 96) + "px";
v.innerHTML = VIS[sl.vis](j, animT(j));
page.appendChild(v);
}
@@ -5817,9 +5888,10 @@ if (location.search.indexOf("nur=anim") >= 0)
const v = document.createElement("div");
v.className = "vis vis-" + a.vis;
const m = visMass(a);
v.style.cssText =
"--vis-w:" + (a.visW ?? 900) + "px;--vis-left:" + (a.visLeft ?? 92) +
"px;--vis-bottom:" + (a.visBottom ?? 96) + "px";
"--vis-w:" + (m.visW ?? 900) + "px;--vis-left:" + (m.visLeft ?? 92) +
"px;--vis-bottom:" + (m.visBottom ?? 96) + "px";
v.innerHTML = VIS[a.vis](i, animT(i));
page.appendChild(v);
@@ -5886,6 +5958,9 @@ if (location.search.indexOf("nur=anim") >= 0)
zeile.textContent = "Chrome startet …";
zeile.dataset.ton = "";
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", {
method: "POST",
headers: { "Content-Type": "application/json" },
@@ -5953,7 +6028,7 @@ if (location.search.indexOf("nur=anim") >= 0)
kopf.innerHTML = ANIM_PFEIL + "<b>Animation</b><span>" +
(liste.length
? 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>";
leiste.innerHTML = "";
leiste.appendChild(kopf);
@@ -5984,7 +6059,7 @@ if (location.search.indexOf("nur=anim") >= 0)
rechts.innerHTML =
'<div class="animtitel">' + a.titel + '</div>' +
'<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" ' +
'data-animschieber="' + a.i + '" aria-label="Zeitpunkt">' +
'<div class="animknoepfe">' +
@@ -5992,7 +6067,7 @@ if (location.search.indexOf("nur=anim") >= 0)
'<button type="button" class="animexport">MP4 exportieren</button>' +
"</div>" +
'<p class="animzeile"><code>node animation.mjs ' + PROJECT + ".html " +
a.key + "</code></p>";
a.key + (format === "4x5" ? "" : " " + format) + "</code></p>";
karte.appendChild(rechts);
reihe.appendChild(karte);
@@ -6256,12 +6331,12 @@ if (location.search.indexOf("nur=anim") >= 0)
const k = superSample;
return {
width: 1080 * k,
height: 1350 * k,
height: SLIDE_H * k,
pixelRatio: 1,
fontEmbedCSS: fontEmbedCss,
style: {
width: "1080px",
height: "1350px",
height: SLIDE_H + "px",
transform: k === 1 ? "none" : "scale(" + k + ")",
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.
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
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
// 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() {
setStatus("Rendere Slide " + (current + 1) + " …");
@@ -6715,7 +6791,8 @@ if (location.search.indexOf("nur=anim") >= 0)
const n = SLIDES.length;
const kopf = document.getElementById("kopfzeile");
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");
if (zip) {
zip.textContent = "Alle " + n + " Slides als ZIP";
@@ -7264,8 +7341,8 @@ if (location.search.indexOf("nur=anim") >= 0)
}
if (senk) {
const ziel = senk === "oben" ? RAND
: senk === "mitte" ? (1350 - h) / 2
: 1350 - RAND - h;
: senk === "mitte" ? (SLIDE_H - h) / 2
: SLIDE_H - RAND - h;
set.dy = Math.round((set.dy || 0) + (ziel - o));
}
applyEditsAll();
@@ -7740,7 +7817,7 @@ if (location.search.indexOf("nur=anim") >= 0)
html += "</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>Tag ausgerückt</td><td>−20 px</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(); }
});
/* 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 => {
const b = e.target.closest("button");
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 preloadAssets();
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
bis in den Export zu schleppen. Sie entstehen, wenn eine importierte
Datei geloescht oder nicht mitgepusht wurde. */
@@ -7982,6 +8083,7 @@ if (location.search.indexOf("nur=anim") >= 0)
press("tagouts", "o", tagOut);
press("frames", "f", frameStyle);
press("views", "w", view);
press("formate", "g", format);
anzahlNachziehen();
show(current);
animLeisteBauen();
@@ -7993,7 +8095,7 @@ if (location.search.indexOf("nur=anim") >= 0)
fontEmbedCss = await htmlToImage.getFontEmbedCSS(deck.querySelector(".slide"));
["dl-png", "dl-all"].forEach(id => document.getElementById(id).disabled = false);
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,
was danach noch ein Element sucht, faende keines mehr. Ein Aufruf
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
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
Jede Feature-Slide besteht aus denselben vier Bausteinen in derselben Reihenfolge.
+1
View File
@@ -16,6 +16,7 @@
"edits": [
{}
],
"format": "1x1",
"frameStyle": "chamfer",
"layoutState": [
"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="./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="./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>
<button type="button" data-klick="neu">Streifen neu laden</button>
</div>
@@ -142,7 +147,11 @@
{ datei: "./post6.html", name: "Post 6 · Team-Chat", theme: "modern" },
{ datei: "./post7.html", name: "Post 7 · Form Builder", 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");