Bewegtbild: die Aufgabe entsteht an der Nachricht, nicht im Plus-Menue

Geprueft im laufenden Team-Chat: an jeder Nachricht haengt ein Menue mit
Reagieren, Antworten, Im Thread antworten, Anpinnen, Aufgabe erstellen,
Bearbeiten, Loeschen. Das ist die Funktion, die die Website meint mit "Aus
'mach das mal' wird mit einem Klick eine echte Aufgabe".

Die Animation zeigt jetzt genau das: Tobias' Nachricht anfassen, der Pfeil
erscheint an ihr, Menue auf, "Aufgabe erstellen", die Aufgabenkarte klappt in
der Nachricht auf, die Systemzeile bestaetigt sie im Projekt.

Tobias' Nachricht und keine andere: die Aufgabenkarte aus der Quelle traegt
"Tobias Wagner · offen" als Zustaendigkeit. An einer fremden Nachricht muesste
der Name erfunden werden. So bleiben alle vier Felder der Karte genau die aus
GeneralTeamChatPage.tsx.

Der Vorgaenger nahm das Menue hinter dem Pluszeichen. Das war sauber belegt --
Datei, Zeile, Grep ueber beide Repos, dazu die Staging-API, die eine Aufgabe
nur mit contact/company/lead/project verknuepft und eine Nachricht als Bezug
gar nicht kennt -- und trotzdem falsch. Ein Pitch-Paket vom Juli belegt, was im
Juli gebaut war, nicht was das Produkt heute kann. Wo eine Aussage der Website
keine Entsprechung im vorliegenden Code hat, ist das ein Grund nachzufragen und
nicht, die Aussage auf den Code zu biegen. Steht so in POST-6.md §6.

Sechs statt acht Sekunden: das Tippen faellt mit dem richtigen Weg weg.
Instagram nimmt je Carousel-Element drei bis sechzig Sekunden an, die Laenge
richtet sich also allein nach dem Ablauf.

Dazu in den Bausteinen: fuenf Symbole fuer das Menue, und eine Blase kann eine
zweite Anhangkarte tragen, die ueber einen Anteil aufklappt -- aufgedeckt per
clipPath, nicht gestaucht.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
dejanandClaude Opus 5 committed 2026-08-26 13:42:53 +02:00
1 parent d4feaa175b
commit a8f79c1224
4 files changed
+227 -215

No files matched your search

