This is a submission for Frontend Challenge - Comfort Food Edition, Perfect Landing
What I Built
In Brazil, nobody says "McDonald's". They say Méqui.
On August 30th, 2019, the company quietly changed the sign on two restaurants to read MÉQUI, issued no press release, and let the country figure it out. The mystery spread on its own. The nickname stuck so hard it went on the packaging, the tray liners and the menu.
So the comfort food I picked isn't a dish. It's the burger every Brazilian kid grew up begging for on a Sunday — and the 46 years of history stacked behind it.
The page is one continuous scroll: a hero, a giant SVG burger that comes apart layer by layer as it travels down the screen, then a timeline from the first store in Copacabana in 1979 to the day the sign changed.
It's written in Portuguese on purpose. The whole piece is about a country renaming a global brand in its own accent — translating it would defeat the point.
Demo
The burger arrives assembled:
…and comes apart as it descends, labelling itself on the way down:
The one trick worth stealing
The separation is a CSS scroll-driven animation anchored to a named view-timeline. The section is 460svh tall and holds a position: sticky child:
.stage { view-timeline-name: --burger; block-size: 460svh; }
.stage__pin { position: sticky; inset-block-start: 0; block-size: 100svh; }
.bx-layer {
animation: peel linear both;
animation-timeline: --burger;
animation-range: contain var(--a) contain var(--b);
}
The part that made it click: for a subject taller than the viewport, the contain range runs from "the section's top edge meets the top of the screen" to "its bottom edge meets the bottom of the screen" — which is, point for point, exactly the window in which the sticky child is pinned. So contain 0% → contain 100% means "animate only while the burger is parked on screen", with no magic numbers.
Each layer gets its own range, offset 5% from the one above. That stagger is the whole reason the sandwich opens top-down instead of all at once.
One more thing that surprised me: no timeline-scope is needed. The SVG layers are descendants of .stage, and timeline name lookup already walks up the ancestors.
Accessibility and the Firefox problem
Scroll-driven animations are Baseline: limited — Chrome/Edge 115+ and Safari 26+ have them; Firefox is still preview-only. So every scroll-driven rule lives inside @supports (animation-timeline: view()), with a not (...) branch that collapses the 460svh section back to auto height, un-sticks the pin, and shows the burger assembled with all labels visible.
I tested that branch by forcing the @supports query to fail: nothing is hidden, and nobody scrolls through 460svh of nothing.
Other things I cared about:
-
prefers-reduced-motionkills the PSX jitter, the fog and the scanlines. The layer separation stays, because it's the content of the section and the user drives it directly — but the travel is damped to 40% so it isn't a full-screen sweep. - On narrow screens the side labels don't fit, so they disappear and the layers come back as a real text list at the bottom of the stage.
- Skip link, semantic landmarks, no heading-level skips, gold focus rings that survive every background in the palette,
scroll-marginso anchor targets don't hide behind the fixed header. - The burger SVG is
role="img"with a<title>/<desc>; the fog, dither and scanline overlays arearia-hiddenandpointer-events: none.
Journey
Two bugs are worth confessing.
The wordmark rendered black. I had background-clip: text for the cel-shading bands plus a filter: drop-shadow() stack for the outline. Chrome loses the text clip when a filter is on the same element. Fix: split the job — solid colour and a text-shadow ring on the base element, gradient clip on an ::after with content: attr(data-txt).
Horizontal scroll on mobile. The stage's grid column was sizing to the SVG's natural width — 509px inside a 369px viewport. I reached for overflow-x: clip on html and it did not stop the viewport scrolling. The actual fix was grid-template-columns: minmax(0, 1fr), because auto tracks grow to max-content. Clipping was treating a symptom.
The whole page ships one line of runtime JavaScript — the import of the CSS that Vite needs as an entry module. Everything else, including the burger, is CSS.
MIT licensed. Fork it, take the contain trick, ignore my colour choices.


Top comments (0)