Zusammengeschrieben und nicht mit Bindestrich, weil die Breite entscheidet: "Chat-Nachrichten werden" misst 916 px und laeuft ueber die 896 der Schutzzone, "Chatnachrichten werden" sind 879. Beide Schreibweisen sind korrekt. Ebenfalls verworfen: "Aus Chat-Nachrichten werden" (1063 px) und "Aus Chatnachrichten werden" (1027 px) -- mit "Aus" passt die Zeile nicht mehr, ohne bleibt der Satz vollstaendig. Nach dem Einsetzen am gerenderten Blatt nachgezaehlt statt nur gerechnet: zwei Zeilen zu 879 und 733 px, Gesamthoehe 158 = zweimal die Zeilenhoehe. Kein Umbruch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
557 lines
29 KiB
Markdown
557 lines
29 KiB
Markdown
# 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"](./README.md#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
|
||
|
||
**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**. 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.
|
||
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. Siebzehn 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.
|
||
|
||
**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.
|
||
|
||
---
|
||
|
||
## 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,6 s | Ruhe. Der Verlauf steht: Sandra, Maike, Tobias |
|
||
| 0,6 – 1,3 s | Der Zeiger fährt auf Tobias' Nachricht, der Pfeil erscheint an ihr |
|
||
| 1,3 – 1,6 s | Klick |
|
||
| 1,6 – 2,4 s | Das Menü klappt 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 |
|
||
|
||
### 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).
|
||
|
||
### 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.
|