Skip to content

/showcases · Brand

Hero motion loop — from text prompt to 460 KB production asset

How we generated, encoded, and integrated the AudioLab.tools hero video — a subtle 5-second cyan-ribbon breathing loop. End-to-end pipeline including Higgsfield seedance generation, ffmpeg WebM + H.264 encoding, image fallback, and prefers-reduced-motion handling.

June 4, 2026 10 min read
Higgsfield seedance 2.0 ffmpeg libvpx-vp9 libx264 Astro Image

Outcome

460 KB total delivered (WebM + MP4). 5 seconds of subtle motion. Static image fallback for slow networks. Removed entirely for users with prefers-reduced-motion.


A motion hero on a tech website is a high-risk move. Get it wrong and it’s a distracting autoplaying ad; get it right and it’s the difference between “another developer site” and “premium brand”. The constraints we set:

  • Subtle. The motion is breath, not action. Nothing should grab attention away from the text overlay.
  • Tiny. Under 500 KB total transport, period.
  • Fallback-first. The page should look great with no video at all.
  • Reduced-motion respecting. Users who opted out get nothing animated.

This is how we got there in about 20 minutes of real work.

Step 1: Generate the motion (Higgsfield seedance 2.0)

Seedance is the right model for this — image-to-video, identity-preserving, with strong support for subtle motion. We used the same 21:9 cyan-ribbon image we built for the static hero (see brand imagery pipeline) as the reference frame.

{
  model: 'seedance_2_0',
  prompt: `The luminous cyan ribbon of light breathes and slowly rotates
           in pitch-black space, with subtle volumetric particles drifting
           through the frame. Very slow, gentle motion — the ribbon appears
           to inhale and exhale, with soft glow pulses. Camera completely
           static. The lighting and composition match the reference image
           exactly. Premium, calm, atmospheric, ambient. No camera movement,
           no cuts, just subtle organic motion of the light ribbon itself.`,
  duration: 5,
  resolution: '720p',
  aspect_ratio: '21:9',
  mode: 'std',
  medias: [{ value: heroImageUrl, role: 'image' }],
}

Two prompt constraints that made the output usable:

  1. “Camera completely static.” Without this, seedance defaults to a slow push-in that looks like every other tech video.
  2. “No camera movement, no cuts, just subtle organic motion.” Reinforces 1 and prevents the model from adding stylistic cuts.

Cost: 22 credits. Render time: ~70 seconds.

Step 2: Strip the audio

Seedance outputs an MP4 with audio. We don’t need it — the hero is silent, and any audio would mean we couldn’t autoplay across most browsers anyway.

ffmpeg -i hero.mp4 -an -c:v copy hero-noaudio.mp4

The -an flag strips audio; -c:v copy skips re-encoding the video. Fast pass, no quality loss. We use this output as the source for both subsequent encodes.

Step 3: Encode VP9 WebM (modern browsers)

VP9 in a WebM container compresses better than H.264 and is the right choice for browsers that support it (~90 % of traffic).

ffmpeg -i hero.mp4 -an \
  -c:v libvpx-vp9 \
  -b:v 800k -crf 36 \
  -row-mt 1 -tile-columns 2 -threads 4 \
  -deadline good -cpu-used 2 \
  hero.webm

Settings worth flagging:

  • -crf 36 — quality target. 36 is on the aggressive side; the source material (a slow-moving abstract light ribbon over black) compresses extremely well, so we can afford it.
  • -b:v 800k — bitrate ceiling. Stops VP9 from over-allocating on the few frames with motion.
  • -row-mt 1 -tile-columns 2 -threads 4 — multi-threaded encoding. Faster local encoding without affecting output quality.
  • -deadline good -cpu-used 2 — quality-vs-speed trade-off. good/2 is the sweet spot for one-off assets.

Output: 252 KB for 5 seconds at 720p.

Step 4: Encode H.264 MP4 (Safari fallback)

Safari (and some older browsers) doesn’t support VP9. We need an H.264 MP4 as fallback.

ffmpeg -i hero-noaudio.mp4 -an \
  -c:v libx264 -crf 28 -preset slow \
  -movflags +faststart -pix_fmt yuv420p \
  hero.mp4

Settings:

  • -crf 28 — slightly more conservative than the WebM crf because H.264 doesn’t compress this material as efficiently.
  • -preset slow — better compression. We’re encoding once, not in real time.
  • -movflags +faststart — moves the MP4 metadata atom to the front so the browser can start playback before the whole file downloads.
  • -pix_fmt yuv420p — required for Safari compatibility on iOS.

Output: 209 KB for 5 seconds at 720p.

Step 5: Wire up the markup

<section class="relative isolate overflow-hidden">
  {/* Static image fallback — renders first, hidden when video plays */}
  <Image
    src={heroWideImg}
    alt=""
    widths={[1280, 1920, 2560, 3168]}
    sizes="100vw"
    loading="eager"
    fetchpriority="high"
    class="absolute inset-0 -z-10 h-full w-full object-cover"
  />

  {/* Motion video — slow ambient loop */}
  <video
    class="absolute inset-0 -z-10 h-full w-full object-cover opacity-0
           transition-opacity duration-700 motion-reduce:hidden"
    id="hero-video"
    autoplay loop muted playsinline
    preload="metadata"
    aria-hidden="true"
  >
    <source src="/media/hero.webm" type="video/webm" />
    <source src="/media/hero.mp4" type="video/mp4" />
  </video>

  {/* Contrast overlays + text layer */}
  ...
</section>

<script is:inline>
  // Fade the video in once it actually starts playing
  const v = document.getElementById('hero-video');
  v?.addEventListener('playing', () => { v.style.opacity = '1'; });
  if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) v?.remove();
</script>

Three patterns worth calling out:

  1. Image renders first, video fades in. The user sees a beautiful static hero immediately. Only once the video has decoded a frame and started playing does it fade in over the image. Zero janky black flash, zero perceived load time.
  2. motion-reduce:hidden Tailwind class — declarative reduced-motion handling.
  3. Imperative removal in the inline script — if the user has reduced motion enabled, the <video> element is removed from the DOM entirely. No network request, no decode, no power draw.

What broke (briefly)

  • First WebM encode was 800 KB. Too big. Bumped -crf from 30 to 36 and -cpu-used from 4 to 2; got to 252 KB.
  • Initial autoplay issues on iOS. Forgot playsinline. iOS requires it for inline video; without it, the video tries to go fullscreen on play.
  • MP4 was 1.2 MB before re-encoding. Seedance output is faithful but bigger than it needs to be for web. The re-encode pass with -crf 28 cut it to 209 KB.

Outcome

Total transport: 460 KB worst-case (both files cached after first load). Visual signature: a luminous, breathing cyan ribbon that elevates the entire homepage. Time from idea to production: ~20 minutes.

The general principle: when you ship motion on a brand hero, do the encoding work. The model output is the source asset, not the production asset.