From db8388c79e44f5d254d984810bd62bfb11effd0f Mon Sep 17 00:00:00 2001 From: Luca <102960244+Luca-Timo@users.noreply.github.com> Date: Sat, 20 Jun 2026 03:07:49 +0200 Subject: [PATCH] fix(slideshow): dip-to-white/black no longer flickers the image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The flash overlay had no base opacity and the keyframe animation has fill-mode none, so after the first dip it reverted to opacity 1 and stayed opaque between slides — hiding the image, then briefly revealing it on each advance. Set base opacity 0, and swap the image at the flash peak so the cut stays hidden. --- frontend/src/pages/gallery/SlideshowPage.tsx | 31 +++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/frontend/src/pages/gallery/SlideshowPage.tsx b/frontend/src/pages/gallery/SlideshowPage.tsx index f7de1c9d..ee1fcb84 100644 --- a/frontend/src/pages/gallery/SlideshowPage.tsx +++ b/frontend/src/pages/gallery/SlideshowPage.tsx @@ -127,15 +127,25 @@ export function SlideshowPage() { if (list.length < 2) return; const next = (positionRef.current + 1) % list.length; positionRef.current = next; - const hidden: 0 | 1 = activeRef.current === 0 ? 1 : 0; - setBuffers((prev) => { - const updated: [number, number] = [...prev] as [number, number]; - updated[hidden] = next; - return updated; - }); - setActive(hidden); + const swap = () => { + const hidden: 0 | 1 = activeRef.current === 0 ? 1 : 0; + setBuffers((prev) => { + const updated: [number, number] = [...prev] as [number, number]; + updated[hidden] = next; + return updated; + }); + setActive(hidden); + }; const tr = settingsRef.current.transition; - if (tr === 'dipwhite' || tr === 'dipblack') setFlash((f) => f + 1); + if (tr === 'dipwhite' || tr === 'dipblack') { + // Dip: start the white/black flash now, and swap the image at the flash + // PEAK (fully opaque) so the cut is hidden — otherwise the new image is + // visible before the flash covers it, which reads as a flicker. + setFlash((f) => f + 1); + window.setTimeout(swap, Math.max(100, settingsRef.current.transition_ms) / 2); + } else { + swap(); + } }, []); // ----- Start: gesture-driven (fullscreen needs a user gesture). ----- @@ -392,6 +402,11 @@ export function SlideshowPage() { inset: 0, pointerEvents: 'none', background: settings.transition === 'dipwhite' ? '#fff' : '#000', + // Base opacity 0 so that BEFORE and AFTER the one-shot animation + // (animation-fill-mode defaults to none) the overlay is fully + // transparent. Without this it reverted to opacity 1 and stayed + // opaque between slides, hiding the image repeatedly. + opacity: 0, animation: `picpeak-dip ${Math.max(200, settings.transition_ms)}ms ease-in-out`, }} />