Animation: eigener Bereich unter dem Canvas, getrennt vom Bildexport

Jedes Projekt hat jetzt unter dem Canvas einen Bereich "Animation", im selben
Fenster. Er zeigt jede Animation als echtes Blatt in Vorschau, mit Zeitregler,
Abspielen und "MP4 exportieren".

Eine Animation ist keine Slide. Sie steht in ANIMS und nicht in SLIDES, liegt
also nicht im Streifen, taucht in der Slide-Auswahl nicht auf und wird vom
Bildexport nicht angefasst. Im Carousel ist sie ein eigenes Element, und ob
sie mitkommt, entscheidet sich beim Posten. Ein erster Anlauf machte sie zur
sechsten Slide; damit war sie in der Zaehlung und im ZIP mit drin.

Der Export laeuft ueber den neuen Endpunkt POST /anim in serve.py. Er startet
animation.mjs in einem eigenen Faden, die Seite fragt den Stand ueber
GET /anim?id= ab und zeigt Fortschritt und Pfad. Ein Browser kann kein MP4
schreiben, und eine offene Anfrage ueber zwei Minuten geht nicht -- zumal sich
animation.mjs waehrenddessen selbst Seiten von diesem Server holt.

Aufgenommen wird im neuen Modus ?nur=anim&k=<key>: dort zeigt die Seite allein
das Blatt der Animation, in 1080 x 1350 an 0,0. Damit entfaellt jede
Ausschnittsrechnung und mit ihr die Falle, dass ein zu schmales Fenster ein
leeres Bild liefert.

Als erstes Beispiel post6-animChat, fuenf Sekunden: die Antwort wird Zeichen
fuer Zeichen getippt, abgeschickt, waechst als Blase in den Verlauf, die
Aufgabenkarte klappt auf, die Systemzeile bestaetigt, alles blendet zurueck
auf den Anfang. Getippt wird VERLAUF[3].text, also die Nachricht, die im
Werkzeug an dieser Stelle ohnehin folgt.

H.264 High, yuv420p, 1080 x 1350, 30 fps, stumme AAC-Spur, +faststart: das
nimmt Instagram fuer ein animiertes Element im Carousel an. GIF nicht, das
landet als Standbild. Ein Video im Carousel wird auch kein Reel, das passiert
nur bei einem einzeln geposteten Video.

Nebenbei:
- post4, post5 und post6 trugen im Panel noch den Kopf ihrer Vorlage.
- video/README.md und video/bauen.mjs behaupteten, ffmpeg sei hier nicht
  installiert. Stimmt seit 9.0.1 nicht mehr; die Bildsequenz bleibt dort
  trotzdem, mit Begruendung.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
dejanandClaude Opus 5 committed 2026-08-25 17:58:29 +02:00
1 parent ce87a7fb57
commit c7c05a85cc
14 files changed
+3295 -247

No files matched your search

