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

+245 -34
View File
@@ -8,21 +8,35 @@ Hell, Theme `modern`, Punktgrund, Text zentriert — dieselbe Anlage wie Post 4
und 5. Datei `post6.html`, Stand in `state/post6.json`, erreichbar über das
Projektmenü jeder Seite und über `uebersicht.html`.
**Fünf Slides.** DSGVO und deutsche Server hätten einen sechsten Punkt
ergeben, dafür gibt es aber keinen Bildschirm. Der steht in der
**Fünf Slides, dazu ein Bewegtbild.** DSGVO und deutsche Server hätten einen
weiteren Punkt ergeben, dafür gibt es aber keinen Bildschirm. Der steht in der
Bildunterschrift, wo eine Aussage ohne Bild hingehört.
Dazu kommt die erste Animation im Repo. Sie ist **keine Slide**: sie steht in
`ANIMS` statt in `SLIDES`, liegt nicht im Streifen und wird vom Bildexport
nicht angefasst. Im Carousel ist sie ein eigenes Element, das mitkommen kann
oder eben nicht. Bedient wird sie im Bereich unter dem Canvas, gebaut mit
einem eigenen Knopf. Wie das funktioniert, steht im
[README unter „Animation"](./README.md#animation); was diese eine zeigt, in
§7 weiter unten.
---
## 1. Die vier Slides
## 1. Die fünf Slides und das Bewegtbild
| # | Tag | Überschrift | Was zu sehen ist |
|---|---|---|---|
| 1 | Team-Chat | Büro und Baustelle, **endlich im selben Chat.** | Kanalliste und Verlauf nebeneinander, mit Filterzeile und Eingabefeld |
| 2 | Die Kanäle | Ein Kanal **pro Projekt.** | Die Chatliste groß: Projektkanäle, Direktnachrichten, Gruppe |
| 2 | Die Kanäle | Ein Kanal **pro Projekt.** | Links das Durcheinander als Zeichnung, rechts die Kanalliste; die Leitung beginnt schon auf Slide 1 |
| 3 | Die Aufgabe | Foto rein, **Aufgabe raus.** | Baustellenfotos, die Aufgabe daraus, die Bestätigung im Projekt |
| 4 | Das Pluszeichen | Nicht nur **Text.** | Das Menü hinter dem Plus im Eingabefeld |
| 5 | Bald verfügbar | Der Chat für unterwegs **wird gerade gebaut.** | Das Handy-Mockup der Website, verwischt, mit Absperrband |
| 4 | Das Pluszeichen | Nicht nur **Text.** | Das Menü hinter dem Plus, auf dem Stapel |
| 5 | Bald verfügbar | Der Chat für unterwegs **wird gerade gebaut.** | Zwei Ausschnitte der Oberfläche, versetzt übereinander, mit dem Absperrband darüber |
Dazu, außerhalb des Streifens:
| Schlüssel | Tag | Überschrift | Was zu sehen ist |
|---|---|---|---|
| `animChat` | Bewegtbild | Getippt, **und schon im Projekt.** | 5 s Animation: die Nachricht wird geschrieben, abgeschickt und landet mit ihrer Aufgabe im Verlauf |
**Slide 3 ist der Kern.** Der Redaktionsplan verlangt „Foto rein, Aufgabe
raus", und genau das liegt im Werkzeug als Abfolge vor: Tobias schickt die
@@ -95,6 +109,11 @@ dem Gewerk: Wärmepumpe Müller, Montagetermin, Baustellenfotos.
## 4. Wo bewusst abgewichen wird
**Slide 4 zeigt das Menü ohne das Eingabefeld, an dem es hängt.** Das Feld
stand einmal darunter, damit man sieht, woher das Menü kommt. Es ist weg: die
Slide zeigt, was man anhängen kann, und dafür braucht es das Feld nicht. Der
Stapel sagt ohnehin, dass hier ein Bildschirm liegt und kein Kasten im Nichts.
**Slide 1 zeigt drei von sechs Nachrichten, Slide 2 fünf von acht Kanälen.**
Was nicht ins Bild passt, fällt weg; ein Chat und eine Liste scrollen beide.
Slide 2 behält alle drei Sorten: Projektkanal, Direktnachricht, Gruppe.
@@ -111,50 +130,64 @@ Beitrag zu setzen, der von der eigenen Baustelle spricht, wäre eine Behauptung
| | |
|---|---|
| Slides | 5 |
| Slides | 5, dazu 1 Animation außerhalb des Streifens |
| Format | 1080 × 1350, Ausgabe zweifach: 2160 × 2700 |
| Luft zwischen Text und Zeichnung | 116 / 79 / 190 / 141 / 77 px (Grenze 40) |
| Luft zwischen Text und Zeichnung | 118 / 170 / 153 / 155 / 55 px, Animation 84 (Grenze 40) |
| Zeichnung ragt über die Seite | nein, alle innerhalb der 92-px-Schutzzone |
| Breiteste Überschriftzeile | 818 px von 896 |
| Stylesheet vollständig | 214 Regeln, `[data-edit]` vorhanden |
| Konsole | keine Fehler |
| Ausgabeweg | geprüft, `01-chat-hell.png` bis `05-handy-hell.png` |
| Überschneidungen | keine, `kollision.mjs` über alle Slides |
| Ausgabeweg | geprüft, `01-chat-hell.png` bis `05-mobil-hell.png` |
| Bewegtbild | `animation/post6-animChat.mp4`, ffprobe siehe §7 |
Geprüft wird mit den Hilfsskripten aus dem Sitzungsordner: `pruef.mjs` misst
über `getBBox()` die tatsächlich gezeichnete Fläche je Slide und die Breite
jeder Überschriftzeile, zählt die Regeln im Stylesheet und liest die Konsole
mit; `schuss.mjs` rendert einzelne Slides; `export.mjs` fährt den echten
Ausgabeweg und fängt `/save` ab.
Ausgabeweg und fängt `/save` ab; `leiste.mjs` prüft in jedem Projekt, ob die
Animationsleiste an der richtigen Stelle steht und ob die Seite fehlerfrei
anläuft.
---
## 5a. Slide 5 kommt von der Website, nicht von mir
**Das Gerät ist eine bewusste Abweichung.** Die Website zeichnet nur einen
hellen Umriss (`border: 4px solid #D1D5DB`). Auf einer Slide, die selbst hell
ist, fällt der in sich zusammen und sieht nach Platzhalter aus. Hier steht
deshalb ein richtiges Gerät: dunkler Korpus, heller Grat, Schirm eingelassen,
Tasten an der Seite. Die Abweichung betrifft den Rahmen, nicht den Inhalt. Der
Schirm wird auch schwächer verwischt als dort: auf der Website liegt das Handy
groß neben Fließtext und darf unlesbar sein, hier ist es klein, und bei voller
Stärke blieb nur Brei.
**Kein Gerät.** Vorbild ist die Machart, die Signal für genau solche Slides
benutzt: zwei Ausschnitte der Oberfläche, versetzt übereinander, jeder
mit eigenem Schatten. Man sieht zwei Zustände nebeneinander, ohne dass ein
Telefonrahmen die halbe Fläche frisst.
**Die App gibt es noch nicht, und die Website sagt das bereits.** In
Ein Zwischenstand hatte hier ein nachgebautes Handy samt Insel und Tasten,
ganz verwischt. Das kostet viel Platz für etwas, das nichts über das Produkt
sagt, und der Rahmen war ohnehin frei erfunden. Die Unschärfe ist damit auch
weg: das Band sagt deutlich genug, dass es die App noch nicht gibt, und die
Ausschnitte sollen lesbar sein.
Links die Kanalliste von Slide 2, rechts der Verlauf von Slide 1, beide schmal
gesetzt und am Rand angeschnitten. Gezeigt wird also nichts, was nicht vorher
schon zu sehen war. Beide sind **leicht verwischt** (`stdDeviation 2.2`):
erkennbar, aber nicht zu lesen. Scharf bleibt allein das Band, und genau das
soll es auch sein.
**Das Absperrband kommt von der Website.** In
`components/sections/ChatDemoSection.tsx` steht unter der Fahne `comingSoon`
genau dieser Zustand: ein Handy-Mockup, das mit `blur(3px) saturate(0.92)`
verwischt wird, und darüber ein **Absperrband** aus dem Bau, gedreht um
**-13°**, in `--accent-yellow` `#F1B729` mit Kanten in `#171106`, darauf immer
wieder Schutzhelm und **BALD VERFÜGBAR**.
genau dieses Element: ein **Absperrband** aus dem Bau, gedreht um **-13°**, in
`--accent-yellow` `#F1B729` mit Kanten in `#171106`, darauf immer wieder
Schutzhelm und **BALD VERFÜGBAR**. Es liegt über beiden Ausschnitten und ragt
beidseitig hinaus: es sperrt die ganze Sache ab, nicht eine Fläche darin.
Ein Zwischenstand hatte hier einen selbst erfundenen gelben Aufkleber
„In Arbeit". Das war eine eigene Bildsprache neben einer vorhandenen.
Übernommen ist jetzt die der Website, samt Winkel, Farben und Wortlaut.
Auch der Inhalt des Schirms kommt von dort und **nicht** aus dem Pitch-Paket:
Heizungsprojekt Schulz, „4 Mitglieder · 2 online", die vier Nachrichten aus
`seedMessages`, die Blasenfarben `#0D6E6E` für eigene und `#FFFFFF` mit Rand
`#E9E7E5` für fremde, der warme Grund `hsl(30 17% 95.5%)`. Auf der Website
steht dieses Handy für den mobilen Chat, also zeigt es hier dasselbe.
**Vom Absperrband abgesehen kommt der Schirm aus dem Pitch-Paket.** Eine
Zwischenfassung zeigte dort den Chat der Website (Heizungsprojekt Schulz,
`seedMessages`, Blasen in `#0D6E6E`). Der ist echt, gehört aber zu einer
anderen Oberfläche, und der Beitrag zeigt sonst durchgehend das Werkzeug.
Jetzt steht auf dem Schirm derselbe Verlauf wie auf Slide 1 und 3, gezeichnet
mit derselben `blase()`, nur schmaler. Man sieht es kaum, weil das Gerät
verwischt ist — das ist kein Grund, etwas anderes hinzulegen: was unter der
Unschärfe liegt, soll stimmen.
Überschrift und Fließtext sind ebenfalls die der Website: „Der Chat für
unterwegs wird gerade gebaut." und „Die mobile App legen wir gerade auf die
@@ -163,6 +196,83 @@ in Kürze." Der Em-Dash der Vorlage ist durch ein Komma ersetzt, `rules.md` §4.
---
## 5d. Slide 2 stellt gegenüber statt zu zeigen
Links das Durcheinander als Zeichnung, rechts die Oberfläche als Ausschnitt,
dazwischen ein Pfeil. Die Aussage steckt in der Gegenüberstellung und nicht in
einem der beiden Teile — der Satz der Website ist genau diese Zweiteilung:
„Schluss mit dem einen Verlauf, in dem alles durcheinandergeht. Ein Kanal pro
Projekt."
**Die linke Hälfte gibt es in diesem Repo schon**, in `post2.html` auf Slide 2
(`visProzesse`): beschriftete Plättchen, die eine geschlängelte gestrichelte
Linie verbindet. Dort ist sie im Zeichenblock-Stil mit schiefen Kanten, hier
glatt — das Theme ist ein anderes, und dieselbe Form zweimal in zwei
Anmutungen wäre schlechter als zwei Formen mit derselben Idee.
Die fünf Plättchen (Angebot, Baustellenfoto, Termin, Rückfrage, Materialliste)
sind **Redaktionstext und keine Produktstrings**. Sie beschreiben die
Wirklichkeit im Betrieb, nicht die Oberfläche; dieselbe Lage wie bei
„E-Mails" und „Zettel im Auto" in Post 2. Gewählt sind Wörter, die im Beitrag
ohnehin vorkommen.
**Die Zeichnung liegt über beiden Slides.** Zwei Plättchen — „Angebot" und
„Termin" — stehen auf Slide 1, drei auf Slide 2, und die Leitung läuft
hindurch. Möglich macht das `visLeft: -634`: die Zeichnung fängt vor der Seite
an, und alles davor landet auf der Nachbarslide.
**Kein Plättchen schneidet die Kante.** Die Reihenfolge in `DURCHEINANDER` ist
danach gewählt: „Angebot" und „Termin" sind kurz genug, um ganz vor
`SLIDE_KANTE` zu passen. Ein halbes Wort am Rand liest sich als Fehler, nicht
als Fortsetzung.
**Die beiden Plättchen liegen über der Kante des Chatfensters, nicht daneben.**
Ein Zwischenstand schrumpfte das Fenster auf zwei Drittel, um rechts Platz zu
schaffen — gemessen stand der Kanaltitel dann auf 10,8 px und die Projektnummer
auf 8,3. So klein trägt die Titelslide nicht mehr, denn im Profilraster ist sie
eine Kachel von rund 150 px. Jetzt bleibt das Fenster groß (15,3 px / 11,7 px),
und die Plättchen hängen an seiner rechten Kante über. Ein weiterer
Zwischenstand setzte sie mitten auf die Nachrichten; das las sich als
Kollision. In den Vorbildern überlappen Flächen an den Kanten, nicht quer über
Text — senkrecht sitzen sie deshalb dort, wo im Fenster am wenigsten steht. Das ist derselbe Überstand, vor dem
`rules.md` §5 sonst warnt: `.page` hat kein `overflow`, also landet alles, was
über die 1080 px hinausragt, auf der Nachbarslide. Hier ist es gewollt, und
`pruef.mjs` weiß es: ein negativer `--vis-left` gilt ihm als Ansage und wird
als „Überstand nach links (gewollt)" gemeldet statt als Fehler.
**Kein Pfeil zwischen den Hälften.** Er stand einmal dort und machte die Sache
zu ordentlich — eine Zeichnung, die Durcheinander zeigt, braucht keinen
Wegweiser, und die Leitung läuft ohnehin nach rechts. Die Plättchen sind aus
demselben Grund gestreut und leicht gekippt statt in einer geraden Spalte
gesetzt. Die Werte dafür stehen fest im Code und sind nicht gewürfelt: ein
Zufallsgenerator gäbe bei jedem Neubau ein anderes Bild, und die Slide soll
zweimal gleich aussehen.
**Was es kostet:** die Kanalliste steht jetzt in der halben Breite. Die
Projektnummer misst dadurch 12,4 px statt 17,4. Sie ist lesbar, aber knapp —
wer sie noch einmal verkleinert, nimmt der Slide ihre eigene Aussage.
---
## 5c. Der ausbrechende Rahmen auf Slide 3
Aus den Slack-Beiträgen kommt noch ein zweites Mittel, das mehr trägt als die
bloße Anordnung: **ein dicker, runder Rahmen, der über die Fläche hinausragt
und auf genau eine Sache zeigt.** Bei Slack ist er violett und liegt über einem
Handy; hier ist er gelb und liegt über der Anhangkarte der Aufgabe. Die Marke
gibt den Ton vor, nicht das Vorbild.
Er sitzt dort, weil diese Karte den Satz der Slide trägt: aus der Nachricht
wird etwas mit Frist und Namen. Seitlich bricht er aus der Blase aus, oben und
unten bleibt er knapp — rundum gleich viel Luft schnitt den Satz darüber und
die Uhrzeit darunter mitten durch.
Seine Lage kommt aus `blase()` selbst: die Funktion merkt sich, wo sie die
Anhangkarte gezeichnet hat. Die Rechnung ein zweites Mal zu führen hätte
geheißen, dass beide auseinanderlaufen, sobald sich an der Karte etwas ändert.
---
## 5b. Woher die Tiefe kommt
Zwei Anläufe waren falsch, bevor es saß.
@@ -199,10 +309,9 @@ Er trägt zwei Aufgaben: er gibt Tiefe, die ein Schatten allein nicht schafft,
und er sagt, dass hinter der einen Ansicht weitere liegen. Die hinteren
Blätter sind deshalb **leer** — Andeutung, keine Kopie.
Auf Slide 1 und 2 liegt er, weil dort ein Bildschirm zu sehen ist. Auf Slide 3
nicht: ein Ausschnitt aus einer Unterhaltung ist kein Blatt, dort tragen die
Blasen ihre eigene Erhebung. Slide 4 ist eine Überlagerung und trägt
`--shadow-overlay`.
Auf Slide 1, 2 und 4 liegt er, weil dort ein Bildschirm oder ein Menü zu sehen
ist. Auf Slide 3 nicht: ein Ausschnitt aus einer Unterhaltung ist kein Blatt,
dort tragen die Blasen ihre eigene Erhebung.
Dazu zwei Dinge aus der Quelle, die der Unterhaltung Struktur geben, ohne eine
Fläche zu behaupten: die Datumsmarke **Heute · 30.07.2026** über dem ersten
@@ -215,7 +324,7 @@ violett.** Im Original steht dort `bg-surface-neutral-primary` mit Rand und
---
## 6. Fünf Fallen aus diesem Bau
## 6. Vierzehn Fallen aus diesem Bau
**Die Listenzeile hat drei Zeilen, nicht zwei.** Der erste Anlauf setzte Titel
und Vorschau untereinander und schob Nadel und Ungelesen-Punkt rechts neben
@@ -224,6 +333,11 @@ Im Original steht sie als dritte Zeile: `P-2026-0042` links, die Nadel rechts.
Das war nicht nur ein Layoutfehler: der Fließtext behauptete „Jeder Kanal hängt
an seiner Projektnummer", und die Nummer war nirgends zu sehen.
**Eine Anhangkarte auf eine Zeile zu zwingen kürzt Zitate.** In der schmalen
Spalte auf Slide 5 stand „Materialliste prüfen · fälli…". Der Titel darf jetzt
auf zwei Zeilen, und die Karte wächst mit; die Blasenhöhe rechnet das mit.
Ebenso bricht die Systemzeile um, statt aus der Fläche zu laufen.
**Eine feste Obergrenze für die Blasenbreite hat ein Zitat gekürzt.** Für die
Titelslide musste sie klein sein, damit die Unterhaltung neben die Liste passt.
Auf Slide 3 stand deshalb „Materialliste prüfen · fälli…" statt
@@ -238,6 +352,26 @@ Nachgemessen sind es 818 px von 896 erlaubten: sie passt. Das Wort ist wieder
drin. Dafür gibt es jetzt `breite.mjs`, das eine Formulierung misst, bevor sie
in die Datei kommt.
**Eine Verschiebung mitten in der Zeichenfunktion erfasst nur die Hälfte.**
Die Zeile, die den Inhalt um `PAD` einrückt, stand mitten in `visChat()`.
Alles, was danach gezeichnet wurde — Datumsmarke, Blasen, Tippzeile,
Eingabefeld — lag um 26 Einheiten daneben, und die Kürzel schnitten die
Trennlinie zwischen den Spalten. Auf der ganzen Slide sieht man das nicht, im
Ausschnitt sofort. Die Verschiebung steht jetzt unmittelbar vor der Rückgabe.
Dafür gibt es `kollision.mjs`: es nimmt jedes gezeichnete Element, rechnet
seinen Kasten in Nutzereinheiten und meldet, was über den viewBox hinausragt
oder die senkrechte Trennlinie schneidet. Die Stapelblätter sind ausgenommen,
sie dürfen alles schneiden.
**Ein Prüfskript, das in den falschen Einheiten rechnet, ist schlimmer als
keines.** `kollision.mjs` benutzte zuerst `getCTM()`. Das enthält die
Skalierung der viewBox auf die Anzeigebreite — die Maße kamen also in
Bildschirmpixeln heraus und wurden gegen die viewBox in Nutzereinheiten
geprüft. Auf Slide 1 fiel es nicht auf, weil der Faktor dort zufällig bei 0,93
lag; auf Slide 2 meldete es 26 Fehler, die keine waren. Gerechnet wird jetzt
über `svg.getScreenCTM().inverse().multiply(el.getScreenCTM())`.
**Gedrehte Blätter ragen aus dem Kasten.** Der Stapel dreht das hinterste
Blatt um 3,2° und verschiebt es um 3 % und 5,5 % der eigenen Größe. Die Ecken
stehen damit über das vordere Blatt hinaus, und die Zeichnung lag bis zu 24 px
@@ -246,6 +380,26 @@ geschätzt: `stapelRand()` schickt die vier Ecken durch dieselbe Kette wie das
Blatt und nimmt den größten Wert. `stapelKasten()` macht den Kasten
entsprechend größer.
**Ein Maß, das aus einer Konstante kommt statt aus dem Gezeichneten, veraltet
still.** Der Pfeil auf Slide 2 rechnete seine Lage aus einer Blockbreite. Als
die Plättchen weiter nach rechts rückten, stimmte sie nicht mehr, und der Pfeil
zerfiel in zwei Striche. Er leitet seine Lage jetzt aus den Plättchen selbst
ab, so wie der Rahmen auf Slide 3 seine aus `blase()` bekommt.
**Eine Sicherung, die zu spät greift, sichert den Fehler mit.** `export.mjs`
schaltete die Slides mit der Pfeiltaste weiter und schrieb dabei im
Bearbeiten-Modus `vis`-Versätze in `state/post6.json`. Die Gegenmaßnahme war,
den Stand vor dem Lauf zu lesen und danach zurückzuschreiben — nur stand die
Drift beim nächsten Lauf schon in der Datei und wurde mitgesichert. Jetzt
werden die Slides über `#slides-nav` angeklickt statt durchgetastet; ohne
Tasten gibt es das Problem gar nicht erst.
**Ein gedrehtes Element wandert seitlich aus, wenn der Drehpunkt woanders
liegt.** Das Band sitzt 24 Einheiten unter der Mitte der Zeichnung, gedreht
wurde aber um die Mitte. Dadurch verschiebt sich sein Kasten um
24·sin(13°) ≈ 5 Einheiten zur Seite und stand über dem Rand. Gedreht wird
jetzt um die eigene Mitte des Bandes.
**`getBBox()` kennt keine Beschneidung.** Das Absperrband war zuerst auf die
175 % der Website gezogen und per `clip-path` am Gerät abgeschnitten. Im Bild
stimmte das, die Randmessung meldete aber einen Überstand von 146 px, den es
@@ -257,3 +411,60 @@ also `B / cos(13°)` plus Zugabe. Messung und Bild sagen wieder dasselbe.
einzelne Slide ein Fenster von 1400 px. Im durchgehenden Blatt liegt Seite 3
aber bei x = 2160, und `captureBeyondViewport` fing das nicht zuverlässig ab.
Die Bühne steht jetzt immer auf 7000 px.
**Eine ungültige `var()` fällt lautlos durch.** Die Animationsleiste war in
Farben gesetzt, die es in diesen Dateien nie gab — `var(--line)`,
`var(--panel)`, `var(--ink)`. Eine Variable ohne Wert und ohne Ersatzwert macht
nicht etwa die Farbe kaputt, sondern lässt **die ganze Angabe** fallen: kein
Rahmen, kein Grund, keine Meldung in der Konsole, kein Fehler in irgendeinem
Prüfskript. Aufgefallen ist es erst auf dem Bildschirmfoto. Die Leiste trägt
jetzt dieselben festen Werte wie der Rest der Oberfläche (`#17181F`, `#24262F`,
`#1D1F26`, `#2E313B`).
**Ein Geschwister hinter einem absolut liegenden Element landet nicht
darunter.** Die Leiste wurde als nächstes Element nach `#scroller` eingehängt
und lag prompt oben über dem Streifen: `.scroller` ist `position: absolute;
inset: 0` und nimmt keinen Platz im Fluss ein. Die Bühne ist deshalb jetzt eine
Spalte aus zwei Zeilen — oben eine neu eingezogene `#stageflaeche` mit Scroller
und Fußleiste, darunter die Leiste. Eingezogen wird sie beim Start per Skript,
damit der HTML-Rumpf jeder Datei unverändert bleibt.
---
## 7. Das Bewegtbild
Fünf Sekunden, 150 Bilder, `animation/post6-animChat.mp4`. Gezeichnet wird es
von `animChat(i, t)` in `post6.html`, mit denselben Bausteinen wie jede Slide —
nur bekommt die Funktion neben der Nummer den Fortschritt `t` und leitet daraus
alles ab. Der Eintrag steht in `ANIMS`, nicht in `SLIDES`.
| Zeit | Was passiert |
|---|---|
| 0,3 – 2,2 s | Die Antwort wird Zeichen für Zeichen ins Eingabefeld getippt, der Cursor blinkt. Sobald Text darin steht, wird aus dem Mikrofon der Senden-Knopf |
| 2,2 – 2,5 s | Das Feld leert sich |
| 2,4 – 3,3 s | Die Blase wächst von unten in den Verlauf, der Verlauf schiebt sich nach oben |
| 3,2 – 4,0 s | Die Aufgabenkarte klappt in der Blase auf, die Blase wächst mit |
| 3,9 – 4,6 s | Die Systemzeile „Aufgabe „Materialliste prüfen" im Projekt erstellt" erscheint darunter |
| 4,6 – 5,0 s | Alles blendet zurück auf den Anfangsstand, damit die Schleife schließt |
Vier Entscheidungen dahinter:
- **Der Verlauf hängt am unteren Rand, nicht am oberen.** Damit erzeugt jedes
Wachstum das Hochschieben von selbst, ohne eine zweite Rechnung für das
Scrollen. Die drei vorherigen Nachrichten sind dieselben wie auf Slide 1 und
stammen aus `GeneralTeamChatPage.tsx`, nicht aus einem erfundenen Szenario.
- **Sie liegt neben den Slides, nicht darin.** Ein erster Anlauf machte sie zur
sechsten Slide im Streifen. Damit wanderte sie in die Slide-Auswahl, in die
Zählung „Carousel, 6 Slides" und in den Bildexport, der sie als Standbild
mitschrieb. Ein Video, das man nur zusammen mit den Bildern herausbekommt,
ist im Carousel aber nicht getrennt einsetzbar.
- **Getippt wird ein Satz, der ohnehin im Verlauf steht.** `ANIM_TIPP` ist
`VERLAUF[3].text` — die Nachricht, die im Werkzeug an dieser Stelle folgt.
Kein Text ist für die Animation erfunden worden, auch keine Uhrzeit.
- **Das letzte Bild ist nicht das erste.** Der Fortschritt läuft über
`t = n / anzahl`; bei `n / (anzahl − 1)` wären Anfang und Ende dasselbe Bild
und der Stoß der Schleife stünde eine Dreißigstelsekunde still.
Nachgeprüft mit `ffprobe`: H.264 High, `yuv420p`, 1080 × 1350, 30 fps, Dauer
exakt 5,000000 s, stumme AAC-Spur, `moov` am Dateianfang, 1,1 MB.