From 194aee269e36db1391538ff3b006ef4cbca02112 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sat, 18 Apr 2026 15:06:15 +0200 Subject: [PATCH] feat(recipe): Pulse-Animation beim Aktivieren Favorit/Wunschliste MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kurzer Scale-Bounce plus ausklingender Ring in der Aktionsfarbe (rot für Favorit, grün für Wunschliste), sobald der Button eine Markierung setzt. Beim Wieder-Abwählen bleibt es ruhig — hilft die Bestätigung visuell abzusetzen. Die Animation wird per tick()-Zwischenschritt (false → tick → true) gestartet, damit mehrfache Klicks innerhalb weniger hundert ms die Animation neu triggern. prefers-reduced-motion schaltet den Effekt aus. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/routes/recipes/[id]/+page.svelte | 71 ++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 5 deletions(-) diff --git a/src/routes/recipes/[id]/+page.svelte b/src/routes/recipes/[id]/+page.svelte index 4881d92..f23fa09 100644 --- a/src/routes/recipes/[id]/+page.svelte +++ b/src/routes/recipes/[id]/+page.svelte @@ -41,6 +41,24 @@ let saving = $state(false); let recipeState = $state(data.recipe); + // Einmalige Pulse-Animation beim Aktivieren (nicht beim Wieder-Abwählen). + // Per tick()-Zwischenschritt "aus → an" erzwingen, damit die Animation + // auch bei mehrmaligem Klick innerhalb weniger hundert ms neu startet. + let pulseFav = $state(false); + let pulseWish = $state(false); + + async function firePulse(which: 'fav' | 'wish') { + if (which === 'fav') { + pulseFav = false; + await tick(); + pulseFav = true; + } else { + pulseWish = false; + await tick(); + pulseWish = true; + } + } + async function saveRecipe(patch: { title: string; description: string | null; @@ -128,15 +146,17 @@ return; } const profileId = profileStore.active.id; - const method = isFav ? 'DELETE' : 'PUT'; + const wasFav = isFav; + const method = wasFav ? 'DELETE' : 'PUT'; await fetch(`/api/recipes/${data.recipe.id}/favorite`, { method, headers: { 'content-type': 'application/json' }, body: JSON.stringify({ profile_id: profileId }) }); - favoriteProfileIds = isFav + favoriteProfileIds = wasFav ? favoriteProfileIds.filter((id) => id !== profileId) : [...favoriteProfileIds, profileId]; + if (!wasFav) void firePulse('fav'); } async function logCooked() { @@ -258,7 +278,8 @@ return; } const profileId = profileStore.active.id; - if (onMyWishlist) { + const wasOn = onMyWishlist; + if (wasOn) { await fetch(`/api/wishlist/${data.recipe.id}?profile_id=${profileId}`, { method: 'DELETE' }); @@ -272,6 +293,7 @@ wishlistProfileIds = [...wishlistProfileIds, profileId]; } void wishlistStore.refresh(); + if (!wasOn) void firePulse('wish'); } // Wake-Lock — Bildschirm beim Kochen nicht dimmen lassen. @@ -387,11 +409,23 @@ {/if}
- -