Lab buildShopify / Dawn 15.4.1 · 2026

Scroll drives the playhead.

A full-bleed product section where scrolling scrubs a helmet video frame by frame, with timed callouts pinned to features on the helmet itself. Built as three new files with nothing in the base theme touched, and with the one dependency that could have sunk it verified before a line of the section was written.

Six frames of the helmet video from rotation through locked position to x-ray view, above a scroll timeline marking tooltip windows
The source clip runs 6.567 seconds and does three distinct things. That structure dictated the whole authoring model: the helmet rotates for the first two seconds, so no callout can appear before 2.00s without sliding off the feature it points at.
Scope

Four ways to build it, three of them wrong

The brief asked for the Apple product-page effect: a video whose playhead follows the scrollbar, holding full-screen while the page moves through it. That reading has a cheap impostor, a video that simply autoplays when it enters the viewport, so the first job was pinning down which one the brief meant. It was the real one.

Dawn already ships a video section, and reusing it was the obvious move. Reading it ruled it out: it wraps the video in deferred-media, which keeps the file inside a template until someone clicks the poster, and it hardcodes controls: true. Scrubbing needs the exact opposite: loaded and buffered up front, no controls, never looking like a player. Extending it meant gutting it.

ApproachWhy it won or lostVerdict
Sticky-pinned video, scroll mapped to currentTimeOne file, authored once, reverses for free: scrolling back up is just a lower number.Built
Canvas image sequenceWhat Apple actually does, and frame-perfect. Also 200+ images to host, preload, and hand to a merchant who will one day want to swap them.Held in reserve
Third-party appNo app renders the custom callout design, which is the entire point of the section.Rejected
Extend Dawn's video sectionClick-to-load, poster-first, controls on. Structurally opposed to scrubbing.Rejected
Plan

Verify the thing that can kill it, first

Scroll-scrubbing rests on one assumption most builds discover far too late: the browser has to be able to seek the file. That needs two things, and both are invisible until they fail.

The MP4 needs its moov atom at the front, the "faststart" flag. Without it a browser must download the entire file before it can seek at all, so the section sits dead through the whole download and then works perfectly. It passes every local test and fails in production. Second, the host has to serve HTTP range requests, or seeking is impossible no matter how the file is built.

The source clip was exported with every frame as a keyframe, which was the right call. Rather than take that on trust, the file was parsed at the box level and the CDN probed directly:

CheckResult
Box orderftypmoovmdat, faststart intact
Sync sample tableNo stss box: all 197 frames are keyframes
Codecavc1 / H.264, 1920×1080, 30 fps
Served by CDN12,542,679 bytes, byte-identical to the export
Range requestHTTP 206, content-range: bytes 0-1023/12542679

That byte count matters more than it looks. Shopify re-encodes anything uploaded through the video picker, which would have destroyed the all-keyframe encode. The file was uploaded to Files instead and came back out unchanged: technique confirmed rather than assumed.

Build

The hard part was never the video

The design uses a full-bleed crop over letterboxing: more cinematic, and safe here because the helmet sits center-frame. But it moves the difficulty somewhere unexpected. Callouts are authored against the video frame, while object-fit: cover crops that frame differently at every window shape. A coordinate measured on one monitor lands somewhere else on another.

So the component measures the video's true rendered box, including the parts cropped off screen, and publishes four custom properties that CSS positions against. It recalculates on resize only, never during scroll, so scrubbing stays free of layout work.

StageVideo fills byRendered boxCropped per side
1440 × 813height1445 × 8132.7px horizontal
2560 × 993width2560 × 1440223.5px vertical

That second row is the catch. On an ultrawide monitor only the middle band of the frame survives, and the helmet fills the frame almost top to bottom, so "the top of the helmet" and "the mouthpiece" are precisely the callouts most at risk. Positions were measured off the footage against a labeled grid, then rendered at both window shapes to confirm they land. The crown callout moved down four points after the ultrawide test showed its marker clipped against the top edge.

