A hero section on a Shopify-based client site used a background video: a looping product animation the client's animator rendered, stretched to fill the section, with text laid over it. In my own desktop preview it looked seamless. Then the client came back with one complaint that kept repeating across six rounds of revisions: "the white is different", "the video background still shows as a box." The video did have a light backdrop, but that backdrop refused to blend into the section behind it. What showed instead was a faint rectangle in a slightly different shade, as if the video were pasted onto the page like a sticker.
What made it maddening: on some monitors the difference was nearly invisible, on others it was obvious. So every time I thought I had nailed it, the client looked at their screen and the box was still there.
The dead ends I tried first
My first guess: just hide the video with a blend mode. I set mix-blend-mode: multiply on the video element, hoping the video's white pixels would dissolve into the section's white. It got me halfway. multiply does darken the video's light areas against the background, but when the video's backdrop color and the section color are not identical, it actually emphasizes the gap over the subject. The box shrank, it did not disappear, and color artifacts crept into the brighter parts of the product.
Second guess: maybe this is compression. I bumped the bitrate, swapped codecs, re-rendered from source. Still there. That is where I stopped guessing and started measuring.
I grabbed one video frame and one screenshot of the section, then sampled the colors pixel by pixel with Python PIL:
from PIL import Image
frame = Image.open("frame.png").convert("RGB")
print(frame.getpixel((10, 10))) # corner of the video backdropThe numbers cracked it open. The video backdrop read #ECEBEE, while the section background I assumed was pure #ffffff. A tiny gap, but the human eye reads it as a box. Worse, the next source version had an uneven backdrop: the corners were #DFDFDF but the bottom-left corner was #E5E3E4. A subtle gradient that a lanczos upscale and codec quantization actually preserved.
The root cause
Three things stacked into one symptom.
First, the baked-in backdrop inside the video never exactly matched the section background. Whatever color the animator rendered became fixed pixels in the file, and those pixels will not adapt to the section behind them.
Second, mix-blend-mode: multiply only hid half the problem. It does not remove the video's backdrop, it blends it, so any color gap still leaks through.
Third, and the most misleading: I had hardcoded the section background to #ffffff, while the theme color variable actually resolved to #FAFAFA. Pure white reads brighter than the neighboring sections, so even though the video and the section were "both white", they were really three different colors: the video backdrop, the #ffffff I wrote, and the theme's #FAFAFA.
The fix
The key: stop hoping the colors happen to match, and make them match on purpose.
Step one, measure the real colors with PIL before deciding anything. Numbers beat eyeballing.
Step two, chroma-key the video backdrop with ffmpeg, using a tight threshold sized between the backdrop's color variance and the distance to the subject's edge. For MP4 (no alpha), I key the backdrop and overlay it onto a solid color that matches the section exactly:
ffmpeg -i in.mp4 -filter_complex \
"color=c=0xFAFAFA:s=1920x1080[bg]; \
[0:v]colorkey=0xDFDFDF:0.02:0.05[fg]; \
[bg][fg]overlay=format=auto" \
-c:v libx264 -pix_fmt yuv420p out.mp4The numbers in colorkey=0xDFDFDF:0.02:0.05 matter. A similarity of 0.02 gives a threshold band around 8 to 13 RGB: wide enough to catch a backdrop that varies by only ~4 RGB, tight enough that the product edge, sitting 10+ RGB from the backdrop, does not get eaten. Open it too wide and the subject edge goes transparent too.
If you need real alpha for a WebM, key it and force an alpha-carrying format:
ffmpeg -i in.mp4 -vf \
"colorkey=0xEDECEF:0.08:0.05,format=yuva420p" \
-c:v libvpx-vp9 -pix_fmt yuva420p out.webmThen verify the alpha actually landed, do not just assume:
ffprobe -show_streams out.webm | grep alpha_mode
# TAG:alpha_mode=1One important trap: do not chroma-key when the subject color is too close to the backdrop. In one revision the product body was #DDDDDD, only 3 to 4 RGB from the #DFDFDF backdrop. Any key wide enough to erase the backdrop also punches holes in the product. For that case it is safer to skip chroma-key entirely and just match the section color to the video's backdrop.
Finally, on the CSS side, stop hardcoding #ffffff. The section has to use the theme variable, and the video's overlay color has to match what that variable resolves to:
.hero-video-section {
background: var(--color-background); /* resolves to #FAFAFA, never write #ffffff */
}If the theme's color setting changes, re-encode the video to the new color. The video's backdrop is fixed pixels; it will not follow your theme variable.
The takeaway
- Measure colors with PIL before concluding; even a 3 to 4 RGB gap reads as a box to the eye.
mix-blend-mode: multiplyhides, it does not remove. The color gap still leaks.- Size the
colorkeythreshold between backdrop variance and subject-edge distance, not just wide. - Skip chroma-key when the subject is within 3 to 4 RGB of the backdrop; the key will punch holes in it.
- Never hardcode
#ffffffin a section. Usevar(--color-background)and match the video overlay to what it resolves to. - If the theme color changes, re-encode. The video backdrop is baked in, not a variable.