+117 -10
View File
@@ -221,10 +221,10 @@ Baustellenfotos.
| Slide | Was sie leistet |
|---|---|
| 1 | Kanalliste und Verlauf nebeneinander, mit Filterzeile und Eingabefeld |
| 2 | die Chatliste groß: Projektkanäle, Direktnachrichten, Gruppe |
| 2 | links das Durcheinander als Zeichnung, rechts die Kanalliste |
| 3 | Fotos, die Aufgabe daraus, die Bestätigung im Projekt |
| 4 | das Menü hinter dem Plus im Eingabefeld |
| 5 | das Handy-Mockup der Website, verwischt, mit Absperrband |
| 5 | zwei Ausschnitte der Oberfläche, versetzt übereinander, mit Absperrband |
**Slide 3 ist der Kern**, und sie ist keine Konstruktion: im Werkzeug folgen
die drei Nachrichten genau so aufeinander. Tobias schickt die Baustellenfotos,
@@ -232,13 +232,35 @@ darauf eine Nachricht mit angehängter Aufgabe, darauf die Systemzeile
„Aufgabe „Materialliste prüfen" im Projekt erstellt". Die letzte Zeile macht
den Unterschied: die Aufgabe liegt danach im Projekt, nicht nur im Verlauf.
**Slide 5 kommt von der Website, nicht aus dem Pitch-Paket.** Die App gibt es
noch nicht, und `ChatDemoSection.tsx` hat den Zustand bereits: ein Handy-Mockup
mit `blur(3px)` und darüber ein **Absperrband** aus dem Bau, -13° gedreht, in
`#F1B729` mit Kanten in `#171106`, darauf Schutzhelm und „BALD VERFÜGBAR".
Auch Inhalt, Blasenfarben, Überschrift und Fließtext sind die der Website. Ein
Zwischenstand hatte hier einen selbst erfundenen Aufkleber; das war eine eigene
Bildsprache neben einer vorhandenen.
**Slide 5 zeigt zwei Ausschnitte statt eines Geräts.** Die Machart ist die,
die Signal für solche Slides benutzt: zwei Teile der Oberfläche,
versetzt übereinander, jeder mit eigenem Schatten. Ein Zwischenstand hatte hier
ein nachgebautes Handy samt Insel und Tasten; das kostet viel Platz für etwas,
das nichts über das Produkt sagt, und der Rahmen war frei erfunden.
**Slide 2 stellt gegenüber statt zu zeigen:** links das Durcheinander als
Zeichnung, rechts die Oberfläche als Ausschnitt. Die linke Hälfte gibt es in
diesem Repo schon, in `post2.html` auf Slide 2 (`visProzesse`) — dort im
Zeichenblock-Stil, hier glatt. Die Plättchen sind Redaktionstext und keine
Produktstrings; sie beschreiben die Wirklichkeit im Betrieb.
**Die Leitung läuft über den Wisch hinweg:** sie beginnt links außerhalb der
Slide, und mit `visLeft: -102` landet dieses Stück auf Slide 1. Das ist der
Überstand, vor dem `rules.md` §5 sonst warnt — `.page` hat kein `overflow` —
hier aber gewollt. `pruef.mjs` liest einen negativen `--vis-left` als Ansage
und meldet ihn nicht mehr als Fehler.
Beide Ausschnitte auf Slide 5 sind leicht verwischt: erkennbar, aber nicht zu lesen.
Scharf bleibt allein das Band. Von Slack kommt ein zweites Mittel, das auf
Slide 3 sitzt: **ein dicker gelber Rahmen, der seitlich aus der Blase
ausbricht** und auf die Anhangkarte der Aufgabe zeigt, weil die den Satz der
Slide trägt.
Dass die App noch nicht da ist, sagt das **Absperrband** von der Website:
`ChatDemoSection.tsx` hat es unter der Fahne `comingSoon`, -13° gedreht, in
`#F1B729` mit Kanten in `#171106`, darauf Schutzhelm und „BALD VERFÜGBAR". Auch
Überschrift und Fließtext sind die der Website; der Inhalt der Ausschnitte
kommt aus dem Pitch-Paket wie auf allen anderen Slides.
**Die Tiefe kommt aus dem Stapel und gestaffelten Schatten, nicht aus
Flächen.** Ein Zwischenstand legte graue Flächen hinter die Elemente; das nimmt
@@ -339,6 +361,89 @@ sonst sieht ihn niemand sonst. Gibt es die Datei noch nicht, wird der Stand
aus dem Browser einmalig hineingeschrieben.
## Animation
Unter dem Canvas liegt in **jedem** Projekt, im selben Fenster, ein Bereich
„Animation". Er zeigt jede Animation als eigenes Blatt in Vorschau, mit
Zeitregler, `Abspielen` und `MP4 exportieren`.
**Eine Animation ist keine Slide.** Sie steht in `ANIMS` und nicht in
`SLIDES`, liegt also nicht im Streifen, taucht in der Slide-Auswahl nicht auf
und wird vom Bildexport nicht angefasst. Das ist der ganze Punkt: im Carousel
ist sie ein eigenes Element, und ob sie mitkommt, entscheidest du beim Posten.
```js
const ANIMS = [
{ key: "animChat", type: "feature", dauer: 5,
eyebrow: "Bewegtbild",
head: 'Getippt,<br><span class="a">und schon im Projekt.</span>',
body: "Eine Nachricht, ein Klick, und die Aufgabe hängt am richtigen Projekt.",
vis: "animChat", visW: 700, visLeft: 190, visBottom: 120 },
];
```
Dieselben Felder wie bei einer Slide, dazu `dauer` in Sekunden. Die
Zeichenfunktion aus `VIS` bekommt einen zweiten Wert `t` zwischen 0 und 1 und
zeichnet daraus den Stand zu diesem Zeitpunkt. Blatt, Überschrift,
Punktgrund und Schutzzone sind dieselben wie überall.
### Exportieren
**`MP4 exportieren`** im Bereich unter dem Canvas. Der Browser kann kein MP4
schreiben, also stößt die Seite den Bau auf dem Server an (`POST /anim`) und
fragt den Stand ab; die Zeile unter den Knöpfen zeigt den Fortschritt und am
Ende den Pfad. Getrennt vom Bildexport, in beide Richtungen: „Alle Slides als
ZIP" enthält kein Video, und dieser Knopf rendert kein Bild.
Auf der Kommandozeile geht dasselbe:
```sh
./serve.sh # muss laufen
node animation.mjs post6.html # alle Animationen des Projekts
node animation.mjs post6.html animChat # nur diese
```
Heraus kommt `animation/post6-animChat.mp4`. Angesprochen wird die Animation
über ihren Schlüssel, nicht über eine Nummer: sie hat keine Position im
Streifen, die sich verschieben könnte.
Für die Aufnahme lädt das Skript die Seite als `?nur=anim&k=<key>`. In diesem
Modus zeigt sie allein das Blatt der Animation, in natürlicher Größe an 0,0 —
damit entfällt jede Ausschnittsrechnung.
### Was Instagram bei so einem Beitrag tatsächlich annimmt
Kurz: **ein MP4, kein GIF.**
| | |
|---|---|
| Format | MP4, H.264 High, `yuv420p`, `+faststart` |
| Maße | 1080 × 1350 (4:5), wie die Standbilder |
| Bildrate | 30 fps, Schlüsselbild jede Sekunde |
| Datenrate | rund 12 Mbit/s |
| Länge | mindestens 3 s, höchstens 60 s je Carousel-Element |
| Ton | eine stumme AAC-Spur |
Vier Dinge, die den Aufbau bestimmen:
- **GIF geht nicht.** Instagram nimmt keine GIF-Uploads an; die Datei landet
als Standbild. Was in der App wie ein animierter Beitrag aussieht, ist ein
kurzes Video.
- **Ein Video im Carousel wird kein Reel.** Seit 2022 wandert nur ein
*einzeln* geposteter Video-Beitrag automatisch in die Reels. Liegt das Video
in einem Carousel, bleibt der Beitrag ein Beitrag.
- **Der erste Beitrag im Carousel bestimmt den Ausschnitt für alle.** Ein
Element in einem anderen Seitenverhältnis wird beschnitten, nicht
eingepasst. Deshalb rendert die Animation in genau denselben 1080 × 1350 wie
jedes Standbild.
- **Eine stumme Tonspur muss mit.** Manche Upload-Wege lehnen ein Video ohne
Audiostrom ab. Die Spur kostet 128 kbit/s und erspart die Fehlersuche.
Die Bewegung läuft in Schleife. Das letzte Bild ist deshalb **nicht** dasselbe
wie das erste (`t = n / anzahl`, nicht `n / (anzahl − 1)`) — sonst steht der
Stoß eine Dreißigstelsekunde still.
## Bedienung
| Taste | Wirkung |
@@ -400,7 +505,9 @@ assets/
└── fonts/ Manrope 400–800, lokal
vendor/ html-to-image, JSZip
state/ Projektstände, gehören ins Repo
serve.py Statischer Server + /save, /state, /upload, /bilder
animation.mjs baut aus einem ANIMS-Eintrag ein MP4 (braucht ffmpeg)
animation/ die fertigen MP4s, ein Carousel-Element je Datei
serve.py Statischer Server + /save, /state, /upload, /bilder, /anim
rules.md Gestaltungsregeln, Farbwerte, Export-Wissen
```