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:
1 parent
d4feaa175b
commit
a8f79c1224
4 files changed
+227
-215
No files matched your search
+166
-168
@@ -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>';
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user