Same product, different colors — cycles between variants while the entire hero recolors. The previous variant lives as a corner thumbnail. Price, sizes, and copy never move.
Watch the auto-cycle, or click any color dot at the bottom to jump to that variant.
Four variants are stacked absolutely on top of each other inside a .stage container. At any moment one variant has .is-active (centered, full opacity) and the just-departed one has .is-previous (scaled to ~16% of its original size, anchored at the bottom-right via transform-origin). The stage's background color is updated by JS to match the active variant. A floating thumbnail bar at the bottom lets the user jump between variants manually.
The surrounding UI (copy, price, sizes) sits in an .overlay layer with pointer-events: none on the wrapper and auto on individual children — so it stays visible above the variants but doesn't intercept clicks meant for the thumbnails.
.variant {
position: absolute;
inset: 0;
opacity: 0;
transform: scale(1);
transform-origin: 86% 80%; /* shrink anchor = bottom-right of stage */
transition:
opacity 0.55s ease,
transform 0.85s cubic-bezier(0.4, 0, 0.2, 1);
}
.variant.is-active {
opacity: 1;
}
.variant.is-previous {
opacity: 0.95;
transform: scale(0.16); /* shrinks toward the anchor */
}
const colors = ['#c0392b', '#bdc3c7', '#1f1f1f', '#e67e22'];
const variants = document.querySelectorAll('.variant');
const stage = document.getElementById('stage');
const thumbs = document.querySelectorAll('#thumbs button');
let active = 0, prev = null, timer = null, paused = false;
function setActive(next) {
if (next === active) return;
prev = active;
active = next;
variants.forEach((el, i) => {
el.classList.toggle('is-active', i === active);
el.classList.toggle('is-previous', i === prev);
});
thumbs.forEach((b, i) => b.classList.toggle('is-active', i === active));
stage.style.background = colors[active];
}
function advance() { setActive((active + 1) % variants.length); }
function schedule() { if (!paused) timer = setTimeout(() => { advance(); schedule(); }, 4000); }
thumbs.forEach((b, i) => b.addEventListener('click', () => {
setActive(i);
clearTimeout(timer); schedule(); // reset the cycle from this point
}));
setActive(0);
schedule();
The continuity trick. The stationary UI is what sells it. If the price and sizes moved with the variant, the user would read "different product" — but because they stay anchored, the eye reads "same product, new color." That's the whole game.
The corner thumbnail is doing two jobs. Visually, it's a receipt of where you just came from — so the transition makes intuitive sense (this color is replacing that one). Functionally, it shows you can come back, which makes the bottom thumbnail nav feel less like a hard reset.
Auto-cycle plus manual control. Auto-cycling hooks the eye on landing. The thumbnail nav respects the user once they engage. The auto-cycle resumes after a manual click so a curious visitor can still see the full range. Pause button on the demo so you can stop it for screenshots / code reading.
Scroll-driven instead of timed — pin the hero with position: sticky and advance the variant index based on scroll progress through the panel. Same look, replaces the timer with scroll position.
Real product photos instead of SVG — swap the SVG with <img> tags. Use the same dominant color for the bg as the photo's primary hue.
More variants — works for 3–6. Past 6 the corner thumbnails get crowded; switch to a color-swatch row below the price instead.
Subtle background pattern — instead of solid color, use a radial gradient or noise overlay matching the variant hue. Adds depth without breaking the cinematic feel.
Source: Jacket Masters demo at maroon-tapir-346920.hostingersite.com/fashion-puffer-jacket (user-uploaded screen recording, 2026-05-19).