+60 -46
View File
@@ -36,7 +36,7 @@ Dazu, außerhalb des Streifens:
| Schlüssel | Tag | Überschrift | Was zu sehen ist |
|---|---|---|---|
| `animChat` | Bewegtbild | Getippt, **und schon im Projekt.** | 8 s Animation: eine Notiz wird getippt, über das Pluszeichen wird „Aufgabe erstellen" gewählt, und beides geht zusammen als eine Nachricht raus |
| `animChat` | Bewegtbild | Getippt, **und schon 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
@@ -324,7 +324,7 @@ violett.** Im Original steht dort `bg-surface-neutral-primary` mit Rand und
---
## 6. Fünfzehn Fallen aus diesem Bau
## 6. Sechzehn 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
@@ -437,69 +437,83 @@ 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.
---
## 7. Das Bewegtbild
Acht Sekunden, 240 Bilder, `animation/post6-animChat.mp4`. Gezeichnet wird es
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,4 – 2,9 s | Die Notiz wird Zeichen für Zeichen ins Eingabefeld getippt, der Cursor blinkt. Sobald Text darin steht, wird aus dem Mikrofon der Senden-Knopf |
| 2,9 – 3,8 s | Der Zeiger fährt auf das Pluszeichen und klickt |
| 3,6 – 4,5 s | Das Menü klappt nach oben auf, die Zeilen gestaffelt |
| 4,5 – 5,8 s | Der Zeiger geht auf „Aufgabe erstellen", die Zeile hebt sich, Klick |
| 5,7 – 6,1 s | Das Menü schließt, das Feld leert sich |
| 5,8 – 6,7 s | Die Nachricht wächst in den Verlauf, Text und Aufgabenkarte zusammen |
| 6,7 – 7,4 s | Die Systemzeile „Aufgabe „Materialliste prüfen" im Projekt erstellt" erscheint |
| 7,4 – 8,0 s | Halt auf dem Ergebnis |
| 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 |
### Der erste Anlauf hatte den Ablauf falsch herum
### Warum Tobias' Nachricht
Dort wurde die Nachricht „Danke. Ich habe dazu direkt eine Aufgabe erstellt."
getippt, abgeschickt, und **danach** wuchs an ihr eine Aufgabenkarte. Die
Nachricht meldete also eine Aufgabe, die es noch gar nicht gab, und wurde
anschließend selbst zu einer. Ein Kreis, kein Ablauf.
Nachgesehen in `GeneralTeamChatPage.tsx`: eine Aufgabe entsteht über das Menü
hinter dem Pluszeichen („Aufgabe erstellen", Zeile 275), und das Ergebnis ist
Nachricht `m4` — Text **und** angehängte Aufgabenkarte in einem — gefolgt von
der Systemzeile `m5`. **Ein Kontextmenü an einer bestehenden Nachricht gibt es
dort nicht.** Wer eine Umwandlung „Nachricht → Aufgabe" zeigt, zeigt eine
Funktion, die wir nicht haben, auch wenn die Website den Nutzen so beschreibt
(„Aus „mach das mal" wird mit einem Klick eine echte Aufgabe").
Die Reihenfolge ist deshalb: Notiz tippen, Pluszeichen, „Aufgabe erstellen",
und beides geht zusammen als eine Nachricht raus. Der Satz im Perfekt stimmt
dann auch: wenn die Nachricht ankommt, hängt die Aufgabe daran.
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. 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 Zwischenstand 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 nicht getrennt einsetzbar.
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 sieht man ein Menü aufgehen und weiß nicht, warum. Er ist weiß mit
dunkler Kante, damit er auf dem Verlauf wie auf dem Menü liest, und geht
nach dem zweiten Klick zügig weg — ein Zeiger, der ohne Aufgabe über dem
Verlauf stehen bleibt, zieht den Blick von der ankommenden Nachricht ab.
- **Am Ende wird nichts zurückgenommen.** Der erste Anlauf blendete die neue
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, eine Nachricht darf nicht verschwinden.
springen.
Kein Text und keine Uhrzeit ist für die Animation erfunden worden. Getippt wird
`VERLAUF[3].text`, also genau die Nachricht, die im Werkzeug an dieser Stelle
steht.
### Zur Länge
Nachgeprüft mit `ffprobe`: H.264 High, `yuv420p`, 1080 × 1350, 30 fps, 240
Bilder, Dauer exakt 8,000000 s, stumme AAC-Spur, `moov` am Dateianfang, 1,5 MB.
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.
+1 -1
View File
@@ -374,7 +374,7 @@ ist sie ein eigenes Element, und ob sie mitkommt, entscheidest du beim Posten.
```js
const ANIMS = [
{ key: "animChat", type: "feature", dauer: 8,
{ key: "animChat", type: "feature", dauer: 6,
eyebrow: "Bewegtbild",
head: 'Getippt,<br><span class="a">und schon im Projekt.</span>',
body: "Eine Nachricht, ein Klick, und die Aufgabe hängt am richtigen Projekt.",
Binary file not shown.
+166 -168
View File
@@ -1464,12 +1464,12 @@ if (location.search.indexOf("nur=anim") >= 0)
ist `dauer` und der zweite Wert t, den die Zeichenfunktion bekommt. */
const ANIMS = [
{
/* Acht Sekunden. Fuenf waren zu knapp, sobald der Ablauf stimmt:
tippen, Menue, waehlen, abschicken, bestaetigen -- das sind fuenf
Schritte, und jeder braucht seinen Moment. Unter drei Sekunden nimmt
Instagram ein Video im Carousel gar nicht erst an, nach oben sind
sechzig erlaubt. */
key: "animChat", type: "feature", dauer: 8,
/* Sechs Sekunden. Instagram nimmt im Carousel drei bis sechzig an; das
ist also weit von jeder Grenze weg, und die Laenge richtet sich
allein nach dem Ablauf. Vier Schritte -- anfassen, waehlen,
aufklappen, bestaetigen -- brauchen etwa so lange, und eine kurze
Schleife wiederholt sich oefter, waehrend jemand liest. */
key: "animChat", type: "feature", dauer: 6,
eyebrow: "Bewegtbild",
head: 'Getippt,<br><span class="a">und schon im Projekt.</span>',
body: "Eine Nachricht, ein Klick, und die Aufgabe hängt am richtigen Projekt.",
@@ -2051,6 +2051,12 @@ if (location.search.indexOf("nur=anim") >= 0)
"workflow": '<rect width="8" height="8" x="3" y="3" rx="2"/><path d="M7 11v4a2 2 0 0 0 2 2h4"/><rect width="8" height="8" x="13" y="13" rx="2"/>',
"x": '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
"zap": '<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>',
/* Fuer das Menue an der Nachricht, aus dem laufenden Team-Chat. */
"thumbs-up": '<path d="M7 10v12"/><path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z"/>',
"corner-up-left": '<polyline points="9 14 4 9 9 4"/><path d="M20 20v-7a4 4 0 0 0-4-4H4"/>',
"pencil": '<path d="M21.2 6.8a1 1 0 0 0-4-4L3.8 16.2a2 2 0 0 0-.5.8l-1.3 4.4a.5.5 0 0 0 .6.6l4.4-1.3a2 2 0 0 0 .8-.5z"/><path d="m15 5 4 4"/>',
"trash-2": '<path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M10 11v6"/><path d="M14 11v6"/>',
"chevron-down": '<path d="m6 9 6 6 6-6"/>',
"rocket": '<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/>'
};
@@ -2703,12 +2709,19 @@ if (location.search.indexOf("nur=anim") >= 0)
const innen = (max || BLASE_MAX) - 32;
const zeilen = m.text ? umbrechen(m.text, innen, 17, 400) : [];
const innenB = (max || BLASE_MAX) - 32;
/* Eine Blase kann eine zweite Anhangkarte tragen: die Aufgabe, die aus
ihr entstanden ist. `a2` ist ihr Anteil zwischen 0 und 1 -- damit
waechst die Blase mit, waehrend die Karte aufklappt, und der ganze
Verlauf rutscht von selbst nach. */
const f2 = m.anhang2 ? (m.a2 == null ? 1 : m.a2) : 0;
let h = 14 + (m.mein ? 0 : 22) + zeilen.length * 25 +
(m.anhang ? anhangHoehe(m.anhang, innenB) + 8 : 0) + 24;
(m.anhang ? anhangHoehe(m.anhang, innenB) + 8 : 0) +
(f2 > 0 ? (anhangHoehe(m.anhang2, innenB) + 8) * f2 : 0) + 24;
let b = 32 + Math.max(
...zeilen.map(z => messen(z, 17, 400)),
m.mein ? 0 : messen(m.autor, 14, 800),
m.anhang ? anhangNoetig(m.anhang) : 0, 90);
m.anhang ? anhangNoetig(m.anhang) : 0,
m.anhang2 ? anhangNoetig(m.anhang2) : 0, 90);
return { zeilen, hoehe: Math.round(h), breite: Math.round(Math.min(b, max || BLASE_MAX)) };
}
@@ -2717,6 +2730,7 @@ if (location.search.indexOf("nur=anim") >= 0)
als die Rechnung ein zweites Mal zu fuehren und sie auseinanderlaufen zu
lassen. */
let letzterAnhang = null;
let klappNr = 0;
function blase(x, y, b, m) {
// Die Systemmeldung ist keine Blase, sondern eine Zeile in der Mitte.
@@ -2762,6 +2776,21 @@ if (location.search.indexOf("nur=anim") >= 0)
m.anhang[2], m.anhang[3]);
cy += ah + 8;
}
/* Die zweite Karte klappt auf: sie wird von oben freigegeben, nicht
gestaucht -- eine gestauchte Karte sieht kaputt aus, eine
aufgedeckte sieht nach Aufklappen aus. */
const f2 = m.anhang2 ? (m.a2 == null ? 1 : m.a2) : 0;
if (f2 > .004) {
const ah = anhangHoehe(m.anhang2, ma.breite - 32);
const sicht = ah * f2;
const id = "anhklapp" + (++klappNr);
g += '<clipPath id="' + id + '"><rect x="' + n1(bx + 14) + '" y="' + n1(cy + 4) +
'" width="' + n1(ma.breite - 28) + '" height="' + n1(sicht) + '"/></clipPath>' +
'<g clip-path="url(#' + id + ')" opacity="' + n1(Math.min(1, f2 * 2.2)) + '">' +
anhangKarte(bx + 16, cy + 4, ma.breite - 32, m.anhang2[0], m.anhang2[1],
m.anhang2[2], m.anhang2[3]) + '</g>';
cy += sicht + 8 * f2;
}
g += uiText(bx + ma.breite - 16, cy + 16, m.zeit + (m.mein ? " ✓✓" : ""),
13, 500, CHAT_TEXT3, { anker: "end" });
return g;
@@ -3265,127 +3294,120 @@ if (location.search.indexOf("nur=anim") >= 0)
/* ─── Das Bewegtbild ────────────────────────────────────────────────────
Wie aus einer Nachricht eine echte Aufgabe wird, in dem Weg, den das
Werkzeug tatsaechlich hat.
Wie aus einer Nachricht eine echte Aufgabe wird -- der Weg, den das
laufende Werkzeug hat: Nachricht anklicken, Menue oeffnet sich,
"Aufgabe erstellen".
Das ist der Kern, und ein erster Anlauf hatte ihn falsch: dort wurde die
Nachricht "Danke. Ich habe dazu direkt eine Aufgabe erstellt." getippt,
abgeschickt -- und *dann* wuchs an ihr eine Aufgabenkarte. Die Nachricht
meldete also eine Aufgabe, die es noch gar nicht gab, und wurde
anschliessend selbst zu einer. Ein Kreis, kein Ablauf.
Zwei Anlaeufe lagen davor daneben, beide aus derselben Ursache. Der
erste liess die Nachricht "Danke. Ich habe dazu direkt eine Aufgabe
erstellt." tippen und danach eine Aufgabenkarte an ihr wachsen -- sie
meldete also eine Aufgabe, die es noch nicht gab, und wurde dann selbst
zu einer. Der zweite nahm das Menue hinter dem Pluszeichen, weil das im
Pitch-Paket der einzige Weg ist, der dort ausgezeichnet ist.
Nachgesehen in GeneralTeamChatPage.tsx: eine Aufgabe entsteht ueber das
Menue hinter dem Pluszeichen ("Aufgabe erstellen", Zeile 275), und das
Ergebnis ist Nachricht m4 -- Text *und* angehaengte Aufgabenkarte in
einem -- gefolgt von der Systemzeile m5. Ein Kontextmenue an einer
bestehenden Nachricht gibt es dort nicht; wer eine Umwandlung zeigt,
zeigt eine Funktion, die wir nicht haben.
Beides war zu wenig geprueft. Das Pitch-Paket ist ein Stand vom Juli;
im laufenden Team-Chat haengt an jeder Nachricht ein Menue mit
Reagieren, Antworten, Im Thread antworten, Anpinnen, Aufgabe erstellen,
Bearbeiten, Loeschen. Das ist die Funktion, die die Website meint, wenn
sie schreibt: "Aus 'mach das mal' wird mit einem Klick eine echte
Aufgabe."
Also die richtige Reihenfolge: Notiz tippen, Pluszeichen, "Aufgabe
erstellen", und beides geht zusammen als eine Nachricht raus. Der Satz
im Perfekt stimmt dann auch -- wenn die Nachricht ankommt, haengt die
Aufgabe daran.
Gewaehlt wird Tobias' Nachricht, nicht irgendeine: die Aufgabenkarte aus
der Quelle traegt "Tobias Wagner · offen" als Zustaendigkeit. Wer sie an
einer fremden Nachricht entstehen laesst, muss den Namen erfinden.
Der Ablauf, in Anteilen der Laufzeit (8 s):
Der Ablauf, in Anteilen der Laufzeit (6 s):
0,00 - 0,05 ruhig, der Verlauf steht, das Feld ist leer
0,05 - 0,36 die Notiz wird getippt, Zeichen fuer Zeichen
0,36 - 0,45 der Zeiger faehrt auf das Pluszeichen und klickt
0,45 - 0,56 das Menue klappt nach oben auf, die Zeilen gestaffelt
0,56 - 0,71 der Zeiger geht auf "Aufgabe erstellen" und klickt
0,71 - 0,76 das Menue schliesst, das Feld leert sich
0,72 - 0,84 die Nachricht mit der Aufgabenkarte waechst in den Verlauf
0,84 - 0,92 die Bestaetigung im Projekt erscheint
0,92 - 1,00 Halt auf dem Ergebnis
0,00 - 0,10 ruhig, der Verlauf steht
0,10 - 0,22 der Zeiger faehrt auf die Nachricht, der Pfeil erscheint
0,22 - 0,27 Klick
0,26 - 0,40 das Menue klappt auf, die Zeilen gestaffelt
0,40 - 0,56 der Zeiger geht auf "Aufgabe erstellen", die Zeile hebt sich
0,56 - 0,61 Klick
0,60 - 0,66 das Menue schliesst, der Zeiger geht
0,62 - 0,80 die Aufgabenkarte klappt in der Nachricht auf
0,80 - 0,90 die Bestaetigung im Projekt erscheint
0,90 - 1,00 Halt auf dem Ergebnis
Am Ende wird nichts zurueckgenommen. Der Vorgaenger blendete die neue
Nachricht wieder aus, damit der Schleifenstoss unsichtbar wird -- das
sah aus, als loesche sich eine abgeschickte Nachricht von selbst. Eine
Schleife darf springen, eine Nachricht darf nicht verschwinden. */
const ANIM_NOTIZ = VERLAUF[3].text;
Am Ende wird nichts zurueckgenommen. Ein frueherer Stand blendete die
Aufgabe wieder aus, damit der Schleifenstoss unsichtbar wird -- das sah
aus, als loesche sie sich von selbst. Eine Schleife darf springen. */
/* Das Menue an der Nachricht, aus dem laufenden Team-Chat. Reihenfolge und
Beschriftung wie dort; die Symbole sind unsere, weil dort ein Emoji
steht und wir durchgehend Strichsymbole benutzen. */
const NACHRICHT_MENUE = [
["thumbs-up", "Reagieren"],
["corner-up-left", "Antworten"],
["message-square", "Im Thread antworten"],
["pin", "Anpinnen"],
["square-check", "Aufgabe erstellen"],
["pencil", "Bearbeiten"],
["trash-2", "Löschen"],
];
const MENUE_ZIEL = 4; // "Aufgabe erstellen"
/* Die Aufgabe, die daraus entsteht. Alle vier Felder stehen so in
GeneralTeamChatPage.tsx an der Nachricht m4. */
const ANIM_AUFGABE = ["square-check", "Materialliste prüfen · fällig 31.07.2026",
"Tobias Wagner · offen", "Offen"];
/* Der Mauszeiger. Kein Bestandteil des Werkzeugs, sondern das Mittel, mit
dem eine Bedienung erzaehlt wird -- ohne ihn sieht man ein Menue
aufgehen und weiss nicht, warum. Weiss mit dunkler Kante, damit er auf
dem Verlauf wie auf dem Menue liest. */
const zeiger = (x, y, gr) =>
'<g transform="translate(' + n1(x) + ',' + n1(y) + ') scale(' + n1(gr) + ')" ' +
'filter="url(#hgklein)">' +
'<path d="M0,0 L0,17.5 L4.3,13.4 L7,19.4 L10.1,18 L7.4,12.2 L13,12.2 Z" ' +
'fill="#FFFFFF" stroke="#24242B" stroke-width="1.5" stroke-linejoin="round"/></g>';
/* Der Klick: ein Ring, der einmal aufgeht und dabei verschwindet. */
const klickRing = (x, y, p) => p <= 0 || p >= 1 ? "" :
'<circle cx="' + n1(x) + '" cy="' + n1(y) + '" r="' + n1(7 + 17 * p) +
'" fill="none" stroke="#24242B" stroke-width="' + n1(2.4 * (1 - p)) +
'" opacity="' + n1(.55 * (1 - p)) + '"/>';
/* Das Menue hinter dem Pluszeichen, aus GeneralTeamChatPage.tsx Zeile 275.
Dieselben sieben Eintraege und derselbe Trenner wie auf Slide 4, nur
kleiner gesetzt: hier haengt es im Fenster und steht nicht allein auf
der Slide. side="top" align="start" heisst, es klappt nach oben auf und
sitzt links am Knopf. */
function animMenue(ax, ay, auf, hoverNr) {
const MW = 306, ZH = 34, PAD = 9, TRENN = 11;
const hoehe = 2 * PAD + MENUE.reduce((a, e) => a + (e ? ZH : TRENN), 0);
const x = ax, y = ay - hoehe;
/* Es klappt nach oben auf, weil die Nachricht unten im Fenster haengt --
nach unten waere kein Platz, und ein Menue dreht dort um. */
function animMenue(rechtsX, untenY, auf, hoverNr) {
const MW = 262, ZH = 34, PAD = 8;
const hoehe = 2 * PAD + NACHRICHT_MENUE.length * ZH;
const x = rechtsX - MW, y = untenY - hoehe;
let g = "";
let yy = y + PAD, nr = 0;
for (const e of MENUE) {
if (!e) {
g += '<path d="M' + n1(x + PAD + 4) + ',' + n1(yy + TRENN / 2) + 'H' +
n1(x + MW - PAD - 4) + '" stroke="' + CHAT_RAND + '" stroke-width="1.1"/>';
yy += TRENN; continue;
}
/* Die gewaehlte Zeile bekommt den Grund, den sie im Werkzeug beim
Ueberfahren bekommt: hover:bg-surface-neutral-tertiary. */
NACHRICHT_MENUE.forEach((e, nr) => {
const yy = y + PAD + nr * ZH;
if (nr === hoverNr)
g += '<rect x="' + n1(x + PAD - 1) + '" y="' + n1(yy) + '" width="' +
n1(MW - 2 * PAD + 2) + '" height="' + ZH + '" rx="7" fill="' + FL_SPALTE + '"/>';
g += feather(e[0], x + PAD + 18, yy + ZH / 2, 16, CHAT_TEXT2, 1.9);
g += uiText(x + PAD + 36, yy + ZH / 2 + 5.5, e[1], 15, 500, CHAT_TEXT);
yy += ZH; nr++;
}
});
/* Es faehrt aus dem Knopf heraus auf: von unten links skaliert, damit es
am Pluszeichen zu haengen scheint. */
const sk = .94 + .06 * auf;
return { hoehe, html:
'<g opacity="' + n1(Math.min(1, auf * 3.2)) + '" transform="translate(' +
n1(x) + ',' + n1(ay) + ') scale(' + n1(sk) + ') translate(' + n1(-x) + ',' +
n1(-ay) + ')">' +
'<g filter="url(#hgschatten)"><path d="' + rundRechteck(x, y, MW, hoehe, 14) +
'" fill="#FFFFFF" stroke="' + CHAT_RAND + '" stroke-width="1.2"/></g>' +
g + '</g>',
// Mitte der Zeile "Aufgabe erstellen", fuer den Zeiger.
zielY: y + PAD + 5 * ZH + TRENN + ZH / 2, zielX: x + MW / 2 };
return { hoehe,
zielX: x + PAD + 30,
zielY: y + PAD + MENUE_ZIEL * ZH + ZH / 2,
html:
'<g opacity="' + n1(Math.min(1, auf * 4.5)) + '" transform="translate(' +
n1(rechtsX) + ',' + n1(untenY) + ') scale(' + n1(sk) + ') translate(' +
n1(-rechtsX) + ',' + n1(-untenY) + ')">' +
'<g filter="url(#hgschatten)"><path d="' + rundRechteck(x, y, MW, hoehe, 14) +
'" fill="#FFFFFF" stroke="' + CHAT_RAND + '" stroke-width="1.2"/></g>' +
g + '</g>' };
}
function animChat(j, t) {
/* 620 x 740: hoeher waere das Fenster oben leer, weil der Verlauf unten
haengt, und die Slide stiesse an den Fliesstext. Was nicht mehr
hineinpasst, schiebt sich oben hinaus -- so macht es ein Chat auch. */
const B = 620, H = 740, RAND = 22, KOPF = 74, FELD = 96;
const innenB = B - 2 * RAND;
const bahnO = KOPF + 8, bahnU = H - FELD - 8;
const dauer = (ANIMS[j] && ANIMS[j].dauer) || 8;
const tippen = stufe(t, .05, .36);
const zeigerAn = stufe(t, .36, .43); // Zeiger faehrt zum Plus
const klick1 = stufe(t, .43, .48);
const auf = daempfen(stufe(t, .45, .56)); // Menue klappt auf
const wandern = daempfen(stufe(t, .56, .68)); // Zeiger zur Zeile
const klick2 = stufe(t, .68, .73);
const zu = daempfen(stufe(t, .71, .76)); // Menue schliesst
/* Die Blase setzt an, waehrend sich das Feld leert. Ein Zwischenstand
hatte dazwischen eine Viertelsekunde Leere: die Notiz war weg, die
Nachricht noch nicht da -- das las sich, als sei sie verschluckt
worden. Die beiden Bewegungen greifen jetzt ineinander. */
const wachsen = daempfen(stufe(t, .72, .84)); // Nachricht kommt herein
const system = daempfen(stufe(t, .84, .92));
const menueAn = Math.max(0, auf - zu);
const zeigerAn = stufe(t, .10, .22);
const pfeil = daempfen(stufe(t, .16, .24)); // der Pfeil an der Blase
const klick1 = stufe(t, .22, .27);
const auf = daempfen(stufe(t, .26, .40));
const wandern = daempfen(stufe(t, .40, .54));
const klick2 = stufe(t, .56, .61);
const zu = daempfen(stufe(t, .60, .66));
const klappen = daempfen(stufe(t, .62, .80)); // die Aufgabe klappt auf
const system = daempfen(stufe(t, .80, .90));
const menueAn = Math.max(0, auf - zu);
let g = "";
@@ -3397,110 +3419,86 @@ if (location.search.indexOf("nur=anim") >= 0)
g += uiText(RAND + 52, RAND + 30, "P-2026-0042 · 8 Mitglieder · aktiv", 15, 500, CHAT_TEXT3);
g += '<path d="M0,' + KOPF + 'H' + B + '" stroke="' + CHAT_RAND + '" stroke-width="1.2"/>';
/* Der Verlauf haengt unten. Waechst er, schiebt er sich nach oben aus dem
Fenster -- so entsteht das Scrollen von selbst, ohne eigenen Zaehler. */
const alt = [VERLAUF[0], VERLAUF[1], VERLAUF[2]];
const neu = VERLAUF[3], sys = VERLAUF[4];
/* Der Verlauf haengt unten. Waechst die Blase, schiebt er sich nach oben
aus dem Fenster -- so entsteht das Scrollen von selbst. */
const spalte = innenB;
const hAlt = alt.map(m => blaseHoehe(m, spalte));
const hNeu = blaseHoehe(neu, spalte) * wachsen;
const m1 = VERLAUF[0], m2 = VERLAUF[1];
// Tobias' Nachricht bekommt die Aufgabe, die aus ihr entsteht.
const m3 = Object.assign({}, VERLAUF[2], { anhang2: ANIM_AUFGABE, a2: klappen });
const sys = VERLAUF[4];
const h1 = blaseHoehe(m1, spalte), h2 = blaseHoehe(m2, spalte);
const h3 = blaseHoehe(m3, spalte);
const hSys = blaseHoehe(sys, spalte) * system;
const LUFT = 16;
const gesamt = hAlt.reduce((a, h) => a + h + LUFT, 0) +
(wachsen > .01 ? hNeu + LUFT : 0) + (system > .01 ? hSys + LUFT : 0);
const gesamt = h1 + h2 + h3 + 3 * LUFT + (system > .01 ? hSys + LUFT : 0);
let y = bahnU - gesamt;
let bahn = "";
alt.forEach((m, i) => { bahn += blase(RAND, y, spalte, m); y += hAlt[i] + LUFT; });
bahn += blase(RAND, y, spalte, m1); y += h1 + LUFT;
bahn += blase(RAND, y, spalte, m2); y += h2 + LUFT;
/* Die gewaehlte Nachricht. Ihre Lage wird gemerkt, damit Pfeil, Zeiger
und Menue daran haengen und nicht an einer zweiten Rechnung. */
const dritteY = y;
const ma3 = blaseMasse(m3, blaseBreite(spalte));
const bx3 = RAND + 52, br3 = ma3.breite;
bahn += blase(RAND, y, spalte, m3);
y += h3 + LUFT;
if (wachsen > .01) {
/* Sie kommt von unten und richtet sich auf: skaliert um den eigenen
unteren Rand, damit sie aus dem Eingabefeld heraus zu wachsen
scheint und nicht aus der Mitte heraus aufplatzt. Text und
Aufgabenkarte kommen zusammen -- es ist eine Nachricht, und der
Klick auf "Aufgabe erstellen" hat beides erzeugt. */
const sk = .88 + .12 * wachsen;
bahn += '<g opacity="' + n1(Math.min(1, wachsen * 1.7)) + '" transform="translate(0,' +
n1((1 - wachsen) * 30) + ') translate(' + n1(B / 2) + ',' + n1(y + hNeu) +
') scale(' + n1(sk) + ') translate(' + n1(-B / 2) + ',' + n1(-(y + hNeu)) + ')">' +
blase(RAND, y, spalte, neu) + '</g>';
y += hNeu + LUFT;
}
if (system > .01) {
bahn += '<g opacity="' + n1(system) + '" transform="translate(0,' +
n1((1 - system) * 12) + ')">' + blase(RAND, y, spalte, sys) + '</g>';
}
/* Der Pfeil an der Blase, wie im Werkzeug: er erscheint, sobald die
Nachricht angefasst wird, und sitzt oben rechts in ihr. */
if (pfeil > .01 && zu < .5) {
const px = bx3 + br3 - 18, py = dritteY + 16;
bahn += '<g opacity="' + n1(pfeil * (1 - stufe(t, .60, .66))) + '">' +
'<circle cx="' + n1(px) + '" cy="' + n1(py) + '" r="12" fill="' + FL_SPALTE + '"/>' +
feather("chevron-down", px, py + 1, 14, CHAT_TEXT2, 2.2) + '</g>';
}
// Was ueber die Bahn hinausragt, wird oben abgeschnitten -- wie im Fenster.
g += '<clipPath id="animbahn"><rect x="0" y="' + bahnO + '" width="' + B +
'" height="' + (bahnU - bahnO + 6) + '"/></clipPath>';
g += '<g clip-path="url(#animbahn)">' + bahn + '</g>';
/* Das Eingabefeld. Waehrend des Tippens laeuft der Text mit, dahinter
blinkt der Strich; sobald etwas drinsteht, wird aus dem Mikrofon der
Sendeknopf -- so macht es der Composer auch. */
// Das Eingabefeld bleibt ruhig: hier wird nichts geschrieben.
const fy = H - FELD;
const plusX = RAND + 14, plusY = fy + 44;
g += '<path d="M0,' + n1(fy) + 'H' + B + '" stroke="' + CHAT_RAND + '" stroke-width="1.2"/>';
g += feather("plus", plusX, plusY, 21, menueAn > .3 ? CHAT_TEXT : CHAT_TEXT2, 2);
g += feather("plus", RAND + 14, fy + 44, 21, CHAT_TEXT2, 2);
const feldX = RAND + 36, feldB = B - 2 * RAND - 100;
g += '<g filter="url(#hgklein)"><rect x="' + n1(feldX) + '" y="' + n1(fy + 24) +
'" width="' + n1(feldB) + '" height="40" rx="20" fill="' + FL_KARTE + '" stroke="' +
CHAT_RAND + '" stroke-width="1.2"/></g>';
g += uiText(feldX + 18, fy + 50, "Nachricht schreiben …", 16, 400, "#C9C9CE");
g += feather("mic", B - RAND - 24, fy + 44, 21, CHAT_TEXT2, 2);
// Nach dem Klick auf "Aufgabe erstellen" geht die Notiz mit raus.
const abgeschickt = zu > .45;
const zeichen = Math.round(ANIM_NOTIZ.length * tippen);
const getippt = abgeschickt ? "" : ANIM_NOTIZ.slice(0, zeichen);
if (getippt) {
const passt = umbrechen(getippt, feldB - 44, 16, 400, 1)[0];
g += uiText(feldX + 18, fy + 50, passt, 16, 400, CHAT_TEXT);
// Der Strich blinkt zweimal je Sekunde, gerechnet auf die Laufzeit.
if (tippen < 1 && Math.floor(t * dauer * 2) % 2 === 0) {
const bx = feldX + 20 + messen(passt, 16, 400);
g += '<rect x="' + n1(bx) + '" y="' + n1(fy + 36) + '" width="2" height="18" ' +
'fill="' + CHAT_TEXT + '"/>';
}
} else {
g += uiText(feldX + 18, fy + 50, "Nachricht schreiben …", 16, 400, "#C9C9CE");
}
if (getippt) {
const px = B - RAND - 24;
g += '<circle cx="' + n1(px) + '" cy="' + n1(plusY) + '" r="19" fill="#24242B"/>';
g += feather("send", px - 1, plusY, 18, "#FFFFFF", 2);
} else {
g += feather("mic", B - RAND - 24, plusY, 21, CHAT_TEXT2, 2);
}
/* Das Menue haengt am Pfeil und liegt ueber allem. */
const chevX = bx3 + br3 - 18, chevY = dritteY + 16;
const m = animMenue(chevX + 14, chevY - 16, Math.max(auf, .001),
wandern > .55 && menueAn > .2 ? MENUE_ZIEL : -1);
/* Unter drei Prozent ist es kein Aufblenden mehr, sondern ein Schemen
ueber dem Verlauf. Dann lieber gar nichts. */
if (menueAn > .03) g += m.html;
/* Das Menue liegt ueber allem, auch ueber dem Eingabefeld -- es haengt am
Pluszeichen und klappt nach oben auf. Die Zeile "Aufgabe erstellen"
hebt sich, sobald der Zeiger dort angekommen ist. */
const m = animMenue(RAND + 2, fy - 6, Math.max(auf, .001),
wandern > .55 && menueAn > .2 ? 5 : -1);
if (menueAn > .01) g += m.html;
/* Der Zeiger. Er kommt von rechts unten herein, faehrt auf das
Pluszeichen, spaeter auf die Zeile, und geht nach dem zweiten Klick
wieder hinaus. */
/* Nach dem zweiten Klick geht er zuegig weg. Ein Zeiger, der ohne
Aufgabe ueber dem Verlauf stehen bleibt, zieht den Blick auf sich,
waehrend gerade die Nachricht ankommt. */
const zWeg = stufe(t, .72, .77);
/* Der Zeiger: herein auf den Pfeil, dann auf die Zeile, dann weg. */
const zWeg = stufe(t, .61, .67);
const zAn = zeigerAn * (1 - zWeg);
if (zAn > .01) {
const vonX = B - 70, vonY = H + 40;
const vonX = B - 60, vonY = H + 40;
const p1 = daempfen(zeigerAn);
let zx = vonX + (plusX + 2 - vonX) * p1;
let zy = vonY + (plusY + 2 - vonY) * p1;
let zx = vonX + (chevX - 2 - vonX) * p1;
let zy = vonY + (chevY - 2 - vonY) * p1;
if (wandern > 0) {
const p2 = wandern;
zx += (m.zielX - 40 - zx) * p2;
zy += (m.zielY - 6 - zy) * p2;
zx += (m.zielX - zx) * wandern;
zy += (m.zielY - 4 - zy) * wandern;
}
g += klickRing(zx - 2, zy - 2, klick1 < 1 ? klick1 : 0);
g += klickRing(zx - 2, zy - 2, klick2 < 1 && klick2 > 0 ? klick2 : 0);
g += klickRing(zx + 2, zy + 2, klick1 < 1 ? klick1 : 0);
g += klickRing(zx + 2, zy + 2, klick2 > 0 && klick2 < 1 ? klick2 : 0);
g += '<g opacity="' + n1(Math.min(1, zeigerAn * 2) * (1 - zWeg)) + '">' +
zeiger(zx, zy, 1.5) + '</g>';
}