Files
posts/POST-6.md
T
dejanandClaude Opus 5 e8f5686f30 Bewegtbild: die Kurven von GSAP, der Zeiger faehrt einen Bogen
Die Website macht ihre Bewegungen mit gsap 3.14.2. Uebernommen sind die
Kurven, nicht das Paket: was GSAP darueber hinaus kann -- Zeitleisten,
ScrollTrigger, MotionPath -- setzt voraus, dass etwas laeuft. Hier
laeuft nichts, animChat(i, t) bekommt seinen Fortschritt von aussen und
ist damit selbst schon die Zeitleiste. Ein paused timeline mit
.progress(t) waere dieselbe Konstruktion mit 70 KB darunter, und ein
Easing ist eine Zeile.

Zwei der Kurven waren ohnehin GSAPs: daempfen ist Wort fuer Wort
power2.inOut, rueck ist back.out(1,70158). Die Namen stehen jetzt
daneben, dazu kommen raus (power2.out) und knall (expo.out).

  Der Zeiger fuhr auf power2.inOut, also langsam an, in der Mitte
  schnell, wieder abbremsend -- die Bewegung eines Schlittens. Eine
  Hand ist sofort schnell und wird zum Ziel hin langsam.

  Und er fuhr auf der Geraden. Jetzt auf einer quadratischen Bezier,
  Stuetzpunkt 13 % der Strecke quer nach aussen, vom Verlauf weg. Rund
  27 px Ausbuchtung auf 410 px Weg: es faellt nicht auf, das ist der
  Punkt.

  Der Klickring geht auf zwei Kurven, Radius auf expo.out und die
  Deckung linear hinterher. Beides auf derselben sah aus wie eine
  Blase, die sich aufblaest.

  Der Anlauf ist von 0,6 s auf 0,18 s geschrumpft. Am Ende steht das
  Bild eine halbe Sekunde still, damit man das Ergebnis liest; lief
  vorn nochmal eine halbe, waren das ueber den Schleifenstoss hinweg
  1,2 von 6 Sekunden Stillstand am Stueck.

MP4 neu gebaut, Werte unveraendert: H.264, 1080x1350, 30 fps, 6,0 s,
stumme AAC-Spur. Geprueft ist das an Kontaktabzuegen ueber die 180
Bilder, nicht am Lauf.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U7RZ6ZpLD8Dp84Rtt4KHfv
2026-08-28 20:37:21 +02:00

41 KiB
Raw Blame History

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
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 Chatnachrichten werden Aufgaben im Projekt. 6 s Animation: eine Nachricht wird angeklickt, im Menü „Aufgabe erstellen" gewählt, und die Aufgabe klappt in der Nachricht auf

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

