- Variante 2/3: Webinar statt HiveGrid, Noahs Satz zum Kopf des Chefs, vorher und heute am Beispiel „Angebot nachfassen“ als Schema - Variante 4/5: drei verschiedene Grafiken, Wabenanimation gelb dann rot, Titel mit Wabenwelle animiert - Variante 6/7: Aussagen, Zeichnungen und Schriften aus „Jede Frage lief über meinen Tisch“, Titel dunkel, Pfeile werden gezeichnet (Test) - post9-hub.mjs legt alle Varianten als Bilder und Videos ins Marketing-Hub - Menüs aller Posts und Übersicht mit Variante 6 und 7 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
112 lines
6.4 KiB
JavaScript
112 lines
6.4 KiB
JavaScript
// Legt alle Varianten von Post 9 ins Marketing-Hub, damit Noah sie auf
|
|
// hivegrid-marketing.pages.dev sieht (Dejan, 04.11.2026). Die Seiten in
|
|
// PostGrid laufen nur lokal, deshalb kommen dort Bilder und Videos an:
|
|
//
|
|
// jede Slide als PNG, 1080 x 1080, so wie der Streifen sie zeigt
|
|
// die animierten Slides als MP4 aus animation/
|
|
// varianten.json mit Reihenfolge und Texten fuer die Webinar-Seite
|
|
//
|
|
// Aufruf, wenn PostGrid laeuft (./serve.sh): node post9-hub.mjs
|
|
// Danach im Hub committen, pushen und ./veroeffentlichen.sh.
|
|
//
|
|
// Vorher die Animationen neu bauen, wenn sich an ihnen etwas geaendert hat:
|
|
// node animation.mjs post9.html animWebinar 1x1
|
|
// node animation.mjs post9-v4.html animBuerokratie 1x1 (ebenso animKi, v5)
|
|
import { spawn } from "node:child_process";
|
|
import { writeFile, mkdir, copyFile, rm, readdir } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
|
|
const HUB = path.resolve("../HiveGrid Marketing /webinar/anzeigen/post9");
|
|
const POSTGRID = "http://127.0.0.1:4321/";
|
|
|
|
/* Reihenfolge im Carousel. "bild:n" ist Slide n des Streifens, "video:x"
|
|
eine Datei aus animation/. In Variante 1 steht das Video vorn, in 4 und 5
|
|
ersetzen die Videos Slide 2 und 3 des Streifens (dort steht ihr Endstand). */
|
|
const VARIANTEN = [
|
|
{ nr: 1, datei: "post9.html", name: "Webinar-Fenster",
|
|
text: "Das Bewegtbild zeigt das Webinar mit seinen Teilen und beiden Sprechern, danach die Anmeldung.",
|
|
folge: ["video:post9-animWebinar-1x1.mp4", "bild:0"] },
|
|
{ nr: 2, datei: "post9-v2.html", name: "Kundengeschichte",
|
|
text: "Angelehnt an ein Karussell von weclapp. Noah als Gesicht, dann vorher und heute am Beispiel „Angebot nachfassen“, dann die Anmeldung.",
|
|
folge: ["bild:0", "bild:1", "bild:2", "bild:3", "bild:4"] },
|
|
{ nr: 3, datei: "post9-v3.html", name: "Kundengeschichte dunkel",
|
|
text: "Wie Variante 2, auf dunklem Grund.",
|
|
folge: ["bild:0", "bild:1", "bild:2", "bild:3", "bild:4"] },
|
|
{ nr: 4, datei: "post9-v4.html", name: "Zahlen",
|
|
text: "Angelehnt an HERO Insights. Noah als Teaser, dann drei Zahlen aus Studien zur Zeit im Handwerk, dann die Anmeldung. Slide 1 bis 3 sind kurze Videos.",
|
|
folge: ["video:post9-v4-animTitel-1x1.mp4", "video:post9-v4-animBuerokratie-1x1.mp4", "video:post9-v4-animKi-1x1.mp4", "bild:3", "bild:4"] },
|
|
{ nr: 5, datei: "post9-v5.html", name: "Zahlen dunkel",
|
|
text: "Wie Variante 4, auf dunklem Grund.",
|
|
folge: ["video:post9-v5-animTitel-1x1.mp4", "video:post9-v5-animBuerokratie-1x1.mp4", "video:post9-v5-animKi-1x1.mp4", "bild:3", "bild:4"] },
|
|
{ nr: 6, datei: "post9-v6.html", name: "Arbeitsheft",
|
|
text: "Aussagen und Zeichnungen aus Noahs Arbeitsheft „Jede Frage lief über meinen Tisch“, im Look des Hefts, der Titel dunkel wie der Umschlag. Vom Engpass über die Inseln und den Bewegungsmelder bis zu den zehn Handgriffen, dann die Anmeldung. Auf dem Titel werden die Pfeile gezeichnet (Test).",
|
|
folge: ["video:post9-v6-animTitel-1x1.mp4", "bild:1", "bild:2", "bild:3", "bild:4", "bild:5"] },
|
|
{ nr: 7, datei: "post9-v7.html", name: "Arbeitsheft dunkel",
|
|
text: "Wie Variante 6, auf dunklem Grund.",
|
|
folge: ["video:post9-v7-animTitel-1x1.mp4", "bild:1", "bild:2", "bild:3", "bild:4", "bild:5"] },
|
|
];
|
|
|
|
const PORT = 9542;
|
|
const profil = path.join(tmpdir(), "pg-hub");
|
|
const chrome = spawn("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
["--headless", "--disable-gpu", "--hide-scrollbars", "--remote-debugging-port=" + PORT,
|
|
"--user-data-dir=" + profil, "about:blank"], { stdio: "ignore" });
|
|
let ziele;
|
|
for (let i = 0; i < 60; i++) {
|
|
ziele = await fetch(`http://127.0.0.1:${PORT}/json/list`).then(r => r.json()).catch(() => null);
|
|
if (ziele && ziele.some(t => t.type === "page")) break;
|
|
await new Promise(r => setTimeout(r, 200));
|
|
}
|
|
const ws = new WebSocket(ziele.find(t => t.type === "page").webSocketDebuggerUrl);
|
|
const offen = new Map(); let n = 0;
|
|
ws.onmessage = e => { const m = JSON.parse(e.data); if (m.id && offen.has(m.id)) { offen.get(m.id)(m); offen.delete(m.id); } };
|
|
await new Promise(ok => ws.onopen = ok);
|
|
const ruf = (method, params = {}) => new Promise((ok, weg) => { const id = ++n;
|
|
offen.set(id, m => m.error ? weg(new Error(m.error.message)) : ok(m.result)); ws.send(JSON.stringify({ id, method, params })); });
|
|
const warte = ms => new Promise(r => setTimeout(r, ms));
|
|
|
|
await ruf("Page.enable"); await ruf("Runtime.enable");
|
|
await rm(HUB, { recursive: true, force: true });
|
|
await mkdir(HUB, { recursive: true });
|
|
|
|
const liste = [];
|
|
for (const v of VARIANTEN) {
|
|
/* Breit genug fuer jeden Streifen, danach auf die echte Breite stellen. */
|
|
await ruf("Emulation.setDeviceMetricsOverride", { width: 7560, height: 1080, deviceScaleFactor: 1, mobile: false });
|
|
await ruf("Page.navigate", { url: POSTGRID + v.datei + "?nur=streifen&format=1x1" });
|
|
await warte(4500);
|
|
const breite = (await ruf("Runtime.evaluate", { expression: "document.documentElement.scrollWidth", returnByValue: true })).result.value;
|
|
const anzahl = Math.round(breite / 1080);
|
|
const slides = [];
|
|
for (const [i, teil] of v.folge.entries()) {
|
|
const [art, wert] = teil.split(":");
|
|
const name = `v${v.nr}-${i + 1}` + (art === "video" ? ".mp4" : ".png");
|
|
if (art === "video") {
|
|
await copyFile(path.join("animation", wert), path.join(HUB, name));
|
|
} else {
|
|
const k = Number(wert);
|
|
if (k >= anzahl) throw new Error(`${v.datei}: Slide ${k + 1} fehlt im Streifen (${anzahl} Slides)`);
|
|
const bild = await ruf("Page.captureScreenshot", { format: "png",
|
|
clip: { x: k * 1080, y: 0, width: 1080, height: 1080, scale: 1 } });
|
|
await writeFile(path.join(HUB, name), Buffer.from(bild.data, "base64"));
|
|
}
|
|
slides.push({ art: art === "video" ? "video" : "bild", datei: name });
|
|
}
|
|
liste.push({ nr: v.nr, name: v.name, text: v.text, slides });
|
|
console.log(`Variante ${v.nr}: ${slides.length} Elemente`);
|
|
}
|
|
|
|
const heute = new Date().toISOString().slice(0, 10);
|
|
await writeFile(path.join(HUB, "varianten.json"), JSON.stringify({
|
|
titel: "Post 9 · Webinar-Anzeige",
|
|
stand: heute,
|
|
hinweis: "Format 1:1 für die Anzeige. Datum und Uhrzeit des Webinars sind noch Platzhalter.",
|
|
varianten: liste,
|
|
}, null, 1) + "\n");
|
|
console.log("Fertig:", (await readdir(HUB)).length, "Dateien in", HUB);
|
|
|
|
ws.close();
|
|
if (chrome.exitCode === null) { const zu = new Promise(ok => chrome.once("exit", ok)); chrome.kill(); await zu; }
|
|
await rm(profil, { recursive: true, force: true }).catch(() => {});
|