Skip to content

Commit 1aeb457

Browse files
jonfroehlichclaude
andcommitted
fix(carousel): eliminate banner "blink" on crossfade switch (#1010)
The hero/banner carousel used a symmetric opacity crossfade: on switch the outgoing slide faded 1->0 while the incoming faded 0->1 at the same time. Each slide is an opaque unit (black bg + cover image), so while both are semi-transparent mid-transition, up to ~25% of #main-carousel's bright magenta gradient backdrop bleeds through at the midpoint -- a visible pink/magenta "blink" when banners switch. Make the crossfade dip-free by splitting the transition: the incoming slide fades in over a still-opaque outgoing slide, which only snaps to 0 (0s change, delayed by the fade duration) after it's fully covered. An opaque slide always covers the backdrop, so there is zero bleed-through, while the 0.6s crossfade look is preserved. Reduced-motion still gets an instant cut. This is the residual half of #1010: the original 2022 report predated the Bootstrap->vanilla rewrite (#1288). The decode-lag trigger (huge undecoded images) was already fixed by server-side thumbnailing; this addresses the crossfade backdrop-bleed that the rewrite carried over. Verified with a headless-browser A/B at the transition midpoint: the old symmetric fade shows a magenta wash (mean RGB blue channel spikes ~+45); the new asymmetric fade shows a clean photo-to-photo blend. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 543f950 commit 1aeb457

1 file changed

Lines changed: 15 additions & 2 deletions

File tree

website/static/website/css/carousel_fade.css

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@
1010
* (.next/.prev/.left/.right); that machinery went away with the Bootstrap
1111
* carousel JS (Track A — issues #1288 / #1253), so the fade is now self-
1212
* contained CSS rather than a hack layered on Bootstrap's animation.
13+
*
14+
* Dip-free crossfade (issue #1010): the transition is asymmetric on purpose.
15+
* The INCOMING slide fades in (opacity 0 -> 1) while the OUTGOING slide holds
16+
* at full opacity, then snaps to 0 only after the incoming has fully covered
17+
* it. This guarantees an opaque slide always covers the carousel's magenta
18+
* gradient backdrop (#main-carousel in base.css). A naive *symmetric* crossfade
19+
* (both slides fading at once) lets up to ~25% of that backdrop bleed through
20+
* at the midpoint, which read as a "blink"/flash when switching banners.
1321
*/
1422

1523
.carousel-fade .carousel-inner > .item {
@@ -23,19 +31,24 @@
2331
z-index: 0;
2432
/* Only the visible slide should capture clicks. */
2533
pointer-events: none;
26-
transition: opacity 0.6s ease-in-out;
34+
/* Deactivating: hold opacity (0s change) until the incoming slide has faded
35+
fully in, i.e. delay the snap-to-0 by the fade duration. */
36+
transition: opacity 0s ease-in-out 0.6s;
2737
}
2838

2939
.carousel-fade .carousel-inner > .item.active {
3040
opacity: 1;
3141
z-index: 1;
3242
pointer-events: auto;
43+
/* Activating: fade in over the still-opaque outgoing slide. */
44+
transition: opacity 0.6s ease-in-out 0s;
3345
}
3446

3547
/* Respect users who prefer reduced motion: switch instantly, no crossfade.
3648
(carousel.js also disables autoplay under this preference.) */
3749
@media (prefers-reduced-motion: reduce) {
38-
.carousel-fade .carousel-inner > .item {
50+
.carousel-fade .carousel-inner > .item,
51+
.carousel-fade .carousel-inner > .item.active {
3952
transition: none;
4053
}
4154
}

0 commit comments

Comments
 (0)