Alles, was einen Chat kennzeichnet, ist grün. Projektkanal, Gruppe und Direktnachricht tragen dieselbe Marke (MARKE, Fläche #DCFCE4, Rand #B7EDC6, Zeichen #148134) — in der Liste wie im Verlauf. Vorher trug der Projektkanal Lila, die Gruppe Grau und jede Person ihre eigene Farbe: vier Familien in einer Liste, und keine davon sagte etwas, das die Zeile nicht schon sagt. Die Kürzel unterscheiden die Personen, dafür braucht es keine vier Farbtöne.

Profilbilder sind vorbereitet, aber keine drin. avatarKreis() zeigt ein Foto, sobald eines unter assets/eigene/portrait-<kürzel>.<endung> liegt (also portrait-sk, portrait-mb, portrait-tw, portrait-ms), rund beschnitten mit demselben Rand wie der Kürzelkreis; sonst steht wie bisher das Kürzel da. Hochladen geht über „Eigene Bilder" in der Seite, preloadAssets() holt alles aus assets/eigene ohnehin mit, und der Export bekommt sie damit als Daten-URL statt als Verweis.

Warum keine im Repo: das Pitch-Paket lädt seine Portraits von Unsplash (PORTRAITS in GeneralTeamChatPage.tsx, vier Aufnahmen echter, erkennbarer Menschen). Die Lizenz deckt das Foto, nicht das Recht am eigenen Bild. Ein fremdes Gesicht als „Sandra Koch, Projektleitung" in einen öffentlichen Beitrag zu setzen, ist etwas anderes als ein Klickdummy für einen Kundentermin.

Die Plakette für Ungelesenes trägt eine Zahl und ist grün. In der Quelle ist unread ein Wahrheitswert und der Punkt violett. Beides ist geändert: violett ist im Produkt die Farbe der Projekte, das Kanalsymbol in derselben Zeile trägt sie schon, und ein zweites violettes Zeichen daneben konkurriert damit, statt etwas anderes zu sagen. Ein farbloser Punkt sagt außerdem nur „da war was", eine Zahl sagt, wie viel — so macht es jeder Messenger, den ein Betrieb kennt.

Die Zahlen (3, 2, 1) stehen nicht in der Quelle; sie ist an dieser Stelle binär. Sie sind die einzigen erfundenen Werte im ganzen Post.

Die Symbole kommen aus Phosphor, nicht aus Feather. Der Feather-Satz steckt in so ziemlich jeder Oberfläche der letzten Jahre; man erkennt ihn sofort wieder, und damit sieht eine Zeichnung aus wie jede andere. Am deutlichsten war das am Gruppensymbol — zwei Köpfe nebeneinander, das Piktogramm schlechthin.

Phosphor ist dieselbe Familie von Strichsymbolen, nur mit eigener Handschrift: runder, gleichmäßiger, andere Metaphern (UsersThree statt Users, PaperPlaneRight statt Send, CaretDown statt ChevronDown). Es ist dasselbe Paket, das die Website benutzt — @phosphor-icons/react steht neben lucide-react in ihrer package.json — also keine dritte Handschrift im Haus.

Die Pfade sind direkt aus dem installierten Paket gezogen, nicht abgeschrieben: Gewichtung bold, Zeichenfläche 256 × 256. Anders als bei Feather sind es gefüllte Formen und keine Striche, ikon() färbt deshalb über fill statt über stroke.

Die Gewichtung ist gemessen, nicht geschätzt. Kein Farbwert hat sich beim Tausch geändert — trotzdem sah es anders aus, und das zu Recht: ein Symbol aus dünneren Formen hat weniger voll gedeckte Pixel und mehr angeschnittene, es liest sich blasser, obwohl derselbe Hexwert darinsteht. Im Kanalsymbol, Ausschnitt 44 × 44:

volle Pixel Deckung
Feather, wie es vorher war 59 180
Phosphor regular 23 151 (16 % zu blass)
Phosphor bold 62 192 (6 % darüber)

Ein Zwischenstand hatte regular, weil es „im Menü leichter aussah". Geschätzt statt gemessen, und daneben. Wer eine optische Stärke beurteilt, zählt Pixel.

Ein Zwischenstand ersetzte nur das eine Gruppensymbol durch zwei überlappende Scheiben. Das war keine Antwort auf die Frage: die Frage war der Satz, nicht das einzelne Zeichen.

Slide 4 nennt drei der sechs Einträge, nicht alle. „Versende mehr als nur Textnachrichten: Dokumente, Fotos, Sprachnachrichten." Drei Beispiele sagen, worum es geht; die vollständige Liste steht im Bild daneben.

Vorher stand dort die ganze Liste mit Erklärung („Dokument, Fotos, Sprachnachricht, Kontakt. Soll daraus etwas werden: Termin oder Aufgabe."). Zu spezifisch war nicht das Aufzählen, sondern das Durchdeklinieren. Auf dem Weg zur jetzigen Fassung habe ich zweimal in die andere Richtung übersteuert und gar nichts mehr genannt — ein Fließtext auf einer Feature-Slide soll das Feature erklären, nicht umschreiben und auch nicht in vier Wörtern abhandeln.

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, Ränder 177/176/123, Luft 118, 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. Auf der Website ist es an den Rand des Telefons beschnitten — „clipped to the phone's rounded bounds so the ends cut off like real tape" steht als Kommentar daneben.

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.

Das Band läuft hinter die Ausschnitte. Es liegt über beiden und verschwindet an beiden Enden hinter ihnen: links hinter der linken Kante der hinteren Platte, rechts hinter der rechten der vorderen. Beschnitten wird also an der Silhouette beider Ausschnitte, und die ist die ganze Sache — der Gegensatz „absperren oder beschneiden", den ein früherer Stand hier behauptete, gibt es nicht.

Vorher lief das Band über die ganze Zeichnung hinaus und endete an der Bildkante, quer zur eigenen Laufrichtung abgeschnitten. Genau das ist das Zeichen für ein Ende: so liest sich ein Aufkleber, der zufällig dort aufhört, wo das Bild aufhört. Ein Ende, das an einer senkrechten Plattenkante abbricht, während das Band mit 13° ankommt, ist keines.

Drei Dinge tragen das, und keines verbiegt die Form.

Die Schnittkante ist die Plattenkante. Getroffen wird die Silhouette an genau zwei Stellen, beide dort gerade — die Rundungen liegen weit weg — und beide senkrecht.

Sie ist weicher als eine gezogene Linie und härter als die Platten. Mit deren stdDeviation 2.2 lief das Band über vier Einheiten ins Weiße aus und sah nach Ausblenden aus statt nach Verdecken; ganz scharf stand eine gestochene Kante an einer unscharfen Platte. 1.1 liegt dazwischen.

Die letzten Einheiten verlieren Licht, weil die Fläche sich wegdreht: von #F1B82A auf #815F11 über 56 Einheiten, mit dem Höhepunkt als Plateau dicht vor der Kante. Der Ton ist ein warmes Dunkelbraun und kein Schwarz — ein Band, das sich wegdreht, wird nicht farblos.

Die Wölbung bleibt, die Form bleibt. Quer über die Höhe liegt ein Verlauf von #F8C64A über #F1B729 nach #D89F1B, knapp unter der Oberkante ein Glanzlicht. Die untere Längskante ist einen Pixel dicker als die obere, weil sie im Schatten der Wölbung liegt. Die Form ist und bleibt das Rechteck, das sie auf der Website ist.

Zwei Anläufe davor haben stattdessen die Form verbogen: erst die Enden zur Spitze verjüngt, dann um eine Knickstelle gekippt. Das erste sah aus wie eine Schleife, das zweite wie eine Welle. Ein Absperrband ist flach und straff — was ihm gefehlt hat, war nicht ein Knick, sondern eine Kante, hinter die es läuft.

Helm und Schrift stehen einmal, mittig auf der geraden Fläche. Wiederholt und am linken Rand beginnend passte der Satz zu einem endlosen Band. Er sitzt in der Mitte des sichtbaren Laufs, weil dessen Mitte und die Drehmitte des Bandes dieselbe Stelle sind: beide Kanten liegen gleich weit von ihr weg.

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. 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 steht ganz auf ihrer eigenen Seite. Ein Zwischenstand zog sie über die Kante nach links, sodass zwei Plättchen auf Slide 1 lagen und die Leitung hindurchlief. Das ist zurückgenommen, und mit ihm der negative visLeft und die Sonderregel für die Slide-Kante. visLeft ist wieder 92 wie überall.

Die Leitung fängt am ersten Plättchen an. Sie hatte einen Anlauf, der links vor dem Nullpunkt begann — ein Rest aus der Fassung über zwei Slides. Ohne die Nachbarslide kam er aus dem Nichts und versprach einen Zusammenhang, den es nicht gibt. Jetzt beginnt sie bei „Angebot" und endet bei „Materialliste", also an beiden Enden an etwas.

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 führt ohnehin von oben nach unten. 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.

Der Ausschnitt steht größer da als die Zeichnung daneben (LISTE_SKALA, 1,28). Er trägt das Produkt, die Plättchen tragen nur die Ausgangslage; gleich groß gesetzt lasen sich beide als gleichwertig, und das sind sie nicht. Platz dafür war da: gemessen endet die linke Hälfte samt Leitung bei x = 344, der Kasten begann erst bei 464.

Die Projektnummer misst dadurch wieder 15,8 px statt 12,4 in der halben Breite. Sie ist der Beleg für den Fließtext („Jeder Kanal hängt an seiner Projektnummer") und muss lesbar bleiben — wer sie verkleinert, nimmt der Slide ihre eigene Aussage.


5c. Slide 3 kommt ohne Hervorhebung aus

Um die Anhangkarte der Aufgabe lag einmal ein gelber Rahmen, geborgt aus den Slack-Beiträgen: rund, über die Blase hinausragend, auf genau eine Sache zeigend. Er ist weg.

Bei sechs Pixeln war er plump — er trug mehr Farbe als die Karte, auf die er zeigen sollte. Der Versuch, ihn zu retten, ging über vier Varianten (ganz weg, weicher Schein, gelbe Kante links, dünner Ring), jeweils gerendert und auf Feed-Breite heruntergerechnet; gewählt war am Ende derselbe Ring mit drei statt sechs Pixeln. Im Beitrag sah das aus wie vorher.

Die Slide braucht ihn nicht. Sie besteht aus drei Nachrichten, die aufeinander folgen, und die Aufgabenkarte ist das einzige Element mit einer gelben Marke („Offen") — der Blick geht ohnehin dorthin. Ein Rahmen sagt nochmal, was die Anordnung schon sagt, und kostet dafür die Ruhe der Fläche.

Mit ihm ist letzterAnhang weggefallen: blase() merkte sich die Lage der zuletzt gezeichneten Anhangkarte, damit der Rahmen nicht aus einer zweiten Rechnung kommt. Ohne Rahmen liest das niemand mehr.


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. Einundzwanzig 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.

Eine Animation deckt auf, was ein Standbild verdeckt. Auf Slide 3 steht die Nachricht „Danke. Ich habe dazu direkt eine Aufgabe erstellt." mit angehängter Aufgabenkarte, und das liest sich einwandfrei: ein Zustand, eine Momentaufnahme. Sobald derselbe Inhalt einen Ablauf bekommt, fällt auf, dass Text und Anhang einander bedingen — die Nachricht meldete eine Aufgabe, die im selben Zug erst entstand. Der Fehler lag nicht in der Quelle, sondern darin, eine Momentaufnahme für eine Reihenfolge zu halten. Mehr dazu in §7.

Das Pitch-Paket ist ein Stand, nicht das Produkt. Der zweite Anlauf für die Animation nahm das Menü hinter dem Pluszeichen, weil GeneralTeamChatPage.tsx den einzigen dort ausgezeichneten Weg zu einer Aufgabe so zeigt. Belegt war das sauber — Datei, Zeile, Grep über beide Repos und über die Staging-API, die eine Aufgabe nur mit contact | company | lead | project verknüpft und eine Nachricht als Bezug gar nicht kennt. Trotzdem falsch: im laufenden Team-Chat hängt an jeder Nachricht ein Menü mit „Aufgabe erstellen".

Die Lehre ist nicht „schlechter prüfen", sondern wissen, was eine Quelle belegt. Ein Pitch-Paket vom Juli belegt, was im Juli gebaut war. Es belegt nicht, was das Produkt heute kann. Wo eine Aussage der Website (hier: „Aus „mach das mal" wird mit einem Klick eine echte Aufgabe") keine Entsprechung im vorliegenden Code hat, ist das ein Grund nachzufragen und nicht, die Aussage auf den vorhandenen Code zu biegen.

Ich habe die falsche Slide geprüft. Nach dem Vergrößern des Ausschnitts auf Slide 2 lief kollision.mjs mit dem Argument 1 — das ist Slide 1. Auf Slide 2 ragte das hinterste, gedrehte Blatt des Stapels fünf Einheiten unter den Kasten: stapelRand() rechnet den Überstand für die unskalierte Größe, und die Skalierung vergrößert ihn mit. Der Kasten wird jetzt aus dem skalierten Stapel gerechnet. Gefunden wurde es erst zwei Commits später, bei einer Prüfung, die alle fünf Slides durchging.

Ein || 0 an der falschen Stelle verdeckt ein NaN. Der Abspielen-Knopf tat nichts, sobald man ihn nach dem Laden als Erstes anfasste. animAbspielen rechnete performance.now() - ANIM_T[i] * dauer, damit die Vorschau dort weiterläuft, wo der Regler steht — nur ist ANIM_T[i] undefined, solange der Regler nie bewegt wurde. Daraus wird NaN, der Fortschritt wird NaN, und animT() liefert wegen seines eigenen || 0 brav wieder 0: die Vorschau stand still, ohne dass in der Konsole etwas auftauchte.

Der Fehler war vier Commits alt und wurde nicht bemerkt, weil jede Prüfung vorher animSetzen() aufgerufen hatte, um irgendetwas zu messen — und damit genau den Zustand herstellte, in dem es funktioniert. Ein Prüfskript, das den Anfangszustand anfasst, prüft ihn nicht mehr. animSetzen weist NaN jetzt zusätzlich mit Number.isFinite ab, damit ein solcher Wert gar nicht erst in den Stand kommt.

Ein weicher Schnitt und ein Schatten an derselben Stelle heben sich auf. Die Falte des Absperrbands hatte ihren dunkelsten Punkt genau auf der Plattenkante. Dort blendet die Maske das Band aber schon aus — das Dunkelste lag also im Durchsichtigen und mischte sich mit dem Weiß der Platte zu einem blassen Streifen, der die Falte gerade wieder aufhob. Der Höhepunkt sitzt jetzt als Plateau ein Stück davor, wo das Band noch voll deckt.

Ein Verlauf gehört in die Koordinaten dessen, woran er endet. Das Abdunkeln der Bandenden stand zuerst im gedrehten Bandkasten und lief damit 13° schräg zur senkrechten Plattenkante, an der es aufhören soll. Über die Bandhöhe verschob sich der Höhepunkt dadurch um 21 Einheiten. Es steht jetzt in den Koordinaten der Zeichnung, waagerecht, parallel zur Kante.

Ein Kommentar, der einen zurückgenommenen Stand beschreibt, ist schlimmer als keiner. Über dem Band standen zwei Kommentarblöcke übereinander: der eine sagte, es rage beidseitig hinaus, der andere, es knicke an den Enden weg. Das erste stimmte noch, das zweite war einen Commit vorher zurückgenommen worden. Wer danach etwas ändert, glaubt dem Kommentar. Mit ihnen weg sind auch WEB.bandKnick und WEB.bandSpitze, die Farben des Knicks, die niemand mehr las.


7. Das Bewegtbild

Sechs Sekunden, 180 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.

Gezeigt wird der Weg, den das laufende Werkzeug hat: Nachricht anfassen, Menü öffnet sich, „Aufgabe erstellen", die Aufgabe klappt in der Nachricht auf, das Projekt bestätigt sie. Das ist die Funktion, die die Website meint mit „Aus „mach das mal" wird mit einem Klick eine echte Aufgabe."

Zeit Was passiert
0,0 – 0,2 s Ruhe. Der Verlauf steht: Sandra, Maike, Tobias
0,2 – 1,1 s Der Zeiger fährt auf Tobias' Nachricht, der Pfeil erscheint an ihr
1,3 – 1,6 s Klick
1,5 – 2,0 s Das Menü schnappt nach oben auf: Reagieren, Antworten, Im Thread antworten, Anpinnen, Aufgabe erstellen, Bearbeiten, Löschen
2,4 – 3,4 s Der Zeiger geht auf „Aufgabe erstellen", die Zeile hebt sich, Klick
3,6 – 4,8 s Das Menü schließt, die Aufgabenkarte klappt in Tobias' Nachricht auf, der Verlauf rutscht nach
4,8 – 5,4 s Die Systemzeile „Aufgabe „Materialliste prüfen" im Projekt erstellt" erscheint
5,4 – 6,0 s Halt auf dem Ergebnis

Die Kurven sind die von GSAP, die Bibliothek ist es nicht

Die Website macht ihre Bewegungen mit GSAP (gsap@3.14.2, Standard-Lizenz; HeroSection, Navbar, HoneycombSection, FAQSection und weitere). Die Frage lag also nahe, es hier auch zu tun. Die Antwort ist: die Kurven ja, die Bibliothek nein.

Was GSAP über eine Kurve hinaus kann — Zeitleisten, ScrollTrigger, MotionPathPlugin —, setzt voraus, dass etwas läuft. Hier läuft nichts: animChat(i, t) bekommt seinen Fortschritt von außen und zeichnet daraus den Stand. Die Funktion ist schon die Zeitleiste, und animation.mjs sampelt sie Bild für Bild. Ein gsap.timeline({paused: true}) mit .progress(t) wäre genau dieselbe Konstruktion, nur mit 70 KB darunter — und das Repo ist autark, jede Datei in vendor/ liegt dort, weil sie etwas kann, das man nicht in fünf Zeilen schreibt (DOM nach Canvas, ZIP). Ein Easing ist eine Zeile.

Dazu kommt: zwei der Kurven waren längst GSAPs. daempfen ist Wort für Wort power2.inOut, rueck ist back.out(1.70158). Die Datei sprach die Sprache schon, sie sagte es nur nicht. Jetzt stehen die Namen daneben, und der Satz ist vollständig:

in der Datei GSAP wofür
daempfen power2.inOut Wege mit Anfang und Ende: das Wandern zur Menüzeile, das Aufklappen der Karte
rueck back.out(1.7) das Menü, das aufschnappt und einmal leicht überschwingt
raus power2.out alles, was ankommt und liegen bleibt: der Zeiger, die Systemzeile
knall expo.out der Klickring

Der Zeiger fährt jetzt wie eine Hand und nicht wie ein Schlitten. Er lag auf power2.inOut — langsam an, in der Mitte schnell, wieder abbremsend. So bewegt sich ein Schlitten auf einer Schiene. Eine Hand ist sofort schnell und wird zum Ziel hin langsam, also power2.out.

Und er fährt einen Bogen. Auf der Geraden zwischen Start und Ziel liegt kein Zeiger, den je ein Mensch geführt hat. Der Stützpunkt einer quadratischen Bézierkurve sitzt quer zur Strecke, 13 % ihrer Länge nach außen — vom Verlauf weg, damit der Zeiger nicht über die Blasen streicht. Das sind rund 27 px Ausbuchtung auf 410 px Weg; es fällt nicht auf, und genau das ist der Punkt. Bei p = 1 liegt die Bahn exakt auf dem Ziel, das anschließende Wandern zur Menüzeile setzt also sauber daran an.

Der Klickring geht auf zwei Kurven. Der Radius auf expo.out, also fast sofort draußen, die Deckung linear hinterher. Beides auf derselben Kurve sah aus wie eine Blase, die sich aufbläst — ein Klick ist ein Stoß und kein Aufpumpen.

Der Anlauf ist von 0,6 s auf 0,18 s geschrumpft. Am Ende steht das Bild eine halbe Sekunde still, damit man das Ergebnis liest. Lief vorne noch einmal eine halbe Sekunde nichts, waren das über den Schleifenstoß hinweg 1,2 von 6 Sekunden Stillstand am Stück, und die Schleife sah aus, als hänge sie. Der Halt am Ende bleibt, der am Anfang ist auf ein Achtel geschrumpft.

Nachgeprüft ist das an Einzelbildern, nicht am Lauf. Kontaktabzüge über die 180 Bilder und eine Nahaufnahme des Klickrings zeigen, was die Kurven rechnen. Ob die Bewegung läuft, sieht man erst im Abspielen — dieser eine Schritt steht noch aus.

Warum Tobias' Nachricht

Nicht irgendeine: die Aufgabenkarte aus der Quelle trägt „Tobias Wagner · offen" als Zuständigkeit. Wer sie an einer fremden Nachricht entstehen lässt, muss den Namen erfinden. So bleiben alle vier Felder der Karte (square-check, „Materialliste prüfen · fällig 31.07.2026", „Tobias Wagner · offen", „Offen") genau die aus GeneralTeamChatPage.tsx.

Vier Entscheidungen

  • 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. Wenn die Aufgabenkarte aufklappt, rutscht der Verlauf nach.
  • Die Karte wird aufgedeckt, nicht gestaucht. Sie liegt in voller Größe da und wird über eine clipPath von oben freigegeben. Eine gestauchte Karte sieht kaputt aus, eine aufgedeckte sieht nach Aufklappen aus.
  • Der Mauszeiger ist erzählerisches Mittel, kein Bestandteil des Werkzeugs. Ohne ihn öffnet sich ein Menü und man weiß nicht, warum. Weiß mit dunkler Kante, damit er auf Verlauf wie Menü liest, und nach dem zweiten Klick zügig weg — sonst zieht er den Blick von der aufklappenden Aufgabe ab.
  • Am Ende wird nichts zurückgenommen. Ein früherer Stand blendete die neue Nachricht wieder aus, damit der Schleifenstoß unsichtbar wird. Das sah aus, als lösche sich eine abgeschickte Nachricht von selbst. Eine Schleife darf springen.

Ohne Fließtext

Die Animation trägt als einzige keine Beschreibung. Die Überschrift sagt es, die Bewegung zeigt es, und die Systemzeile im Bild bestätigt es — ein Satz darunter wäre die dritte Stelle für dieselbe Aussage. Ein leerer body lässt contentHtml den Absatz ganz weg, sonst schöbe sein Zeilenabstand die Zeichnung nach unten, ohne dass etwas dasteht.

Die gut hundert Pixel, die dadurch frei werden, gehen an die Zeichnung: visW von 700 auf 776, mittig gesetzt bei visLeft: 152. Luft zum Text 118, also derselbe Wert wie auf Slide 1.

Die Überschrift „Chatnachrichten werden / Aufgaben im Projekt." misst 879 px in der ersten und 733 in der zweiten Zeile, bei 896 erlaubten. Gemessen mit breite.mjs, bevor sie in die Datei kam, und nach dem Einsetzen noch einmal am gerenderten Blatt nachgezählt: zwei Zeilen, kein Umbruch.

„Chatnachrichten" zusammengeschrieben, nicht mit Bindestrich. Beides ist korrekt, also entscheidet die Breite: „Chat-Nachrichten werden" misst 916 px und läuft über die Schutzzone, zusammengeschrieben sind es 879. Verworfen wurden außerdem „Aufgaben im richtigen Projekt." (1067 px) und „Aus Chat-Nachrichten werden" (1063 px).

Das Menü schnappt auf, es kriecht nicht. Ein Zwischenstand nahm dafür dieselbe gedämpfte Kurve wie alles andere, über 0,8 Sekunden. Die Deckung war darin nach fünf Bildern fertig, während die Skalierung noch eine halbe Sekunde von 0,96 auf 1,00 weiterlief. Zu sehen war davon fast nichts, zu spüren war es als Hänger genau in dem Moment, in dem geklickt wird. Jetzt öffnet es in einer Drittelsekunde mit rueck, schwingt einmal ganz leicht über und steht: drei Bilder Aufblenden, dann Ruhe.

Zur Länge

Instagram nimmt je Carousel-Element 3 bis 60 Sekunden an. Sechs liegt weit von beiden Grenzen weg; die Länge richtet sich also allein nach dem Ablauf. Vier Schritte — anfassen, wählen, aufklappen, bestätigen — brauchen etwa so lange. Eine kurze Schleife wiederholt sich außerdem öfter, während jemand liest. Eine Zwischenfassung lief acht Sekunden, weil dort zusätzlich eine Nachricht getippt wurde; das fällt mit dem richtigen Weg weg.

Die Symbole im Menü sind unsere Strichsymbole. Im Werkzeug steht bei „Reagieren" ein Emoji; ein Emoji zwischen sechs Strichsymbolen bricht die Bildsprache, und die Slides tragen durchgehend Feather-Symbole.

Nachgeprüft mit ffprobe: H.264 High, yuv420p, 1080 × 1350, 30 fps, 180 Bilder, Dauer exakt 6,000000 s, stumme AAC-Spur, moov am Dateianfang, 1,1 MB.