diff --git a/web/frontend/index.html b/web/frontend/index.html index e8f6bcb..2e2a1f2 100644 --- a/web/frontend/index.html +++ b/web/frontend/index.html @@ -624,23 +624,46 @@ function moveNavigation(to) { const after = island.getBoundingClientRect(); if (!before.width || !after.width) return; + const dx = before.left - after.left; + const dy = before.top - after.top; + const sx = before.width / after.width; + const sy = before.height / after.height; + + // **Zwei Züge, nicht einer.** + // + // Ein reiner Weg von A nach B sieht aus, als würde ein Kasten verschoben. + // Schöner ist, was die Leiste erst *in sich* zusammenziehen lässt und dann + // erst umziehen — wie ein Gummiband, das sich sammelt, bevor es schnellt. + // + // Welche Richtung sich zuerst sammelt, hängt davon ab, wohin es geht: + // Wer nach **oben** zieht, wird flach — also klappt zuerst die Höhe + // zusammen, und die Breite kommt erst beim Hinüberschieben. Wer an die + // **Seite** zieht, wird schmal — also zuerst die Breite, die Höhe danach. + // + // Die Zwischenstation steht deshalb noch an der alten Stelle, aber schon + // mit dem Zielmaß in der einen Richtung. + const gathered = to === 'top' + ? `translate(${dx}px, ${dy}px) scale(${sx}, 1)` // Höhe zuerst + : `translate(${dx}px, ${dy}px) scale(1, ${sy})`; // Breite zuerst + island.animate( [ - { - transformOrigin: 'top left', - transform: `translate(${before.left - after.left}px, ${before.top - after.top}px)` - + ` scale(${before.width / after.width}, ${before.height / after.height})`, - }, - { transformOrigin: 'top left', transform: 'none' }, + { transformOrigin: 'top left', transform: `translate(${dx}px, ${dy}px) scale(${sx}, ${sy})`, + easing: 'cubic-bezier(.5,0,.75,0)', offset: 0 }, + // Gesammelt — noch am alten Ort, aber schon im Zielmaß der einen Seite. + { transformOrigin: 'top left', transform: gathered, + easing: 'cubic-bezier(.22,1,.36,1)', offset: 0.42 }, + // Und hinüber. Das leichte Überschwingen am Ende ist das Gummiband. + { transformOrigin: 'top left', transform: 'none', offset: 1 }, ], - { duration: 420, easing: 'cubic-bezier(.32,.72,0,1)' } + { duration: 520 } ); // Die Beschriftungen gegen die Stauchung aufblenden, damit aus dem // Verziehen kein Lesen wird. island.animate( - [{ opacity: 0.55 }, { opacity: 1 }], - { duration: 260, easing: 'ease-out' } + [{ opacity: 0.35 }, { opacity: 0.5, offset: 0.42 }, { opacity: 1 }], + { duration: 420, easing: 'ease-out' } ); }