From 2874a89a6eaad807f68bf5e6ca07d1343b6f4200 Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Mon, 14 Sep 2026 16:42:33 +0200 Subject: [PATCH] Die Leiste zieht jetzt um, statt zu springen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nachgebaut nach der App: die Spalte klappt nach oben weg und kommt von oben zurück, die Insel schiebt sich seitwärts hinaus und herein. Der Inhalt rückt gleichzeitig nach — ohne das bliebe links ein Loch stehen, bis die Leiste angekommen ist. Dazwischen eine Pause von gut zwei Zehnteln: ohne sie tauscht der Browser die Leiste in einem Bild aus, und vom Umzug ist nichts zu sehen. Dazu gestaffelt einblendende Reihen und Kacheln, die sich beim Überfahren leicht anheben. Wer "Bewegung reduzieren" eingeschaltet hat, bekommt von alledem nichts. Co-Authored-By: Claude Opus 5 --- web/frontend/index.html | 55 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/web/frontend/index.html b/web/frontend/index.html index 59720c0..cb962c7 100644 --- a/web/frontend/index.html +++ b/web/frontend/index.html @@ -255,6 +255,25 @@ img { display: block; } #app.side .island button { text-align: left; border-radius: var(--r-control); } #app.side .island .sep { width: auto; height: 1px; margin: var(--s-snug); } #app.side #content { margin-left: calc(var(--sidebar-w) + 24px); } + +/* ---- Der Umzug der Leiste ----------------------------------------------- + Nachgebaut nach der App: die Spalte klappt nach **oben** weg und kommt von + oben zurück, die Insel schiebt sich **seitwärts** hinaus und herein. + Zusammen sieht es aus, als zöge dieselbe Leiste um — erst zusammenklappen, + dann hinüberschieben. + + Der Inhalt rückt gleichzeitig nach; ohne das bliebe links ein Loch stehen, + bis die Leiste angekommen ist. */ +.island { transition: transform .3s cubic-bezier(.32,.72,0,1), opacity .22s ease; } +#content { transition: margin-left .32s cubic-bezier(.32,.72,0,1); } + +#chrome.moving .island { opacity: 0; transform: translateX(-115%); } +#app.side #chrome.moving .island { opacity: 0; transform: translateY(-115%); } + +/* Wer Bewegung nicht mag, bekommt keine. */ +@media (prefers-reduced-motion: reduce) { + .island, #content, #chrome, .card .art, .shot { transition: none !important; } +} #app.side .pad-top { padding-top: var(--s-loose); } .island .brand { @@ -298,6 +317,20 @@ input[type=range] { width: 100%; accent-color: var(--accent); } /* ---- Zustände ------------------------------------------------------------ */ #content { padding-bottom: var(--s-section); } + +/* Ein Seitenwechsel soll nicht schlagen. Kurz und flach — was länger dauert, + fühlt sich nach Warten an statt nach Bewegung. */ +@keyframes rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } } +.row, .grid, .settings, #hero { animation: rise .32s cubic-bezier(.32,.72,0,1) both; } +/* Die Reihen kommen gestaffelt — die erste zuerst, dann nach unten weg. */ +.row:nth-of-type(2) { animation-delay: .04s; } +.row:nth-of-type(3) { animation-delay: .08s; } +.row:nth-of-type(4) { animation-delay: .12s; } +.row:nth-of-type(n+5) { animation-delay: .16s; } + +/* Kacheln heben sich beim Überfahren leicht an. */ +.card .art { transition: transform .2s cubic-bezier(.32,.72,0,1), box-shadow .2s ease; } +.card:hover .art { transform: scale(1.04); box-shadow: 0 10px 24px rgba(0,0,0,.5); } .pad-top { padding-top: calc(84px + env(safe-area-inset-top)); } .spinner { width: 26px; height: 26px; border: 2px solid var(--separator); @@ -533,10 +566,28 @@ function buildIsland() { ? '' // Rechteck mit Spalte links — die Leiste zieht an die Seite. : ''; - nav.onclick = () => { + // Erst wegklappen, dann umziehen, dann zurückkommen. Ohne die Pause + // dazwischen tauscht der Browser die Leiste in einem Bild aus, und vom + // Umzug ist nichts zu sehen. + nav.onclick = async () => { + const chrome = el('chrome'); + const quick = matchMedia('(prefers-reduced-motion: reduce)').matches; + + if (!quick) { + chrome.classList.add('moving'); + await new Promise((r) => setTimeout(r, 210)); + } + setPref('nav', P.nav === 'side' ? 'top' : 'side'); buildIsland(); - go(current); + + if (!quick) { + // Ein Bild abwarten, damit die neue Form ihre Ausgangsstellung + // übernimmt, bevor sie hereinfährt — sonst gibt es keinen Weg zu gehen. + requestAnimationFrame(() => requestAnimationFrame(() => { + el('chrome').classList.remove('moving'); + })); + } }; isl.appendChild(nav);