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>
24 KiB
Post 6 · Team-Chat
Fünfter Beitrag der Produktreihe, Termin Mi 26.08. laut Redaktionsplan (von Mo 24.08. verschoben). Stichwort dort: „Ein Kanal pro Projekt, Foto rein, Aufgabe raus."
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, 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"; was diese eine zeigt, in
§7 weiter unten.
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. | 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, 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 Baustellenfotos, darauf folgt eine Nachricht mit angehängter Aufgabe, darauf eine Systemzeile „Aufgabe „Materialliste prüfen" im Projekt erstellt". Die letzte Zeile macht den Unterschied: die Aufgabe liegt danach im Projekt und nicht nur im Verlauf.
Slide 1 trägt den ganzen Chat, weil sie im Profilraster eine Kachel von rund 150 px ist. Dort ist kein Wort lesbar; was ankommt, sind zwei Spalten mit Blasen.
2. Die Bildunterschrift
Im Handwerk läuft die halbe Baustelle über WhatsApp. Das Foto von letzter Woche findet trotzdem keiner wieder.
In HiveGrid hat jedes Projekt seinen eigenen Kanal, mit Projektnummer. Daneben stehen Direktnachrichten und Gruppen, aber eben nicht alles in einem Verlauf.
Ein Foto von der Baustelle geht rein, alle sehen es sofort. Aus „mach das mal“ wird mit einem Klick eine echte Aufgabe: mit Frist, mit Namen, im Projekt.
Dokument, Sprachnachricht, Kontakt, Termin. Was sonst im Chat-Verlauf verschwindet, hängt hier am Projekt.
Kundendaten gehören nicht in eine WhatsApp-Gruppe. Bei HiveGrid liegen sie verschlüsselt auf deutschen Servern.
#handwerk #shk #handwerksbetrieb #digitalisierungimhandwerk #teamchat #baustelle #kommunikation #hivegrid
| Vorgabe | Ist |
|---|---|
| Haken in den ersten 125 Zeichen | erster Absatz, 110 |
| 300 bis 800 Zeichen | 644 |
| 5 bis 10 Hashtags, unten abgesetzt | 8 |
| Suchwörter zusätzlich im Fließtext | Handwerk, Baustelle, Projekt, Kanal, Aufgabe, Chat, Kontakt, Termin |
Der letzte Absatz steht wörtlich so als Merkmal „DSGVO-konform" in
lib/produkt-daten.ts der Website. „Aus „mach das mal" wird mit einem Klick
eine echte Aufgabe" ebenfalls, als Merkmal „Aufgaben-Integration".
3. Woher die Angaben kommen
Quelle ist components/general/GeneralTeamChatPage.tsx aus dem Pitch-Paket
(fertig bombo · Kundentermin 30.07.2026).
| Was | Woher |
|---|---|
| Alle acht Kanäle: Titel, Vorschau, Zeit, Zuordnung, Nadel, Ungelesen | CHATS, wörtlich |
| Der Verlauf: Autoren, Texte, Zeiten, Anhänge | INITIAL_MESSAGES.waermepumpe, wörtlich |
| Aufbau einer Listenzeile (drei Zeilen) | die Zeilen 170 bis 172 der Quelle |
| Aufbau einer Nachricht, Anhangkarte, Systemzeile | MessageBubble und AttachmentCard |
| Sinnbild je Kanalsorte (Koffer, Personen, Kürzel) | ChatAvatar |
| Kopfzeile „P-2026-0042 · 8 Mitglieder · aktiv" | ChatHeader |
| Filterzeile Alle / Projekte / Direkt / Ungelesen | der Typ ChatFilter |
| Das Menü hinter dem Plus, samt Trenner | Composer |
| Eingabefeld „Nachricht schreiben …" | derselbe Ort |
| Farben, Ränder, Plättchentöne | ui/theme.css |
| Alle Symbole | @subframe/core, Pfade aus dem Paket kopiert |
| „Büro und Baustelle, endlich im selben Chat." | Überschrift in lib/produkt-daten.ts |
Vom Chat gibt es nur eine Fassung. Anders als bei der Pipeline, die mit
Board, Liste, Detail und Schablone vier eigene SHK-Bildschirme hat, liegt der
Chat allein unter /allgemein/team-chat. Die Inhalte darin sind trotzdem aus
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.
Die Bildergalerie ist eine Anhangkarte, kein Bild. Im Werkzeug zeigt
GalleryPreview zwei Fotos, geladen von Unsplash. Fremde Fotos in einen
Beitrag zu setzen, der von der eigenen Baustelle spricht, wäre eine Behauptung
über Material, das wir nicht haben. Stattdessen steht dort die Karte
„2 Baustellenfotos · Galerie · heute" — der Text ist der der Quelle.
5. Stand der Dinge
| Slides | 5, dazu 1 Animation außerhalb des Streifens |
| Format | 1080 × 1350, Ausgabe zweifach: 2160 × 2700 |
| 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 |
| Konsole | keine Fehler |
| Ü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; 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
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.
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 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.
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
Baustelle. Am Desktop bist du schon voll dabei, die Version fürs Handy folgt
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ß.
Erster Versuch: graue Flächen hinter die Elemente. Das nimmt den Zeichnungen ihre Durchsichtigkeit und setzt einen Kasten auf den Punktgrund, der dort nicht hingehört. Wieder raus.
Zweiter Versuch: nur bessere Schatten. Richtig, aber zu wenig. Die
Schatten des Produkts sind in ui/theme.css gestaffelt und nicht
einlagig, und das war tatsächlich ein Fehler im ersten Nachbau:
--shadow-sm 0 1px 2px rgb(0 0 0 / .08)
--shadow-md 0 4px 16px -2px + 0 2px 4px -1px
--shadow-lg 0 12px 32px -4px + 0 4px 8px -2px
Zwei Lagen, weil eine allein nicht beides kann: die enge dunkle Kante setzt
das Element auf die Fläche, die weite helle hebt es davon ab. In SVG zwei
feDropShadow im selben Filter. Vorher stand dort einer mit dy 7 und
stdDeviation 10.
Was es gebracht hat: der Stapel. post1.html hat ihn als Bildrahmen
stapel im CSS, für Screenshots. Hier ist er in SVG nachgebaut, mit genau
denselben Werten, damit die Reihe zusammenpasst:
.sheet-mid rotate(-2.4deg) translate(-2.2%, 3%) scale(.975)
.sheet-back rotate( 3.2deg) translate( 3%, 5.5%) scale(.95)
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, 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
Beitrag und die Zeile „Maike Berger schreibt gerade …" über dem
Eingabefeld. Beides steht so in GeneralTeamChatPage.tsx.
Nebenbei richtiggestellt: der offene Kanal ist grau hinterlegt, nicht
violett. Im Original steht dort bg-surface-neutral-primary mit Rand und
shadow-sm.
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
die Vorschau. Dort stießen sie an den Text — und die Zuordnung fehlte ganz.
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 „… · fällig 31.07.2026". Die Grenze hängt jetzt an der Spalte, in der die Blase steht.
Beinahe hätte ich ein Zitat ohne Grund gekürzt. Die Website-Überschrift
lautet „Büro und Baustelle, endlich im selben Chat." Ich hatte das „endlich"
gestrichen mit der Begründung, die Zeile laufe sonst über die Schutzzone, und
diese Begründung sogar mit einer Zahl versehen, die ich nicht gemessen hatte.
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
außerhalb der 92-px-Schutzzone. Der Überstand wird jetzt gerechnet statt
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
gar nicht gab: getBBox() liefert die Geometrie, nicht das Sichtbare. Das
Band ist jetzt nur so lang, wie es bei -13° quer über das Gerät sein muss,
also B / cos(13°) plus Zugabe. Messung und Bild sagen wieder dasselbe.
Das Prüfskript nahm eine leere Slide auf. schuss.mjs stellte für eine
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_TIPPistVERLAUF[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; bein / (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.