The dust filter callout rendered on the helmet frame: a ring marker, connecting rule, and label resolving on one axis
The finished callout, rendered against the real frame at the measured coordinates. Marker center, rule, and label all resolve to a single axis.
Verify

Measured, not eyeballed

Every number below came from driving the real component against the real CDN file in a headless browser: scripted scrolling, pixel fingerprints, network accounting. Not from looking at it and deciding it seemed fine.

0.010s
Worst scrub drift
Across six scroll positions, forward and reverse. Three tenths of one frame.
0 bytes
Video on mobile
Confirmed at 500px, 900px, and at desktop width under reduced motion. Never requested.
8.3px
Scroll per frame
Dense enough to step cleanly without visible skipping.
3 / 0
Files added / changed
One section, one stylesheet, one web component. No base-theme file touched.

What measurement caught that a read-through would not

Four defects survived code review and died under a camera. Two were in the product; two were in the test rig, which is its own lesson: a passing test can be lying.

  • The callout's accent color had no CSS-side default. It was set only from the section's style block, so leaving the color picker empty made border: … solid var(--accent) invalid and the background transparent. Ring, dot, and rule all vanished in silence, leaving labels floating with nothing pointing at anything. Every numeric test still passed.
  • Dawn has no global border-box reset, only three scoped declarations. Padding was adding to max-width, so the mobile image stack rendered 590px wide instead of 560.
  • The test server ignored HTTP range requests, so every "frame" extracted from the video was actually frame zero. Nine identical images looked plausible enough to publish. Pixel fingerprints caught it: an accidental live demo of exactly why the CDN range check mattered.
  • Headless screenshots capture from the document origin, ignoring scroll position, and the virtual clock gives up before video seeking finishes. The workaround, render to a canvas and post the result back, became the measurement rig for everything after it.

One defect worth showing

The callout's ring was a 270° arc with its gap at the upper right, exactly where the rule exits. The rule read as floating beside a broken circle instead of running out of it, and it sat 1.25px below the axis besides.

Before and after comparison of the callout ring: detached arc with a gap turned away from the rule, then a connected half-arc opening toward it
Before: opening turned away from the rule, a 5px gap, the rule hanging half its own thickness below center. After: a true half-arc opening toward the rule, which now begins at the ring's edge and straddles the axis. Mirrors automatically for left-growing callouts.

The mechanism is worth keeping: with border-radius: 50% each border side owns a 90° wedge and they meet on the diagonals, so two transparent adjacent sides give a 180° opening centered on a diagonal. Then rotate(45deg) swings that center round to due east.

Accepted

What it deliberately does not do

Some of these are scope lines drawn on purpose and recorded as such. A case study that lists only wins is a brochure.

  • Mobile gets three stills and no callouts. Phones get a static image stack instead of a 12.5 MB video, and the section never requests the video below 990px.
  • Callouts are confined to the middle 70% of the frame. A direct consequence of choosing crop over letterbox: outside that band they can be cut off entirely on some window shapes.
  • Timings are typed, not dragged. A slider caps at 101 steps, so 0.1s precision would ceiling out around a ten-second clip. Typed numbers have no ceiling, so a longer re-cut later just works.
  • Scroll length is a constant, not a setting. Deliberately withheld from the customizer: it is a feel decision, tuned once against the real clip.
  • Callout positions are specific to this cut. Re-cut the video and both timings and coordinates need re-measuring. Documented rather than discovered.
Footprint

What shipped

sections/section-scroll-video.liquid · assets/section-scroll-video.css · assets/scroll-video.js. One section, one stylesheet, one vanilla web component. No libraries, no base-theme edits, no template changes, no new locale keys. Theme Check clean. The video is never requested below 990px or under reduced motion, where the section falls back to a static image stack.

Next build log: Modern Product Grid →