[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-site-config":3,"$fsZG8yWIXxUrstMFIVj4roDt0X5DZe_5A2lmd8HcgCuI":6,"mdc--cdab2e-key":121},{"gaMeasurementId":4,"bookingUrl":5},"G-7BYTDCVDDR","https:\u002F\u002Ftidycal.com\u002Fcmdcntr\u002F30-minute-meeting",{"post":7,"related":99},{"id":8,"slug":9,"title":10,"description":11,"summary":12,"content":13,"coverImageUrl":14,"coverImageAlt":15,"tags":16,"targetKeywords":22,"faq":27,"howTo":40,"demo":64,"featured":67,"tool":68,"status":69,"reviewNote":68,"metaTitle":70,"metaDescription":71,"canonicalUrl":68,"sourceTicketId":68,"agentGenerated":67,"publishedAt":72,"publishedBy":73,"createdAt":74,"updatedAt":72,"authorId":75,"clusterId":76,"ctaId":77,"author":78,"cluster":88,"cta":92,"tools":98},"d84001f0-3817-42c3-b57a-3e40e6c28ce7","css-horizontal-scroll-animation-off-main-thread","CSS Horizontal Scroll Animation That Runs Off the Main Thread","Build a pinned horizontal scroll section with position: sticky and a CSS view timeline. No scroll listener, and the browser can run it off the main thread.","A CSS horizontal scroll animation needs no scroll listener. Pin a flex track with `position: sticky` inside a taller section. Name that section as a view timeline and animate the track's `transform` across its `contain` range. JavaScript only measures the track's width on resize. In Chrome and Safari 26.4+, the browser then moves the track off the main thread.","Pinned horizontal scrolling is the section that holds still while its panels slide sideways as you scroll down. The common recipes use a scroll listener or GreenSock Animation Platform (GSAP) ScrollTrigger. Both recompute the track's position in JavaScript on every frame while you scroll. That holds up until the main thread gets busy. Then the page keeps scrolling and the track falls behind.\n\nI maintain two horizontal scroll engines. One runs on GSAP ScrollTrigger. The other powers a free, open-source Elementor plugin and uses no animation library at all. This article rebuilds the second one as a plain CSS horizontal scroll animation: a sticky track, a view timeline for progress, and JavaScript that only measures on resize. Every snippet below ran together in one page in Chrome 153 and Firefox 156, and I flag the bugs I hit shipping it.\n\n## Why does a scroll-listener horizontal scroll stutter?\n\nA listener-based horizontal scroll runs a loop in your code. On each scroll update it reads the section's position and turns it into progress. Then it writes a new `transform` to the track. GSAP ScrollTrigger is a more careful version of the same loop. Its [documentation](https:\u002F\u002Fgsap.com\u002Fdocs\u002Fv3\u002FPlugins\u002FScrollTrigger\u002F) says it debounces scroll events and updates on the next `requestAnimationFrame`. That is still JavaScript on the main thread.\n\nThe browser scrolls the page on a separate compositor thread, so scrolling itself stays smooth when the main thread is busy. Your loop gets no such help. Say a long task holds the main thread for 200 milliseconds. The page keeps moving under the user's finger. The track freezes, then jumps to catch up. On a quiet demo page you never see it. On a production page with hydration and a tag manager, you will.\n\n![Diagram comparing a scroll listener with a CSS view timeline during a 200 millisecond long task: the listener-driven track freezes and then jumps, while the view timeline track keeps moving on the compositor](\u002Fapi\u002Fblog\u002Fimages\u002Fblog\u002F1\u002Ffc3eda30-f34d-403d-8ca8-071777f6e61e.webp)\n\nScroll-driven animations remove the loop instead of optimizing it. You declare the relationship once: this animation's progress equals this element's position in the scroller. The browser advances the animation itself. When the animated property allows it, that happens on the compositor.\n\nSo the line that matters runs between JavaScript that defines an animation and JavaScript that drives its frames. A Web Animations API (WAAPI) call attached to a scroll timeline can be composited just like a CSS rule. In the trace test below, both were.\n\nIf an AI builder generated your horizontal scroll, search the code for a scroll handler that sets `style.transform`. That handler is the part to replace.\n\n## How do you build a horizontal scroll section in CSS?\n\nPinned horizontal scrolling has two jobs: hold the section still, and move the track sideways while it is held. `position: sticky` does the first one with no JavaScript.\n\nThe structure is `.hs > .hs__track > .hs__panel`. The outer section supplies the vertical scroll distance, and the track sticks to the top of the viewport inside it. Here is the horizontal scroll section CSS:\n\n```css\n.hs {\n  width: 100%;\n  container-type: inline-size;\n  overflow-x: clip;\n  height: calc(100vh + var(--travel, 0px));\n}\n\n.hs__track {\n  position: sticky;\n  top: 0;\n  display: flex;\n  width: max-content;\n  height: 100vh;\n}\n\n.hs__panel {\n  flex: 0 0 auto;\n  width: 100cqw;\n}\n```\n\n`container-type: inline-size` makes `100cqw` equal the section's width, so panels size against the section instead of the window. Panels can be any width; mine range from 60 to 100 percent of the section.\n\n`overflow-x: clip` looks like a detail. It decides whether the pin works at all. `overflow: hidden` turns an element into a scroll container. A sticky element sticks to its nearest scroll container, so the pin silently stops working. `clip` cuts off the overflow without creating one. A support thread I handled traced a dead pin to a theme's `html, body { overflow-x: hidden }`. Either rule alone is harmless, because the browser passes it on to the viewport. Together, the body keeps its own overflow and becomes a scroll container. The pin is gone.\n\nThe track is wider than the section by a distance I call travel. It depends on content, so measure it at startup and whenever either box resizes. Never on scroll:\n\n```js\nconst section = document.querySelector(\".hs\");\nconst track = section.querySelector(\".hs__track\");\n\nfunction measure() {\n  const travel = Math.max(0, track.scrollWidth - section.clientWidth);\n  section.style.setProperty(\"--travel\", `${travel}px`);\n}\n\n\u002F\u002F measure() resizes the section. Doing that inside the observer\n\u002F\u002F callback fires the \"ResizeObserver loop\" error, so wait a frame.\nconst observer = new ResizeObserver(() => requestAnimationFrame(measure));\nobserver.observe(section);\nobserver.observe(track);\nmeasure();\n```\n\nThat `requestAnimationFrame` is there because of a bug. Changing the section's height inside the observer callback fires `ResizeObserver loop completed with undelivered notifications` on `window`. It reproduced in every engine I tried: Chromium 153, WebKit 26.5 and Firefox 156. Deferring the write by one frame fixed it everywhere. The price is one frame painted with the old height.\n\nYou get one pixel of horizontal travel per pixel of vertical scroll. To slow it down, make the section taller without touching the travel. The plugin's scroll length setting uses `height: calc(100vh + var(--travel) * var(--factor, 1))`. Speed lives in the height; the endpoint never changes.\n\n## How do scroll-driven animations map vertical scroll to horizontal movement?\n\nThis is the part people search for as horizontal scroll on vertical scroll. Name a timeline on the section and attach the track's animation to it:\n\n```css\n.hs {\n  view-timeline: --hs block;\n  view-timeline-inset: 0px;\n}\n\n.hs__track {\n  animation: hs-slide linear both;\n  animation-timeline: --hs;\n  animation-range: contain 0% contain 100%;\n}\n\n@keyframes hs-slide {\n  to {\n    transform: translateX(calc(-100% + 100cqw));\n  }\n}\n```\n\nThe timeline's subject is the section. The sticky track does not move relative to the viewport while pinned, so a timeline on the track would stall.\n\n`contain` lines the movement up with the pin. For a subject taller than the scrollport, [MDN defines](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FCSS\u002FGuides\u002FScroll-driven_animations\u002FTimeline_range_names) `contain 0%` as the moment its start edge reaches the start of the scrollport, and `contain 100%` as the moment its end edge reaches the end. With a `100vh` track, those two moments are the start and end of the sticky interval. The animation runs during the pin and nowhere else.\n\nThree details in that block come straight from comments in the engine's source:\n\n- `linear` is load-bearing. The `animation` shorthand defaults to `ease`, which makes the track crawl at both ends of the pin and rush through the middle.\n- Declare the `animation` shorthand before `animation-timeline` and `animation-range`. The shorthand resets both, so putting it second silently detaches the timeline.\n- Set `view-timeline-inset: 0px` explicitly. The default `auto` [uses the scroller's `scroll-padding`](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FCSS\u002FReference\u002FProperties\u002Fview-timeline-inset), and WordPress sets `scroll-padding-top` to the admin bar height. On a logged-in page, the range shifts by 32 pixels.\n\nThe endpoint has no pixel value in it. `-100%` is the track's own width and `100cqw` is the section's, so their sum is always minus the travel. The animation is created once and never rebuilt. Resizing the test page from 1200 to 900 pixels wide moved the end position from -2880 to -2160 pixels on the same animation.\n\nThe native path of [the open-source horizontal scroll engine on GitHub](https:\u002F\u002Fgithub.com\u002Fartkrsk\u002Fhorizontal-scroll-for-elementor) is this CSS, plus a direction multiplier for right-to-left pages. Clone it and run `pnpm test` for the unit tests. Its Playwright spec, `tests\u002Fe2e\u002Fspecs\u002F01-pin-and-scrub.spec.ts`, runs against WordPress in wp-env. It scrolls through the pin and resizes the window. It fails if a ResizeObserver error reaches `window`. GitHub Actions runs it in Chromium for the native path and Firefox for the polyfilled one.\n\n## How do you add more effects to the same timeline?\n\nOnce the section has a named timeline, every extra effect is another animation on it. No second listener, and no progress value copied between callbacks. Each effect also gets the same compositor treatment as the track, as long as it animates `transform` or `opacity`.\n\nElements inside the section can use `--hs` directly, because a named view timeline is visible to the descendants of the element that declares it. This decorative layer drifts 80 pixels while the pin goes from 20 to 60 percent:\n\n```css\n.hs__layer {\n  animation: drift linear both;\n  animation-timeline: --hs;\n  animation-range: contain 20% contain 60%;\n}\n\n@keyframes drift {\n  from { transform: translateX(-40px); }\n  to { transform: translateX(40px); }\n}\n```\n\nEffects tied to one panel need that panel's own window: the moment it enters at the right edge and the moment it leaves at the left. Both follow from numbers `measure()` already has. The inputs are the panel's offset `x` and width `w`, plus the section width `v` and the travel `d`:\n\n```text\nstart = clamp((x - v) \u002F d, 0, 1)\nend   = clamp((x + w) \u002F d, 0, 1)\n```\n\nThe clamp matters at both ends. The first panel is already on screen before the pin starts, so its raw start is negative. The last panel's raw end is above 1. Extend `measure()` to write the clamped values onto each panel as custom properties:\n\n![Diagram of per-panel animation ranges: start is clamp((x minus v) divided by d, 0, 1) and end is clamp((x plus w) divided by d, 0, 1), with the third panel range running from 25% to 100% of the contain range](\u002Fapi\u002Fblog\u002Fimages\u002Fblog\u002F1\u002F147c955b-4132-48fb-a5cf-1524f4ae588c.webp)\n\n```js\nfunction measure() {\n  const travel = Math.max(0, track.scrollWidth - section.clientWidth);\n  section.style.setProperty(\"--travel\", `${travel}px`);\n\n  const viewport = section.clientWidth;\n  const clamp = (n) => Math.min(1, Math.max(0, n));\n\n  for (const panel of track.children) {\n    const start = travel ? clamp((panel.offsetLeft - viewport) \u002F travel) : 0;\n    const end = travel ? clamp((panel.offsetLeft + panel.offsetWidth) \u002F travel) : 1;\n    panel.style.setProperty(\"--panel-start\", `${(start * 100).toFixed(3)}%`);\n    panel.style.setProperty(\"--panel-end\", `${(end * 100).toFixed(3)}%`);\n  }\n}\n```\n\nAny descendant can then animate over its own panel's visibility:\n\n```css\n.hs__panel-media {\n  animation: drift linear both;\n  animation-timeline: --hs;\n  animation-range: contain var(--panel-start) contain var(--panel-end);\n}\n```\n\nThe plugin hands per-panel ranges to other code the same way, as inherited `--arts-hs-panel-start` and `--arts-hs-panel-end` properties written on resize. In my test page, the third panel's range came out as 25% to 100%. Its animation read 0 when the panel's left edge touched the right side of the viewport, and 1 when its right edge left on the other side.\n\nElements outside the section cannot see a timeline named inside it. A fixed progress bar at the top of the page is the usual case. For those, build the same timeline in JavaScript and hand it to WAAPI:\n\n```js\nconst progress = document.querySelector(\".progress\");\n\nif (\"ViewTimeline\" in window) {\n  const timeline = new ViewTimeline({\n    subject: section,\n    axis: \"block\",\n    inset: \"0px\",\n  });\n\n  progress.animate(\n    { transform: [\"scaleX(0)\", \"scaleX(1)\"] },\n    {\n      timeline,\n      rangeStart: \"contain 0%\",\n      rangeEnd: \"contain 100%\",\n      easing: \"linear\",\n      fill: \"both\",\n    }\n  );\n}\n```\n\nThe `\"ViewTimeline\" in window` check keeps the script from throwing where the API is missing. What those browsers show instead is covered below.\n\n## Does this CSS horizontal scroll animation really run off the main thread?\n\nThis build still reads layout and writes styles in `measure()`, but only on load and resize. The per-frame loop is gone. Whether the browser then runs the animation on the compositor depends on the browser and the property.\n\nChrome and Edge shipped scroll-driven animations in version 115, and Chrome's own case study dates the off-main-thread gains to 116. There, `transform`, `opacity` and some filters can run on the compositor. Safari shipped support in 26.0 on the main thread, and 26.4 moved it to the compositor. [WebKit's release notes](https:\u002F\u002Fwebkit.org\u002Fblog\u002F17862\u002Fwebkit-features-for-safari-26-4\u002F) promise animations that \"stay smooth even when the main thread is busy\". Firefox 156, stable when I tested, has no native support. It is on by default only in Nightly.\n\nI checked the test page with a Chrome 153 performance trace. Chrome emits an `Animation` trace event for each running animation and attaches a `compositeFailed` reason when it cannot composite one. All four animations on the page came through clean, the WAAPI progress bar included. As a control, I switched the track's keyframe from `transform` to `margin-left`. The same trace then reported `compositeFailed` with `unsupportedProperties: [\"margin-left\"]`.\n\nTo run the same check by hand, record a profile in the Chrome DevTools Performance panel while scrolling through the section. Then open the Animations track. DevTools puts a red triangle on non-composited animations and lists the reason in the Summary tab. No triangle on the track animation means it runs on the compositor.\n\nI did not measure frame rates under load. Chrome's [case study](https:\u002F\u002Fdeveloper.chrome.com\u002Fblog\u002Fscroll-animation-performance-case-study) shows that behavior by running a heavy loop every 100 milliseconds next to two progress bars. The scroll listener version turns janky. The scroll-driven one is \"completely unaffected by the heavy JavaScript work\".\n\nThe practical rule: put `transform` and `opacity` on the scroll timeline. Anything that changes layout, such as `width` or `margin`, sends the work back to the main thread even without a listener.\n\n## What happens in browsers without scroll-driven animations?\n\nTreat the horizontal layout as an enhancement over a readable vertical stack. Where the timeline cannot run, a sticky track that never moves is worse than no effect: the first panel pins and the others are unreachable. So unsupported browsers get the stack:\n\n```css\n@supports not (animation-range: contain 0% contain 100%) {\n  .hs {\n    height: auto;\n  }\n\n  .hs__track {\n    position: static;\n    flex-direction: column;\n    width: auto;\n    height: auto;\n  }\n}\n```\n\nIn Firefox 156, this turned the test page into four stacked panels with no script errors.\n\nSome layouts should stack on purpose. The plugin stacks panels on touch screens by default. A media query can reset a CSS animation, but `animation: none` does nothing to an animation created with WAAPI. Cancelling it from JavaScript means JavaScript has to know about breakpoints. The engine avoids that by folding a state variable into the endpoint and zeroing it wherever the panels stack:\n\n```css\n@keyframes hs-slide {\n  to {\n    transform: translateX(calc((-100% + 100cqw) * var(--move, 1)));\n  }\n}\n\n@media (hover: none) and (pointer: coarse) {\n  .hs {\n    --move: 0;\n    height: auto;\n  }\n\n  .hs__track {\n    position: static;\n    flex-direction: column;\n    width: auto;\n    height: auto;\n  }\n}\n```\n\nAny animation built from that endpoint, CSS or WAAPI, now resolves to `translateX(0)` in that state. The trace showed Chrome still composites it with the `var()` inside.\n\nFirefox and Safari before 26 can get the horizontal layout through a polyfill. The plugin lazy-loads a patched scroll-timeline polyfill (about 19 KB gzipped) when a `CSS.supports()` check fails. The stock copy aborts its whole setup when one stylesheet trips its parser, and Elementor's inline CSS does exactly that. Its CSS layer also maps `contain` wrongly on subjects taller than the viewport, which is every pinned section. So the engine builds the polyfilled track through WAAPI with the same endpoint, and stays vertical if loading fails. That path runs on the main thread. It keeps Firefox working. It does not make Firefox fast.\n\n## How to build it, step by step\n\nHere is the whole build in order. Steps 1 to 5 produce the effect in Chrome, Edge and Safari 26 or later. Step 6 covers every other browser, and step 7 confirms the result. Each step matches a section above if you need the reasoning.\n\n1. **Step 1: Structure the section.** Nest `.hs > .hs__track > .hs__panel`. The section provides the scroll distance, and the track carries the panels sideways.\n2. **Step 2: Pin the track with CSS.** Make the track `position: sticky` and `100vh` tall. Give the section `overflow-x: clip` and `container-type: inline-size`, because `overflow: hidden` on any ancestor breaks the pin.\n3. **Step 3: Measure the travel on resize.** One frame after each ResizeObserver callback, write `track.scrollWidth - section.clientWidth` to `--travel` and add it to the section's height. Waiting that frame avoids the ResizeObserver loop error.\n4. **Step 4: Name a view timeline on the section.** Set `view-timeline: --hs block` and `view-timeline-inset: 0px` on the section, not the track. The pinned track never moves relative to the viewport, so a timeline on it would stall. The explicit inset stops `scroll-padding` from shifting the range.\n5. **Step 5: Animate the track across the contain range.** Attach a `linear` animation ending at `translateX(calc(-100% + 100cqw))` to `--hs` with `animation-range: contain 0% contain 100%`. The relative endpoint stays correct after every resize, so the animation is never rebuilt.\n6. **Step 6: Stack the panels where the timeline cannot run.** Reset the layout under `@supports not` and in stacking media queries. Set `--move: 0` there too, because WAAPI animations ignore `animation: none`.\n7. **Step 7: Verify compositing.** Profile a scroll in DevTools and check the Animations track for red triangles. A red triangle names the property that kept an animation on the main thread.\n\n## Key takeaways\n\n- Pin with `position: sticky`, and use `overflow-x: clip` instead of `hidden`.\n- Measure the travel one frame after a resize, never on scroll.\n- Put the view timeline on the section and animate the track from `contain 0%` to `contain 100%`, with `linear` easing.\n- Animate `transform`, then check DevTools for red triangles.\n- Stack the panels wherever the timeline cannot run.","\u002Fapi\u002Fblog\u002Fimages\u002Fblog\u002F1\u002Fc0d8aefd-9565-4ffc-9fd5-1152d0360b61.webp","A pinned horizontal scroll section drawn in violet on a dark circuit background: a scroll progress ruler at 35%, four panels sliding left behind a glowing sticky viewport frame, and the translateX(calc(-100% + 100cqw)) endpoint",[17,18,19,20,21],"css","scroll-driven animations","web performance","frontend","animation",[23,18,24,25,26],"css horizontal scroll animation","horizontal scroll on vertical scroll","horizontal scroll section css","pinned horizontal scrolling",[28,31,34,37],{"answer":29,"question":30},"Yes, if the panel widths are known in advance. When every panel is as wide as the viewport, the travel is a fixed multiple of the viewport width. CSS alone can then set the section height. You need JavaScript only when panel widths depend on content. Then it measures the travel on resize and writes it to a custom property.","Can I build a horizontal scroll section with pure CSS and no JavaScript?",{"answer":32,"question":33},"An ancestor became a scroll container. `overflow: hidden`, `auto` or `scroll` on any ancestor makes the sticky track stick to that ancestor instead of the viewport. Use `overflow-x: clip` on the section. Also look for `html, body { overflow-x: hidden }`. That pair turns the body into a scroll container, while either rule alone is safe.","Why does position: sticky stop working in my horizontal scroll section?",{"answer":35,"question":36},"Chrome and Edge support them from version 115, and Safari from 26.0. Safari runs them on the compositor thread from 26.4. Firefox 156, stable when I tested in September 2026, does not support them yet. They are on by default only in Firefox Nightly. For Firefox, load a polyfill or fall back to a vertical layout.","Which browsers support CSS scroll-driven animations?",{"answer":38,"question":39},"On an idle page, both look smooth. The difference shows when the main thread is busy. ScrollTrigger updates on `requestAnimationFrame`, so it waits for the main thread. A scroll-driven `transform` animation can run on the compositor in Chrome and Safari 26.4+, so a long task does not stall it. GSAP still wins for complex sequenced timelines.","Is a scroll-driven animation faster than GSAP ScrollTrigger?",{"name":41,"steps":42},"How to build a CSS horizontal scroll animation with a view timeline",[43,46,49,52,55,58,61],{"name":44,"text":45},"Structure the section","Nest `.hs > .hs__track > .hs__panel`. The section provides the scroll distance, and the track carries the panels sideways.",{"name":47,"text":48},"Pin the track with CSS","Make the track `position: sticky` and `100vh` tall. Give the section `overflow-x: clip` and `container-type: inline-size`, because `overflow: hidden` on any ancestor breaks the pin.",{"name":50,"text":51},"Measure the travel on resize","One frame after each ResizeObserver callback, write `track.scrollWidth - section.clientWidth` to `--travel` and add it to the section's height. Waiting that frame avoids the ResizeObserver loop error.",{"name":53,"text":54},"Name a view timeline on the section","Set `view-timeline: --hs block` and `view-timeline-inset: 0px` on the section, not the track. The pinned track never moves relative to the viewport, so a timeline on it would stall. The explicit inset stops `scroll-padding` from shifting the range.",{"name":56,"text":57},"Animate the track across the contain range","Attach a `linear` animation ending at `translateX(calc(-100% + 100cqw))` to `--hs` with `animation-range: contain 0% contain 100%`. The relative endpoint stays correct after every resize, so the animation is never rebuilt.",{"name":59,"text":60},"Stack the panels where the timeline cannot run","Reset the layout under `@supports not` and in stacking media queries. Set `--move: 0` there too, because WAAPI animations ignore `animation: none`.",{"name":62,"text":63},"Verify compositing","Profile a scroll in DevTools and check the Animations track for red triangles. A red triangle names the property that kept an animation on the main thread.",{"dir":65,"repoUrl":66},"src\u002Fts","https:\u002F\u002Fgithub.com\u002Fartkrsk\u002Fhorizontal-scroll-for-elementor",false,null,"published","CSS Horizontal Scroll Animation Off the Main Thread","Pinned horizontal scroll with position: sticky and a CSS view timeline. No scroll listener, tested code, and how to check it runs off the main thread.","2026-10-22T02:46:00.822Z","fb696ca1-7851-4fc7-bb8d-0b897916e799","2026-09-26T04:49:30.476Z","d2e105e2-e504-4955-bb0d-62225a87037a","e0373d69-94df-4c92-9be0-324d83321924","c0fcf6a1-58d2-4262-8996-3ad29d58eea5",{"slug":79,"name":80,"avatarUrl":81,"role":82,"bio":83,"isPublic":84,"expertiseSummary":68,"linkedin":85,"github":86,"twitter":68,"website":87},"artem-semkin","Artem Semkin","\u002Fapi\u002Fpublic\u002Favatar\u002F6f0370c4-5e63-4ca0-bd2b-16ae42e38548","Senior WordPress Engineer","I develop premium motion-focused themes and plugins for WordPress focusing primarily on Elementor ecosystem. I can either design (in Figma) and build using modern WordPress stack and tools. I'm also fine with supporting legacy\u002Foutdated WordPress installations from jQuery era. Currently I have 10 plugins published on WP.org https:\u002F\u002Fprofiles.wordpress.org\u002Fartemsemkin\u002F that I maintain.",true,"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fartemsemkin\u002F","https:\u002F\u002Fgithub.com\u002Fartkrsk\u002F","https:\u002F\u002Fartemsemkin.com\u002F",{"slug":89,"name":90,"color":91},"learnings","Learnings","#6B7280",{"id":77,"name":93,"ctaType":94,"heading":95,"description":96,"buttonText":97,"url":5,"leadMagnetUrl":68},"Book a Call (blog)","book_call","Shipping AI-generated code that keeps breaking?","Book a free 30-minute call with a senior engineer. We diagnose what is going wrong and give you a concrete fix plan, no obligation.","Book a free call",[],[100],{"id":101,"slug":102,"title":103,"description":104,"coverImageUrl":105,"coverImageAlt":106,"tags":107,"featured":67,"tool":68,"publishedAt":113,"author":114,"cluster":120},"01c73e72-9edb-489c-933f-03b7fed9add8","nap-consistency-checklist","NAP Consistency Checklist","How do I keep my business Name, Address, and Phone number consistent across the web?","\u002Fapi\u002Fblog\u002Fimages\u002Fblog\u002F1\u002Fd52990a4-2ded-4b5d-996c-820a0d118824.webp","3D futuristic illustration of a glowing NAP consistency checklist displaying Name, Address, and Phone Number with completed checkmarks, connected by neon purple data streams across a dark circuit board, symbolizing accurate local business listings and citation consistency for SEO.",[108,109,110,111,112],"local SEO","NAP consistency","Google Business Profile","citations","small business","2026-08-02T04:51:22.902Z",{"slug":115,"name":116,"avatarUrl":117,"role":118,"bio":119,"isPublic":84},"anthony-martire","Anthony Martire","\u002Fapi\u002Fpublic\u002Favatar\u002Fd0a2bfc9-2850-4ac7-b92e-09fa71170cb6","Lead Project Manager | QA","Hi, I'm Anthony Martire, Lead Project Manager at CMDCNTR.\n\nI work at the intersection of technology and client partnership, guiding web projects from the first conversation all the way through launch and beyond. My job is to make sure every engagement runs smoothly: scoping the work clearly, keeping communication open and honest, and making sure what we deliver actually moves the needle for your business.\nBefore stepping into project management, I spent several years as a frontend engineer building scalable, high-quality web applications across the banking, financial services, and government sectors, including work with companies like Ally Financial, OneMain Financial, and General Dynamics Information Technology. That technical background means I understand what goes into great software, and I can bridge the gap between what a client envisions and what our engineers build.\n\nWhat I enjoy most is turning a rough idea into something polished and real. Whether it's a custom application, a full website rebuild, or ongoing support, I care about getting the details right and making the process feel easy on your end. When you work with CMDCNTR, my goal is simple: deliver work you're proud of, on time, with a team you can trust.\n\nLet's build something great together.",{"slug":89,"name":90,"color":91},{"data":122,"body":123},{},{"type":124,"children":125},"root",[126,134,139,146,179,184,193,198,203,216,222,235,248,650,669,704,709,1177,1205,1218,1224,1229,1505,1510,1553,1558,1660,1679,1715,1721,1740,1753,1970,2014,2024,2036,2044,2815,2820,2941,2961,2966,3452,3465,3471,3483,3511,3561,3566,3580,3613,3619,3624,3840,3845,3858,4231,4252,4272,4278,4283,4483,4489,4565],{"type":127,"tag":128,"props":129,"children":130},"element","p",{},[131],{"type":132,"value":133},"text","Pinned horizontal scrolling is the section that holds still while its panels slide sideways as you scroll down. The common recipes use a scroll listener or GreenSock Animation Platform (GSAP) ScrollTrigger. Both recompute the track's position in JavaScript on every frame while you scroll. That holds up until the main thread gets busy. Then the page keeps scrolling and the track falls behind.",{"type":127,"tag":128,"props":135,"children":136},{},[137],{"type":132,"value":138},"I maintain two horizontal scroll engines. One runs on GSAP ScrollTrigger. The other powers a free, open-source Elementor plugin and uses no animation library at all. This article rebuilds the second one as a plain CSS horizontal scroll animation: a sticky track, a view timeline for progress, and JavaScript that only measures on resize. Every snippet below ran together in one page in Chrome 153 and Firefox 156, and I flag the bugs I hit shipping it.",{"type":127,"tag":140,"props":141,"children":143},"h2",{"id":142},"why-does-a-scroll-listener-horizontal-scroll-stutter",[144],{"type":132,"value":145},"Why does a scroll-listener horizontal scroll stutter?",{"type":127,"tag":128,"props":147,"children":148},{},[149,151,158,160,169,171,177],{"type":132,"value":150},"A listener-based horizontal scroll runs a loop in your code. On each scroll update it reads the section's position and turns it into progress. Then it writes a new ",{"type":127,"tag":152,"props":153,"children":155},"code",{"className":154},[],[156],{"type":132,"value":157},"transform",{"type":132,"value":159}," to the track. GSAP ScrollTrigger is a more careful version of the same loop. Its ",{"type":127,"tag":161,"props":162,"children":166},"a",{"href":163,"rel":164},"https:\u002F\u002Fgsap.com\u002Fdocs\u002Fv3\u002FPlugins\u002FScrollTrigger\u002F",[165],"nofollow",[167],{"type":132,"value":168},"documentation",{"type":132,"value":170}," says it debounces scroll events and updates on the next ",{"type":127,"tag":152,"props":172,"children":174},{"className":173},[],[175],{"type":132,"value":176},"requestAnimationFrame",{"type":132,"value":178},". That is still JavaScript on the main thread.",{"type":127,"tag":128,"props":180,"children":181},{},[182],{"type":132,"value":183},"The browser scrolls the page on a separate compositor thread, so scrolling itself stays smooth when the main thread is busy. Your loop gets no such help. Say a long task holds the main thread for 200 milliseconds. The page keeps moving under the user's finger. The track freezes, then jumps to catch up. On a quiet demo page you never see it. On a production page with hydration and a tag manager, you will.",{"type":127,"tag":128,"props":185,"children":186},{},[187],{"type":127,"tag":188,"props":189,"children":192},"img",{"alt":190,"src":191},"Diagram comparing a scroll listener with a CSS view timeline during a 200 millisecond long task: the listener-driven track freezes and then jumps, while the view timeline track keeps moving on the compositor","\u002Fapi\u002Fblog\u002Fimages\u002Fblog\u002F1\u002Ffc3eda30-f34d-403d-8ca8-071777f6e61e.webp",[],{"type":127,"tag":128,"props":194,"children":195},{},[196],{"type":132,"value":197},"Scroll-driven animations remove the loop instead of optimizing it. You declare the relationship once: this animation's progress equals this element's position in the scroller. The browser advances the animation itself. When the animated property allows it, that happens on the compositor.",{"type":127,"tag":128,"props":199,"children":200},{},[201],{"type":132,"value":202},"So the line that matters runs between JavaScript that defines an animation and JavaScript that drives its frames. A Web Animations API (WAAPI) call attached to a scroll timeline can be composited just like a CSS rule. In the trace test below, both were.",{"type":127,"tag":128,"props":204,"children":205},{},[206,208,214],{"type":132,"value":207},"If an AI builder generated your horizontal scroll, search the code for a scroll handler that sets ",{"type":127,"tag":152,"props":209,"children":211},{"className":210},[],[212],{"type":132,"value":213},"style.transform",{"type":132,"value":215},". That handler is the part to replace.",{"type":127,"tag":140,"props":217,"children":219},{"id":218},"how-do-you-build-a-horizontal-scroll-section-in-css",[220],{"type":132,"value":221},"How do you build a horizontal scroll section in CSS?",{"type":127,"tag":128,"props":223,"children":224},{},[225,227,233],{"type":132,"value":226},"Pinned horizontal scrolling has two jobs: hold the section still, and move the track sideways while it is held. ",{"type":127,"tag":152,"props":228,"children":230},{"className":229},[],[231],{"type":132,"value":232},"position: sticky",{"type":132,"value":234}," does the first one with no JavaScript.",{"type":127,"tag":128,"props":236,"children":237},{},[238,240,246],{"type":132,"value":239},"The structure is ",{"type":127,"tag":152,"props":241,"children":243},{"className":242},[],[244],{"type":132,"value":245},".hs > .hs__track > .hs__panel",{"type":132,"value":247},". The outer section supplies the vertical scroll distance, and the track sticks to the top of the viewport inside it. Here is the horizontal scroll section CSS:",{"type":127,"tag":249,"props":250,"children":254},"pre",{"className":251,"code":252,"language":17,"meta":253,"style":253},"language-css shiki shiki-themes material-theme-lighter material-theme material-theme-palenight",".hs {\n  width: 100%;\n  container-type: inline-size;\n  overflow-x: clip;\n  height: calc(100vh + var(--travel, 0px));\n}\n\n.hs__track {\n  position: sticky;\n  top: 0;\n  display: flex;\n  width: max-content;\n  height: 100vh;\n}\n\n.hs__panel {\n  flex: 0 0 auto;\n  width: 100cqw;\n}\n","",[255],{"type":127,"tag":152,"props":256,"children":257},{"__ignoreMap":253},[258,281,307,330,352,415,424,433,450,472,494,516,537,558,566,574,591,621,642],{"type":127,"tag":259,"props":260,"children":263},"span",{"class":261,"line":262},"line",1,[264,270,276],{"type":127,"tag":259,"props":265,"children":267},{"style":266},"--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF",[268],{"type":132,"value":269},".",{"type":127,"tag":259,"props":271,"children":273},{"style":272},"--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B",[274],{"type":132,"value":275},"hs",{"type":127,"tag":259,"props":277,"children":278},{"style":266},[279],{"type":132,"value":280}," {\n",{"type":127,"tag":259,"props":282,"children":284},{"class":261,"line":283},2,[285,291,296,302],{"type":127,"tag":259,"props":286,"children":288},{"style":287},"--shiki-light:#8796B0;--shiki-default:#B2CCD6;--shiki-dark:#B2CCD6",[289],{"type":132,"value":290},"  width",{"type":127,"tag":259,"props":292,"children":293},{"style":266},[294],{"type":132,"value":295},":",{"type":127,"tag":259,"props":297,"children":299},{"style":298},"--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C",[300],{"type":132,"value":301}," 100%",{"type":127,"tag":259,"props":303,"children":304},{"style":266},[305],{"type":132,"value":306},";\n",{"type":127,"tag":259,"props":308,"children":310},{"class":261,"line":309},3,[311,316,320,326],{"type":127,"tag":259,"props":312,"children":313},{"style":287},[314],{"type":132,"value":315},"  container-type",{"type":127,"tag":259,"props":317,"children":318},{"style":266},[319],{"type":132,"value":295},{"type":127,"tag":259,"props":321,"children":323},{"style":322},"--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8",[324],{"type":132,"value":325}," inline-size",{"type":127,"tag":259,"props":327,"children":328},{"style":266},[329],{"type":132,"value":306},{"type":127,"tag":259,"props":331,"children":333},{"class":261,"line":332},4,[334,339,343,348],{"type":127,"tag":259,"props":335,"children":336},{"style":287},[337],{"type":132,"value":338},"  overflow-x",{"type":127,"tag":259,"props":340,"children":341},{"style":266},[342],{"type":132,"value":295},{"type":127,"tag":259,"props":344,"children":345},{"style":322},[346],{"type":132,"value":347}," clip",{"type":127,"tag":259,"props":349,"children":350},{"style":266},[351],{"type":132,"value":306},{"type":127,"tag":259,"props":353,"children":355},{"class":261,"line":354},5,[356,361,365,371,376,381,386,391,395,400,405,410],{"type":127,"tag":259,"props":357,"children":358},{"style":287},[359],{"type":132,"value":360},"  height",{"type":127,"tag":259,"props":362,"children":363},{"style":266},[364],{"type":132,"value":295},{"type":127,"tag":259,"props":366,"children":368},{"style":367},"--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF",[369],{"type":132,"value":370}," calc",{"type":127,"tag":259,"props":372,"children":373},{"style":266},[374],{"type":132,"value":375},"(",{"type":127,"tag":259,"props":377,"children":378},{"style":298},[379],{"type":132,"value":380},"100vh",{"type":127,"tag":259,"props":382,"children":383},{"style":266},[384],{"type":132,"value":385}," +",{"type":127,"tag":259,"props":387,"children":388},{"style":367},[389],{"type":132,"value":390}," var",{"type":127,"tag":259,"props":392,"children":393},{"style":266},[394],{"type":132,"value":375},{"type":127,"tag":259,"props":396,"children":397},{"style":322},[398],{"type":132,"value":399},"--travel",{"type":127,"tag":259,"props":401,"children":402},{"style":266},[403],{"type":132,"value":404},",",{"type":127,"tag":259,"props":406,"children":407},{"style":298},[408],{"type":132,"value":409}," 0px",{"type":127,"tag":259,"props":411,"children":412},{"style":266},[413],{"type":132,"value":414},"));\n",{"type":127,"tag":259,"props":416,"children":418},{"class":261,"line":417},6,[419],{"type":127,"tag":259,"props":420,"children":421},{"style":266},[422],{"type":132,"value":423},"}\n",{"type":127,"tag":259,"props":425,"children":427},{"class":261,"line":426},7,[428],{"type":127,"tag":259,"props":429,"children":430},{"emptyLinePlaceholder":84},[431],{"type":132,"value":432},"\n",{"type":127,"tag":259,"props":434,"children":436},{"class":261,"line":435},8,[437,441,446],{"type":127,"tag":259,"props":438,"children":439},{"style":266},[440],{"type":132,"value":269},{"type":127,"tag":259,"props":442,"children":443},{"style":272},[444],{"type":132,"value":445},"hs__track",{"type":127,"tag":259,"props":447,"children":448},{"style":266},[449],{"type":132,"value":280},{"type":127,"tag":259,"props":451,"children":453},{"class":261,"line":452},9,[454,459,463,468],{"type":127,"tag":259,"props":455,"children":456},{"style":287},[457],{"type":132,"value":458},"  position",{"type":127,"tag":259,"props":460,"children":461},{"style":266},[462],{"type":132,"value":295},{"type":127,"tag":259,"props":464,"children":465},{"style":322},[466],{"type":132,"value":467}," sticky",{"type":127,"tag":259,"props":469,"children":470},{"style":266},[471],{"type":132,"value":306},{"type":127,"tag":259,"props":473,"children":475},{"class":261,"line":474},10,[476,481,485,490],{"type":127,"tag":259,"props":477,"children":478},{"style":287},[479],{"type":132,"value":480},"  top",{"type":127,"tag":259,"props":482,"children":483},{"style":266},[484],{"type":132,"value":295},{"type":127,"tag":259,"props":486,"children":487},{"style":298},[488],{"type":132,"value":489}," 0",{"type":127,"tag":259,"props":491,"children":492},{"style":266},[493],{"type":132,"value":306},{"type":127,"tag":259,"props":495,"children":497},{"class":261,"line":496},11,[498,503,507,512],{"type":127,"tag":259,"props":499,"children":500},{"style":287},[501],{"type":132,"value":502},"  display",{"type":127,"tag":259,"props":504,"children":505},{"style":266},[506],{"type":132,"value":295},{"type":127,"tag":259,"props":508,"children":509},{"style":322},[510],{"type":132,"value":511}," flex",{"type":127,"tag":259,"props":513,"children":514},{"style":266},[515],{"type":132,"value":306},{"type":127,"tag":259,"props":517,"children":519},{"class":261,"line":518},12,[520,524,528,533],{"type":127,"tag":259,"props":521,"children":522},{"style":287},[523],{"type":132,"value":290},{"type":127,"tag":259,"props":525,"children":526},{"style":266},[527],{"type":132,"value":295},{"type":127,"tag":259,"props":529,"children":530},{"style":322},[531],{"type":132,"value":532}," max-content",{"type":127,"tag":259,"props":534,"children":535},{"style":266},[536],{"type":132,"value":306},{"type":127,"tag":259,"props":538,"children":540},{"class":261,"line":539},13,[541,545,549,554],{"type":127,"tag":259,"props":542,"children":543},{"style":287},[544],{"type":132,"value":360},{"type":127,"tag":259,"props":546,"children":547},{"style":266},[548],{"type":132,"value":295},{"type":127,"tag":259,"props":550,"children":551},{"style":298},[552],{"type":132,"value":553}," 100vh",{"type":127,"tag":259,"props":555,"children":556},{"style":266},[557],{"type":132,"value":306},{"type":127,"tag":259,"props":559,"children":561},{"class":261,"line":560},14,[562],{"type":127,"tag":259,"props":563,"children":564},{"style":266},[565],{"type":132,"value":423},{"type":127,"tag":259,"props":567,"children":569},{"class":261,"line":568},15,[570],{"type":127,"tag":259,"props":571,"children":572},{"emptyLinePlaceholder":84},[573],{"type":132,"value":432},{"type":127,"tag":259,"props":575,"children":577},{"class":261,"line":576},16,[578,582,587],{"type":127,"tag":259,"props":579,"children":580},{"style":266},[581],{"type":132,"value":269},{"type":127,"tag":259,"props":583,"children":584},{"style":272},[585],{"type":132,"value":586},"hs__panel",{"type":127,"tag":259,"props":588,"children":589},{"style":266},[590],{"type":132,"value":280},{"type":127,"tag":259,"props":592,"children":594},{"class":261,"line":593},17,[595,600,604,608,612,617],{"type":127,"tag":259,"props":596,"children":597},{"style":287},[598],{"type":132,"value":599},"  flex",{"type":127,"tag":259,"props":601,"children":602},{"style":266},[603],{"type":132,"value":295},{"type":127,"tag":259,"props":605,"children":606},{"style":298},[607],{"type":132,"value":489},{"type":127,"tag":259,"props":609,"children":610},{"style":298},[611],{"type":132,"value":489},{"type":127,"tag":259,"props":613,"children":614},{"style":322},[615],{"type":132,"value":616}," auto",{"type":127,"tag":259,"props":618,"children":619},{"style":266},[620],{"type":132,"value":306},{"type":127,"tag":259,"props":622,"children":624},{"class":261,"line":623},18,[625,629,633,638],{"type":127,"tag":259,"props":626,"children":627},{"style":287},[628],{"type":132,"value":290},{"type":127,"tag":259,"props":630,"children":631},{"style":266},[632],{"type":132,"value":295},{"type":127,"tag":259,"props":634,"children":635},{"style":298},[636],{"type":132,"value":637}," 100cqw",{"type":127,"tag":259,"props":639,"children":640},{"style":266},[641],{"type":132,"value":306},{"type":127,"tag":259,"props":643,"children":645},{"class":261,"line":644},19,[646],{"type":127,"tag":259,"props":647,"children":648},{"style":266},[649],{"type":132,"value":423},{"type":127,"tag":128,"props":651,"children":652},{},[653,659,661,667],{"type":127,"tag":152,"props":654,"children":656},{"className":655},[],[657],{"type":132,"value":658},"container-type: inline-size",{"type":132,"value":660}," makes ",{"type":127,"tag":152,"props":662,"children":664},{"className":663},[],[665],{"type":132,"value":666},"100cqw",{"type":132,"value":668}," equal the section's width, so panels size against the section instead of the window. Panels can be any width; mine range from 60 to 100 percent of the section.",{"type":127,"tag":128,"props":670,"children":671},{},[672,678,680,686,688,694,696,702],{"type":127,"tag":152,"props":673,"children":675},{"className":674},[],[676],{"type":132,"value":677},"overflow-x: clip",{"type":132,"value":679}," looks like a detail. It decides whether the pin works at all. ",{"type":127,"tag":152,"props":681,"children":683},{"className":682},[],[684],{"type":132,"value":685},"overflow: hidden",{"type":132,"value":687}," turns an element into a scroll container. A sticky element sticks to its nearest scroll container, so the pin silently stops working. ",{"type":127,"tag":152,"props":689,"children":691},{"className":690},[],[692],{"type":132,"value":693},"clip",{"type":132,"value":695}," cuts off the overflow without creating one. A support thread I handled traced a dead pin to a theme's ",{"type":127,"tag":152,"props":697,"children":699},{"className":698},[],[700],{"type":132,"value":701},"html, body { overflow-x: hidden }",{"type":132,"value":703},". Either rule alone is harmless, because the browser passes it on to the viewport. Together, the body keeps its own overflow and becomes a scroll container. The pin is gone.",{"type":127,"tag":128,"props":705,"children":706},{},[707],{"type":132,"value":708},"The track is wider than the section by a distance I call travel. It depends on content, so measure it at startup and whenever either box resizes. Never on scroll:",{"type":127,"tag":249,"props":710,"children":714},{"className":711,"code":712,"language":713,"meta":253,"style":253},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const section = document.querySelector(\".hs\");\nconst track = section.querySelector(\".hs__track\");\n\nfunction measure() {\n  const travel = Math.max(0, track.scrollWidth - section.clientWidth);\n  section.style.setProperty(\"--travel\", `${travel}px`);\n}\n\n\u002F\u002F measure() resizes the section. Doing that inside the observer\n\u002F\u002F callback fires the \"ResizeObserver loop\" error, so wait a frame.\nconst observer = new ResizeObserver(() => requestAnimationFrame(measure));\nobserver.observe(section);\nobserver.observe(track);\nmeasure();\n","js",[715],{"type":127,"tag":152,"props":716,"children":717},{"__ignoreMap":253},[718,779,833,840,862,948,1027,1034,1041,1050,1058,1111,1137,1161],{"type":127,"tag":259,"props":719,"children":720},{"class":261,"line":262},[721,727,732,737,742,746,751,755,760,766,770,775],{"type":127,"tag":259,"props":722,"children":724},{"style":723},"--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA",[725],{"type":132,"value":726},"const",{"type":127,"tag":259,"props":728,"children":729},{"style":322},[730],{"type":132,"value":731}," section ",{"type":127,"tag":259,"props":733,"children":734},{"style":266},[735],{"type":132,"value":736},"=",{"type":127,"tag":259,"props":738,"children":739},{"style":322},[740],{"type":132,"value":741}," document",{"type":127,"tag":259,"props":743,"children":744},{"style":266},[745],{"type":132,"value":269},{"type":127,"tag":259,"props":747,"children":748},{"style":367},[749],{"type":132,"value":750},"querySelector",{"type":127,"tag":259,"props":752,"children":753},{"style":322},[754],{"type":132,"value":375},{"type":127,"tag":259,"props":756,"children":757},{"style":266},[758],{"type":132,"value":759},"\"",{"type":127,"tag":259,"props":761,"children":763},{"style":762},"--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D",[764],{"type":132,"value":765},".hs",{"type":127,"tag":259,"props":767,"children":768},{"style":266},[769],{"type":132,"value":759},{"type":127,"tag":259,"props":771,"children":772},{"style":322},[773],{"type":132,"value":774},")",{"type":127,"tag":259,"props":776,"children":777},{"style":266},[778],{"type":132,"value":306},{"type":127,"tag":259,"props":780,"children":781},{"class":261,"line":283},[782,786,791,795,800,804,808,812,816,821,825,829],{"type":127,"tag":259,"props":783,"children":784},{"style":723},[785],{"type":132,"value":726},{"type":127,"tag":259,"props":787,"children":788},{"style":322},[789],{"type":132,"value":790}," track ",{"type":127,"tag":259,"props":792,"children":793},{"style":266},[794],{"type":132,"value":736},{"type":127,"tag":259,"props":796,"children":797},{"style":322},[798],{"type":132,"value":799}," section",{"type":127,"tag":259,"props":801,"children":802},{"style":266},[803],{"type":132,"value":269},{"type":127,"tag":259,"props":805,"children":806},{"style":367},[807],{"type":132,"value":750},{"type":127,"tag":259,"props":809,"children":810},{"style":322},[811],{"type":132,"value":375},{"type":127,"tag":259,"props":813,"children":814},{"style":266},[815],{"type":132,"value":759},{"type":127,"tag":259,"props":817,"children":818},{"style":762},[819],{"type":132,"value":820},".hs__track",{"type":127,"tag":259,"props":822,"children":823},{"style":266},[824],{"type":132,"value":759},{"type":127,"tag":259,"props":826,"children":827},{"style":322},[828],{"type":132,"value":774},{"type":127,"tag":259,"props":830,"children":831},{"style":266},[832],{"type":132,"value":306},{"type":127,"tag":259,"props":834,"children":835},{"class":261,"line":309},[836],{"type":127,"tag":259,"props":837,"children":838},{"emptyLinePlaceholder":84},[839],{"type":132,"value":432},{"type":127,"tag":259,"props":841,"children":842},{"class":261,"line":332},[843,848,853,858],{"type":127,"tag":259,"props":844,"children":845},{"style":723},[846],{"type":132,"value":847},"function",{"type":127,"tag":259,"props":849,"children":850},{"style":367},[851],{"type":132,"value":852}," measure",{"type":127,"tag":259,"props":854,"children":855},{"style":266},[856],{"type":132,"value":857},"()",{"type":127,"tag":259,"props":859,"children":860},{"style":266},[861],{"type":132,"value":280},{"type":127,"tag":259,"props":863,"children":864},{"class":261,"line":354},[865,870,875,880,885,889,894,899,904,908,913,917,922,927,931,935,940,944],{"type":127,"tag":259,"props":866,"children":867},{"style":723},[868],{"type":132,"value":869},"  const",{"type":127,"tag":259,"props":871,"children":872},{"style":322},[873],{"type":132,"value":874}," travel",{"type":127,"tag":259,"props":876,"children":877},{"style":266},[878],{"type":132,"value":879}," =",{"type":127,"tag":259,"props":881,"children":882},{"style":322},[883],{"type":132,"value":884}," Math",{"type":127,"tag":259,"props":886,"children":887},{"style":266},[888],{"type":132,"value":269},{"type":127,"tag":259,"props":890,"children":891},{"style":367},[892],{"type":132,"value":893},"max",{"type":127,"tag":259,"props":895,"children":897},{"style":896},"--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178",[898],{"type":132,"value":375},{"type":127,"tag":259,"props":900,"children":901},{"style":298},[902],{"type":132,"value":903},"0",{"type":127,"tag":259,"props":905,"children":906},{"style":266},[907],{"type":132,"value":404},{"type":127,"tag":259,"props":909,"children":910},{"style":322},[911],{"type":132,"value":912}," track",{"type":127,"tag":259,"props":914,"children":915},{"style":266},[916],{"type":132,"value":269},{"type":127,"tag":259,"props":918,"children":919},{"style":322},[920],{"type":132,"value":921},"scrollWidth",{"type":127,"tag":259,"props":923,"children":924},{"style":266},[925],{"type":132,"value":926}," -",{"type":127,"tag":259,"props":928,"children":929},{"style":322},[930],{"type":132,"value":799},{"type":127,"tag":259,"props":932,"children":933},{"style":266},[934],{"type":132,"value":269},{"type":127,"tag":259,"props":936,"children":937},{"style":322},[938],{"type":132,"value":939},"clientWidth",{"type":127,"tag":259,"props":941,"children":942},{"style":896},[943],{"type":132,"value":774},{"type":127,"tag":259,"props":945,"children":946},{"style":266},[947],{"type":132,"value":306},{"type":127,"tag":259,"props":949,"children":950},{"class":261,"line":417},[951,956,960,965,969,974,978,982,986,990,994,999,1004,1009,1014,1019,1023],{"type":127,"tag":259,"props":952,"children":953},{"style":322},[954],{"type":132,"value":955},"  section",{"type":127,"tag":259,"props":957,"children":958},{"style":266},[959],{"type":132,"value":269},{"type":127,"tag":259,"props":961,"children":962},{"style":322},[963],{"type":132,"value":964},"style",{"type":127,"tag":259,"props":966,"children":967},{"style":266},[968],{"type":132,"value":269},{"type":127,"tag":259,"props":970,"children":971},{"style":367},[972],{"type":132,"value":973},"setProperty",{"type":127,"tag":259,"props":975,"children":976},{"style":896},[977],{"type":132,"value":375},{"type":127,"tag":259,"props":979,"children":980},{"style":266},[981],{"type":132,"value":759},{"type":127,"tag":259,"props":983,"children":984},{"style":762},[985],{"type":132,"value":399},{"type":127,"tag":259,"props":987,"children":988},{"style":266},[989],{"type":132,"value":759},{"type":127,"tag":259,"props":991,"children":992},{"style":266},[993],{"type":132,"value":404},{"type":127,"tag":259,"props":995,"children":996},{"style":266},[997],{"type":132,"value":998}," `${",{"type":127,"tag":259,"props":1000,"children":1001},{"style":322},[1002],{"type":132,"value":1003},"travel",{"type":127,"tag":259,"props":1005,"children":1006},{"style":266},[1007],{"type":132,"value":1008},"}",{"type":127,"tag":259,"props":1010,"children":1011},{"style":762},[1012],{"type":132,"value":1013},"px",{"type":127,"tag":259,"props":1015,"children":1016},{"style":266},[1017],{"type":132,"value":1018},"`",{"type":127,"tag":259,"props":1020,"children":1021},{"style":896},[1022],{"type":132,"value":774},{"type":127,"tag":259,"props":1024,"children":1025},{"style":266},[1026],{"type":132,"value":306},{"type":127,"tag":259,"props":1028,"children":1029},{"class":261,"line":426},[1030],{"type":127,"tag":259,"props":1031,"children":1032},{"style":266},[1033],{"type":132,"value":423},{"type":127,"tag":259,"props":1035,"children":1036},{"class":261,"line":435},[1037],{"type":127,"tag":259,"props":1038,"children":1039},{"emptyLinePlaceholder":84},[1040],{"type":132,"value":432},{"type":127,"tag":259,"props":1042,"children":1043},{"class":261,"line":452},[1044],{"type":127,"tag":259,"props":1045,"children":1047},{"style":1046},"--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic",[1048],{"type":132,"value":1049},"\u002F\u002F measure() resizes the section. Doing that inside the observer\n",{"type":127,"tag":259,"props":1051,"children":1052},{"class":261,"line":474},[1053],{"type":127,"tag":259,"props":1054,"children":1055},{"style":1046},[1056],{"type":132,"value":1057},"\u002F\u002F callback fires the \"ResizeObserver loop\" error, so wait a frame.\n",{"type":127,"tag":259,"props":1059,"children":1060},{"class":261,"line":496},[1061,1065,1070,1074,1079,1084,1088,1092,1097,1102,1107],{"type":127,"tag":259,"props":1062,"children":1063},{"style":723},[1064],{"type":132,"value":726},{"type":127,"tag":259,"props":1066,"children":1067},{"style":322},[1068],{"type":132,"value":1069}," observer ",{"type":127,"tag":259,"props":1071,"children":1072},{"style":266},[1073],{"type":132,"value":736},{"type":127,"tag":259,"props":1075,"children":1076},{"style":266},[1077],{"type":132,"value":1078}," new",{"type":127,"tag":259,"props":1080,"children":1081},{"style":367},[1082],{"type":132,"value":1083}," ResizeObserver",{"type":127,"tag":259,"props":1085,"children":1086},{"style":322},[1087],{"type":132,"value":375},{"type":127,"tag":259,"props":1089,"children":1090},{"style":266},[1091],{"type":132,"value":857},{"type":127,"tag":259,"props":1093,"children":1094},{"style":723},[1095],{"type":132,"value":1096}," =>",{"type":127,"tag":259,"props":1098,"children":1099},{"style":367},[1100],{"type":132,"value":1101}," requestAnimationFrame",{"type":127,"tag":259,"props":1103,"children":1104},{"style":322},[1105],{"type":132,"value":1106},"(measure))",{"type":127,"tag":259,"props":1108,"children":1109},{"style":266},[1110],{"type":132,"value":306},{"type":127,"tag":259,"props":1112,"children":1113},{"class":261,"line":518},[1114,1119,1123,1128,1133],{"type":127,"tag":259,"props":1115,"children":1116},{"style":322},[1117],{"type":132,"value":1118},"observer",{"type":127,"tag":259,"props":1120,"children":1121},{"style":266},[1122],{"type":132,"value":269},{"type":127,"tag":259,"props":1124,"children":1125},{"style":367},[1126],{"type":132,"value":1127},"observe",{"type":127,"tag":259,"props":1129,"children":1130},{"style":322},[1131],{"type":132,"value":1132},"(section)",{"type":127,"tag":259,"props":1134,"children":1135},{"style":266},[1136],{"type":132,"value":306},{"type":127,"tag":259,"props":1138,"children":1139},{"class":261,"line":539},[1140,1144,1148,1152,1157],{"type":127,"tag":259,"props":1141,"children":1142},{"style":322},[1143],{"type":132,"value":1118},{"type":127,"tag":259,"props":1145,"children":1146},{"style":266},[1147],{"type":132,"value":269},{"type":127,"tag":259,"props":1149,"children":1150},{"style":367},[1151],{"type":132,"value":1127},{"type":127,"tag":259,"props":1153,"children":1154},{"style":322},[1155],{"type":132,"value":1156},"(track)",{"type":127,"tag":259,"props":1158,"children":1159},{"style":266},[1160],{"type":132,"value":306},{"type":127,"tag":259,"props":1162,"children":1163},{"class":261,"line":560},[1164,1169,1173],{"type":127,"tag":259,"props":1165,"children":1166},{"style":367},[1167],{"type":132,"value":1168},"measure",{"type":127,"tag":259,"props":1170,"children":1171},{"style":322},[1172],{"type":132,"value":857},{"type":127,"tag":259,"props":1174,"children":1175},{"style":266},[1176],{"type":132,"value":306},{"type":127,"tag":128,"props":1178,"children":1179},{},[1180,1182,1187,1189,1195,1197,1203],{"type":132,"value":1181},"That ",{"type":127,"tag":152,"props":1183,"children":1185},{"className":1184},[],[1186],{"type":132,"value":176},{"type":132,"value":1188}," is there because of a bug. Changing the section's height inside the observer callback fires ",{"type":127,"tag":152,"props":1190,"children":1192},{"className":1191},[],[1193],{"type":132,"value":1194},"ResizeObserver loop completed with undelivered notifications",{"type":132,"value":1196}," on ",{"type":127,"tag":152,"props":1198,"children":1200},{"className":1199},[],[1201],{"type":132,"value":1202},"window",{"type":132,"value":1204},". It reproduced in every engine I tried: Chromium 153, WebKit 26.5 and Firefox 156. Deferring the write by one frame fixed it everywhere. The price is one frame painted with the old height.",{"type":127,"tag":128,"props":1206,"children":1207},{},[1208,1210,1216],{"type":132,"value":1209},"You get one pixel of horizontal travel per pixel of vertical scroll. To slow it down, make the section taller without touching the travel. The plugin's scroll length setting uses ",{"type":127,"tag":152,"props":1211,"children":1213},{"className":1212},[],[1214],{"type":132,"value":1215},"height: calc(100vh + var(--travel) * var(--factor, 1))",{"type":132,"value":1217},". Speed lives in the height; the endpoint never changes.",{"type":127,"tag":140,"props":1219,"children":1221},{"id":1220},"how-do-scroll-driven-animations-map-vertical-scroll-to-horizontal-movement",[1222],{"type":132,"value":1223},"How do scroll-driven animations map vertical scroll to horizontal movement?",{"type":127,"tag":128,"props":1225,"children":1226},{},[1227],{"type":132,"value":1228},"This is the part people search for as horizontal scroll on vertical scroll. Name a timeline on the section and attach the track's animation to it:",{"type":127,"tag":249,"props":1230,"children":1232},{"className":251,"code":1231,"language":17,"meta":253,"style":253},".hs {\n  view-timeline: --hs block;\n  view-timeline-inset: 0px;\n}\n\n.hs__track {\n  animation: hs-slide linear both;\n  animation-timeline: --hs;\n  animation-range: contain 0% contain 100%;\n}\n\n@keyframes hs-slide {\n  to {\n    transform: translateX(calc(-100% + 100cqw));\n  }\n}\n",[1233],{"type":127,"tag":152,"props":1234,"children":1235},{"__ignoreMap":253},[1236,1251,1272,1292,1299,1306,1321,1342,1363,1398,1405,1412,1431,1443,1490,1498],{"type":127,"tag":259,"props":1237,"children":1238},{"class":261,"line":262},[1239,1243,1247],{"type":127,"tag":259,"props":1240,"children":1241},{"style":266},[1242],{"type":132,"value":269},{"type":127,"tag":259,"props":1244,"children":1245},{"style":272},[1246],{"type":132,"value":275},{"type":127,"tag":259,"props":1248,"children":1249},{"style":266},[1250],{"type":132,"value":280},{"type":127,"tag":259,"props":1252,"children":1253},{"class":261,"line":283},[1254,1259,1263,1268],{"type":127,"tag":259,"props":1255,"children":1256},{"style":322},[1257],{"type":132,"value":1258},"  view-timeline",{"type":127,"tag":259,"props":1260,"children":1261},{"style":266},[1262],{"type":132,"value":295},{"type":127,"tag":259,"props":1264,"children":1265},{"style":322},[1266],{"type":132,"value":1267}," --hs block",{"type":127,"tag":259,"props":1269,"children":1270},{"style":266},[1271],{"type":132,"value":306},{"type":127,"tag":259,"props":1273,"children":1274},{"class":261,"line":309},[1275,1280,1284,1288],{"type":127,"tag":259,"props":1276,"children":1277},{"style":322},[1278],{"type":132,"value":1279},"  view-timeline-inset",{"type":127,"tag":259,"props":1281,"children":1282},{"style":266},[1283],{"type":132,"value":295},{"type":127,"tag":259,"props":1285,"children":1286},{"style":298},[1287],{"type":132,"value":409},{"type":127,"tag":259,"props":1289,"children":1290},{"style":266},[1291],{"type":132,"value":306},{"type":127,"tag":259,"props":1293,"children":1294},{"class":261,"line":332},[1295],{"type":127,"tag":259,"props":1296,"children":1297},{"style":266},[1298],{"type":132,"value":423},{"type":127,"tag":259,"props":1300,"children":1301},{"class":261,"line":354},[1302],{"type":127,"tag":259,"props":1303,"children":1304},{"emptyLinePlaceholder":84},[1305],{"type":132,"value":432},{"type":127,"tag":259,"props":1307,"children":1308},{"class":261,"line":417},[1309,1313,1317],{"type":127,"tag":259,"props":1310,"children":1311},{"style":266},[1312],{"type":132,"value":269},{"type":127,"tag":259,"props":1314,"children":1315},{"style":272},[1316],{"type":132,"value":445},{"type":127,"tag":259,"props":1318,"children":1319},{"style":266},[1320],{"type":132,"value":280},{"type":127,"tag":259,"props":1322,"children":1323},{"class":261,"line":426},[1324,1329,1333,1338],{"type":127,"tag":259,"props":1325,"children":1326},{"style":287},[1327],{"type":132,"value":1328},"  animation",{"type":127,"tag":259,"props":1330,"children":1331},{"style":266},[1332],{"type":132,"value":295},{"type":127,"tag":259,"props":1334,"children":1335},{"style":322},[1336],{"type":132,"value":1337}," hs-slide linear both",{"type":127,"tag":259,"props":1339,"children":1340},{"style":266},[1341],{"type":132,"value":306},{"type":127,"tag":259,"props":1343,"children":1344},{"class":261,"line":435},[1345,1350,1354,1359],{"type":127,"tag":259,"props":1346,"children":1347},{"style":322},[1348],{"type":132,"value":1349},"  animation-timeline",{"type":127,"tag":259,"props":1351,"children":1352},{"style":266},[1353],{"type":132,"value":295},{"type":127,"tag":259,"props":1355,"children":1356},{"style":322},[1357],{"type":132,"value":1358}," --hs",{"type":127,"tag":259,"props":1360,"children":1361},{"style":266},[1362],{"type":132,"value":306},{"type":127,"tag":259,"props":1364,"children":1365},{"class":261,"line":452},[1366,1371,1375,1380,1385,1389,1394],{"type":127,"tag":259,"props":1367,"children":1368},{"style":322},[1369],{"type":132,"value":1370},"  animation-range",{"type":127,"tag":259,"props":1372,"children":1373},{"style":266},[1374],{"type":132,"value":295},{"type":127,"tag":259,"props":1376,"children":1377},{"style":322},[1378],{"type":132,"value":1379}," contain ",{"type":127,"tag":259,"props":1381,"children":1382},{"style":298},[1383],{"type":132,"value":1384},"0%",{"type":127,"tag":259,"props":1386,"children":1387},{"style":322},[1388],{"type":132,"value":1379},{"type":127,"tag":259,"props":1390,"children":1391},{"style":298},[1392],{"type":132,"value":1393},"100%",{"type":127,"tag":259,"props":1395,"children":1396},{"style":266},[1397],{"type":132,"value":306},{"type":127,"tag":259,"props":1399,"children":1400},{"class":261,"line":474},[1401],{"type":127,"tag":259,"props":1402,"children":1403},{"style":266},[1404],{"type":132,"value":423},{"type":127,"tag":259,"props":1406,"children":1407},{"class":261,"line":496},[1408],{"type":127,"tag":259,"props":1409,"children":1410},{"emptyLinePlaceholder":84},[1411],{"type":132,"value":432},{"type":127,"tag":259,"props":1413,"children":1414},{"class":261,"line":518},[1415,1421,1427],{"type":127,"tag":259,"props":1416,"children":1418},{"style":1417},"--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic",[1419],{"type":132,"value":1420},"@keyframes",{"type":127,"tag":259,"props":1422,"children":1424},{"style":1423},"--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic",[1425],{"type":132,"value":1426}," hs-slide",{"type":127,"tag":259,"props":1428,"children":1429},{"style":266},[1430],{"type":132,"value":280},{"type":127,"tag":259,"props":1432,"children":1433},{"class":261,"line":539},[1434,1439],{"type":127,"tag":259,"props":1435,"children":1436},{"style":272},[1437],{"type":132,"value":1438},"  to",{"type":127,"tag":259,"props":1440,"children":1441},{"style":266},[1442],{"type":132,"value":280},{"type":127,"tag":259,"props":1444,"children":1445},{"class":261,"line":560},[1446,1451,1455,1460,1464,1469,1473,1478,1482,1486],{"type":127,"tag":259,"props":1447,"children":1448},{"style":287},[1449],{"type":132,"value":1450},"    transform",{"type":127,"tag":259,"props":1452,"children":1453},{"style":266},[1454],{"type":132,"value":295},{"type":127,"tag":259,"props":1456,"children":1457},{"style":367},[1458],{"type":132,"value":1459}," translateX",{"type":127,"tag":259,"props":1461,"children":1462},{"style":266},[1463],{"type":132,"value":375},{"type":127,"tag":259,"props":1465,"children":1466},{"style":367},[1467],{"type":132,"value":1468},"calc",{"type":127,"tag":259,"props":1470,"children":1471},{"style":266},[1472],{"type":132,"value":375},{"type":127,"tag":259,"props":1474,"children":1475},{"style":298},[1476],{"type":132,"value":1477},"-100%",{"type":127,"tag":259,"props":1479,"children":1480},{"style":266},[1481],{"type":132,"value":385},{"type":127,"tag":259,"props":1483,"children":1484},{"style":298},[1485],{"type":132,"value":637},{"type":127,"tag":259,"props":1487,"children":1488},{"style":266},[1489],{"type":132,"value":414},{"type":127,"tag":259,"props":1491,"children":1492},{"class":261,"line":568},[1493],{"type":127,"tag":259,"props":1494,"children":1495},{"style":266},[1496],{"type":132,"value":1497},"  }\n",{"type":127,"tag":259,"props":1499,"children":1500},{"class":261,"line":576},[1501],{"type":127,"tag":259,"props":1502,"children":1503},{"style":266},[1504],{"type":132,"value":423},{"type":127,"tag":128,"props":1506,"children":1507},{},[1508],{"type":132,"value":1509},"The timeline's subject is the section. The sticky track does not move relative to the viewport while pinned, so a timeline on the track would stall.",{"type":127,"tag":128,"props":1511,"children":1512},{},[1513,1519,1521,1528,1530,1536,1538,1544,1546,1551],{"type":127,"tag":152,"props":1514,"children":1516},{"className":1515},[],[1517],{"type":132,"value":1518},"contain",{"type":132,"value":1520}," lines the movement up with the pin. For a subject taller than the scrollport, ",{"type":127,"tag":161,"props":1522,"children":1525},{"href":1523,"rel":1524},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FCSS\u002FGuides\u002FScroll-driven_animations\u002FTimeline_range_names",[165],[1526],{"type":132,"value":1527},"MDN defines",{"type":132,"value":1529}," ",{"type":127,"tag":152,"props":1531,"children":1533},{"className":1532},[],[1534],{"type":132,"value":1535},"contain 0%",{"type":132,"value":1537}," as the moment its start edge reaches the start of the scrollport, and ",{"type":127,"tag":152,"props":1539,"children":1541},{"className":1540},[],[1542],{"type":132,"value":1543},"contain 100%",{"type":132,"value":1545}," as the moment its end edge reaches the end. With a ",{"type":127,"tag":152,"props":1547,"children":1549},{"className":1548},[],[1550],{"type":132,"value":380},{"type":132,"value":1552}," track, those two moments are the start and end of the sticky interval. The animation runs during the pin and nowhere else.",{"type":127,"tag":128,"props":1554,"children":1555},{},[1556],{"type":132,"value":1557},"Three details in that block come straight from comments in the engine's source:",{"type":127,"tag":1559,"props":1560,"children":1561},"ul",{},[1562,1589,1617],{"type":127,"tag":1563,"props":1564,"children":1565},"li",{},[1566,1572,1574,1579,1581,1587],{"type":127,"tag":152,"props":1567,"children":1569},{"className":1568},[],[1570],{"type":132,"value":1571},"linear",{"type":132,"value":1573}," is load-bearing. The ",{"type":127,"tag":152,"props":1575,"children":1577},{"className":1576},[],[1578],{"type":132,"value":21},{"type":132,"value":1580}," shorthand defaults to ",{"type":127,"tag":152,"props":1582,"children":1584},{"className":1583},[],[1585],{"type":132,"value":1586},"ease",{"type":132,"value":1588},", which makes the track crawl at both ends of the pin and rush through the middle.",{"type":127,"tag":1563,"props":1590,"children":1591},{},[1592,1594,1599,1601,1607,1609,1615],{"type":132,"value":1593},"Declare the ",{"type":127,"tag":152,"props":1595,"children":1597},{"className":1596},[],[1598],{"type":132,"value":21},{"type":132,"value":1600}," shorthand before ",{"type":127,"tag":152,"props":1602,"children":1604},{"className":1603},[],[1605],{"type":132,"value":1606},"animation-timeline",{"type":132,"value":1608}," and ",{"type":127,"tag":152,"props":1610,"children":1612},{"className":1611},[],[1613],{"type":132,"value":1614},"animation-range",{"type":132,"value":1616},". The shorthand resets both, so putting it second silently detaches the timeline.",{"type":127,"tag":1563,"props":1618,"children":1619},{},[1620,1622,1628,1630,1636,1637,1650,1652,1658],{"type":132,"value":1621},"Set ",{"type":127,"tag":152,"props":1623,"children":1625},{"className":1624},[],[1626],{"type":132,"value":1627},"view-timeline-inset: 0px",{"type":132,"value":1629}," explicitly. The default ",{"type":127,"tag":152,"props":1631,"children":1633},{"className":1632},[],[1634],{"type":132,"value":1635},"auto",{"type":132,"value":1529},{"type":127,"tag":161,"props":1638,"children":1641},{"href":1639,"rel":1640},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FCSS\u002FReference\u002FProperties\u002Fview-timeline-inset",[165],[1642,1644],{"type":132,"value":1643},"uses the scroller's ",{"type":127,"tag":152,"props":1645,"children":1647},{"className":1646},[],[1648],{"type":132,"value":1649},"scroll-padding",{"type":132,"value":1651},", and WordPress sets ",{"type":127,"tag":152,"props":1653,"children":1655},{"className":1654},[],[1656],{"type":132,"value":1657},"scroll-padding-top",{"type":132,"value":1659}," to the admin bar height. On a logged-in page, the range shifts by 32 pixels.",{"type":127,"tag":128,"props":1661,"children":1662},{},[1663,1665,1670,1672,1677],{"type":132,"value":1664},"The endpoint has no pixel value in it. ",{"type":127,"tag":152,"props":1666,"children":1668},{"className":1667},[],[1669],{"type":132,"value":1477},{"type":132,"value":1671}," is the track's own width and ",{"type":127,"tag":152,"props":1673,"children":1675},{"className":1674},[],[1676],{"type":132,"value":666},{"type":132,"value":1678}," is the section's, so their sum is always minus the travel. The animation is created once and never rebuilt. Resizing the test page from 1200 to 900 pixels wide moved the end position from -2880 to -2160 pixels on the same animation.",{"type":127,"tag":128,"props":1680,"children":1681},{},[1682,1684,1690,1692,1698,1700,1706,1708,1713],{"type":132,"value":1683},"The native path of ",{"type":127,"tag":161,"props":1685,"children":1687},{"href":66,"rel":1686},[165],[1688],{"type":132,"value":1689},"the open-source horizontal scroll engine on GitHub",{"type":132,"value":1691}," is this CSS, plus a direction multiplier for right-to-left pages. Clone it and run ",{"type":127,"tag":152,"props":1693,"children":1695},{"className":1694},[],[1696],{"type":132,"value":1697},"pnpm test",{"type":132,"value":1699}," for the unit tests. Its Playwright spec, ",{"type":127,"tag":152,"props":1701,"children":1703},{"className":1702},[],[1704],{"type":132,"value":1705},"tests\u002Fe2e\u002Fspecs\u002F01-pin-and-scrub.spec.ts",{"type":132,"value":1707},", runs against WordPress in wp-env. It scrolls through the pin and resizes the window. It fails if a ResizeObserver error reaches ",{"type":127,"tag":152,"props":1709,"children":1711},{"className":1710},[],[1712],{"type":132,"value":1202},{"type":132,"value":1714},". GitHub Actions runs it in Chromium for the native path and Firefox for the polyfilled one.",{"type":127,"tag":140,"props":1716,"children":1718},{"id":1717},"how-do-you-add-more-effects-to-the-same-timeline",[1719],{"type":132,"value":1720},"How do you add more effects to the same timeline?",{"type":127,"tag":128,"props":1722,"children":1723},{},[1724,1726,1731,1733,1739],{"type":132,"value":1725},"Once the section has a named timeline, every extra effect is another animation on it. No second listener, and no progress value copied between callbacks. Each effect also gets the same compositor treatment as the track, as long as it animates ",{"type":127,"tag":152,"props":1727,"children":1729},{"className":1728},[],[1730],{"type":132,"value":157},{"type":132,"value":1732}," or ",{"type":127,"tag":152,"props":1734,"children":1736},{"className":1735},[],[1737],{"type":132,"value":1738},"opacity",{"type":132,"value":269},{"type":127,"tag":128,"props":1741,"children":1742},{},[1743,1745,1751],{"type":132,"value":1744},"Elements inside the section can use ",{"type":127,"tag":152,"props":1746,"children":1748},{"className":1747},[],[1749],{"type":132,"value":1750},"--hs",{"type":132,"value":1752}," directly, because a named view timeline is visible to the descendants of the element that declares it. This decorative layer drifts 80 pixels while the pin goes from 20 to 60 percent:",{"type":127,"tag":249,"props":1754,"children":1756},{"className":251,"code":1755,"language":17,"meta":253,"style":253},".hs__layer {\n  animation: drift linear both;\n  animation-timeline: --hs;\n  animation-range: contain 20% contain 60%;\n}\n\n@keyframes drift {\n  from { transform: translateX(-40px); }\n  to { transform: translateX(40px); }\n}\n",[1757],{"type":127,"tag":152,"props":1758,"children":1759},{"__ignoreMap":253},[1760,1776,1796,1815,1848,1855,1862,1878,1923,1963],{"type":127,"tag":259,"props":1761,"children":1762},{"class":261,"line":262},[1763,1767,1772],{"type":127,"tag":259,"props":1764,"children":1765},{"style":266},[1766],{"type":132,"value":269},{"type":127,"tag":259,"props":1768,"children":1769},{"style":272},[1770],{"type":132,"value":1771},"hs__layer",{"type":127,"tag":259,"props":1773,"children":1774},{"style":266},[1775],{"type":132,"value":280},{"type":127,"tag":259,"props":1777,"children":1778},{"class":261,"line":283},[1779,1783,1787,1792],{"type":127,"tag":259,"props":1780,"children":1781},{"style":287},[1782],{"type":132,"value":1328},{"type":127,"tag":259,"props":1784,"children":1785},{"style":266},[1786],{"type":132,"value":295},{"type":127,"tag":259,"props":1788,"children":1789},{"style":322},[1790],{"type":132,"value":1791}," drift linear both",{"type":127,"tag":259,"props":1793,"children":1794},{"style":266},[1795],{"type":132,"value":306},{"type":127,"tag":259,"props":1797,"children":1798},{"class":261,"line":309},[1799,1803,1807,1811],{"type":127,"tag":259,"props":1800,"children":1801},{"style":322},[1802],{"type":132,"value":1349},{"type":127,"tag":259,"props":1804,"children":1805},{"style":266},[1806],{"type":132,"value":295},{"type":127,"tag":259,"props":1808,"children":1809},{"style":322},[1810],{"type":132,"value":1358},{"type":127,"tag":259,"props":1812,"children":1813},{"style":266},[1814],{"type":132,"value":306},{"type":127,"tag":259,"props":1816,"children":1817},{"class":261,"line":332},[1818,1822,1826,1830,1835,1839,1844],{"type":127,"tag":259,"props":1819,"children":1820},{"style":322},[1821],{"type":132,"value":1370},{"type":127,"tag":259,"props":1823,"children":1824},{"style":266},[1825],{"type":132,"value":295},{"type":127,"tag":259,"props":1827,"children":1828},{"style":322},[1829],{"type":132,"value":1379},{"type":127,"tag":259,"props":1831,"children":1832},{"style":298},[1833],{"type":132,"value":1834},"20%",{"type":127,"tag":259,"props":1836,"children":1837},{"style":322},[1838],{"type":132,"value":1379},{"type":127,"tag":259,"props":1840,"children":1841},{"style":298},[1842],{"type":132,"value":1843},"60%",{"type":127,"tag":259,"props":1845,"children":1846},{"style":266},[1847],{"type":132,"value":306},{"type":127,"tag":259,"props":1849,"children":1850},{"class":261,"line":354},[1851],{"type":127,"tag":259,"props":1852,"children":1853},{"style":266},[1854],{"type":132,"value":423},{"type":127,"tag":259,"props":1856,"children":1857},{"class":261,"line":417},[1858],{"type":127,"tag":259,"props":1859,"children":1860},{"emptyLinePlaceholder":84},[1861],{"type":132,"value":432},{"type":127,"tag":259,"props":1863,"children":1864},{"class":261,"line":426},[1865,1869,1874],{"type":127,"tag":259,"props":1866,"children":1867},{"style":1417},[1868],{"type":132,"value":1420},{"type":127,"tag":259,"props":1870,"children":1871},{"style":1423},[1872],{"type":132,"value":1873}," drift",{"type":127,"tag":259,"props":1875,"children":1876},{"style":266},[1877],{"type":132,"value":280},{"type":127,"tag":259,"props":1879,"children":1880},{"class":261,"line":435},[1881,1886,1891,1896,1900,1904,1908,1913,1918],{"type":127,"tag":259,"props":1882,"children":1883},{"style":272},[1884],{"type":132,"value":1885},"  from",{"type":127,"tag":259,"props":1887,"children":1888},{"style":266},[1889],{"type":132,"value":1890}," {",{"type":127,"tag":259,"props":1892,"children":1893},{"style":287},[1894],{"type":132,"value":1895}," transform",{"type":127,"tag":259,"props":1897,"children":1898},{"style":266},[1899],{"type":132,"value":295},{"type":127,"tag":259,"props":1901,"children":1902},{"style":367},[1903],{"type":132,"value":1459},{"type":127,"tag":259,"props":1905,"children":1906},{"style":266},[1907],{"type":132,"value":375},{"type":127,"tag":259,"props":1909,"children":1910},{"style":298},[1911],{"type":132,"value":1912},"-40px",{"type":127,"tag":259,"props":1914,"children":1915},{"style":266},[1916],{"type":132,"value":1917},");",{"type":127,"tag":259,"props":1919,"children":1920},{"style":266},[1921],{"type":132,"value":1922}," }\n",{"type":127,"tag":259,"props":1924,"children":1925},{"class":261,"line":452},[1926,1930,1934,1938,1942,1946,1950,1955,1959],{"type":127,"tag":259,"props":1927,"children":1928},{"style":272},[1929],{"type":132,"value":1438},{"type":127,"tag":259,"props":1931,"children":1932},{"style":266},[1933],{"type":132,"value":1890},{"type":127,"tag":259,"props":1935,"children":1936},{"style":287},[1937],{"type":132,"value":1895},{"type":127,"tag":259,"props":1939,"children":1940},{"style":266},[1941],{"type":132,"value":295},{"type":127,"tag":259,"props":1943,"children":1944},{"style":367},[1945],{"type":132,"value":1459},{"type":127,"tag":259,"props":1947,"children":1948},{"style":266},[1949],{"type":132,"value":375},{"type":127,"tag":259,"props":1951,"children":1952},{"style":298},[1953],{"type":132,"value":1954},"40px",{"type":127,"tag":259,"props":1956,"children":1957},{"style":266},[1958],{"type":132,"value":1917},{"type":127,"tag":259,"props":1960,"children":1961},{"style":266},[1962],{"type":132,"value":1922},{"type":127,"tag":259,"props":1964,"children":1965},{"class":261,"line":474},[1966],{"type":127,"tag":259,"props":1967,"children":1968},{"style":266},[1969],{"type":132,"value":423},{"type":127,"tag":128,"props":1971,"children":1972},{},[1973,1975,1981,1983,1989,1991,1997,1999,2005,2007,2013],{"type":132,"value":1974},"Effects tied to one panel need that panel's own window: the moment it enters at the right edge and the moment it leaves at the left. Both follow from numbers ",{"type":127,"tag":152,"props":1976,"children":1978},{"className":1977},[],[1979],{"type":132,"value":1980},"measure()",{"type":132,"value":1982}," already has. The inputs are the panel's offset ",{"type":127,"tag":152,"props":1984,"children":1986},{"className":1985},[],[1987],{"type":132,"value":1988},"x",{"type":132,"value":1990}," and width ",{"type":127,"tag":152,"props":1992,"children":1994},{"className":1993},[],[1995],{"type":132,"value":1996},"w",{"type":132,"value":1998},", plus the section width ",{"type":127,"tag":152,"props":2000,"children":2002},{"className":2001},[],[2003],{"type":132,"value":2004},"v",{"type":132,"value":2006}," and the travel ",{"type":127,"tag":152,"props":2008,"children":2010},{"className":2009},[],[2011],{"type":132,"value":2012},"d",{"type":132,"value":295},{"type":127,"tag":249,"props":2015,"children":2019},{"className":2016,"code":2018,"language":132,"meta":253},[2017],"language-text","start = clamp((x - v) \u002F d, 0, 1)\nend   = clamp((x + w) \u002F d, 0, 1)\n",[2020],{"type":127,"tag":152,"props":2021,"children":2022},{"__ignoreMap":253},[2023],{"type":132,"value":2018},{"type":127,"tag":128,"props":2025,"children":2026},{},[2027,2029,2034],{"type":132,"value":2028},"The clamp matters at both ends. The first panel is already on screen before the pin starts, so its raw start is negative. The last panel's raw end is above 1. Extend ",{"type":127,"tag":152,"props":2030,"children":2032},{"className":2031},[],[2033],{"type":132,"value":1980},{"type":132,"value":2035}," to write the clamped values onto each panel as custom properties:",{"type":127,"tag":128,"props":2037,"children":2038},{},[2039],{"type":127,"tag":188,"props":2040,"children":2043},{"alt":2041,"src":2042},"Diagram of per-panel animation ranges: start is clamp((x minus v) divided by d, 0, 1) and end is clamp((x plus w) divided by d, 0, 1), with the third panel range running from 25% to 100% of the contain range","\u002Fapi\u002Fblog\u002Fimages\u002Fblog\u002F1\u002F147c955b-4132-48fb-a5cf-1524f4ae588c.webp",[],{"type":127,"tag":249,"props":2045,"children":2047},{"className":711,"code":2046,"language":713,"meta":253,"style":253},"function measure() {\n  const travel = Math.max(0, track.scrollWidth - section.clientWidth);\n  section.style.setProperty(\"--travel\", `${travel}px`);\n\n  const viewport = section.clientWidth;\n  const clamp = (n) => Math.min(1, Math.max(0, n));\n\n  for (const panel of track.children) {\n    const start = travel ? clamp((panel.offsetLeft - viewport) \u002F travel) : 0;\n    const end = travel ? clamp((panel.offsetLeft + panel.offsetWidth) \u002F travel) : 1;\n    panel.style.setProperty(\"--panel-start\", `${(start * 100).toFixed(3)}%`);\n    panel.style.setProperty(\"--panel-end\", `${(end * 100).toFixed(3)}%`);\n  }\n}\n",[2048],{"type":127,"tag":152,"props":2049,"children":2050},{"__ignoreMap":253},[2051,2070,2145,2216,2223,2255,2353,2360,2409,2495,2585,2696,2801,2808],{"type":127,"tag":259,"props":2052,"children":2053},{"class":261,"line":262},[2054,2058,2062,2066],{"type":127,"tag":259,"props":2055,"children":2056},{"style":723},[2057],{"type":132,"value":847},{"type":127,"tag":259,"props":2059,"children":2060},{"style":367},[2061],{"type":132,"value":852},{"type":127,"tag":259,"props":2063,"children":2064},{"style":266},[2065],{"type":132,"value":857},{"type":127,"tag":259,"props":2067,"children":2068},{"style":266},[2069],{"type":132,"value":280},{"type":127,"tag":259,"props":2071,"children":2072},{"class":261,"line":283},[2073,2077,2081,2085,2089,2093,2097,2101,2105,2109,2113,2117,2121,2125,2129,2133,2137,2141],{"type":127,"tag":259,"props":2074,"children":2075},{"style":723},[2076],{"type":132,"value":869},{"type":127,"tag":259,"props":2078,"children":2079},{"style":322},[2080],{"type":132,"value":874},{"type":127,"tag":259,"props":2082,"children":2083},{"style":266},[2084],{"type":132,"value":879},{"type":127,"tag":259,"props":2086,"children":2087},{"style":322},[2088],{"type":132,"value":884},{"type":127,"tag":259,"props":2090,"children":2091},{"style":266},[2092],{"type":132,"value":269},{"type":127,"tag":259,"props":2094,"children":2095},{"style":367},[2096],{"type":132,"value":893},{"type":127,"tag":259,"props":2098,"children":2099},{"style":896},[2100],{"type":132,"value":375},{"type":127,"tag":259,"props":2102,"children":2103},{"style":298},[2104],{"type":132,"value":903},{"type":127,"tag":259,"props":2106,"children":2107},{"style":266},[2108],{"type":132,"value":404},{"type":127,"tag":259,"props":2110,"children":2111},{"style":322},[2112],{"type":132,"value":912},{"type":127,"tag":259,"props":2114,"children":2115},{"style":266},[2116],{"type":132,"value":269},{"type":127,"tag":259,"props":2118,"children":2119},{"style":322},[2120],{"type":132,"value":921},{"type":127,"tag":259,"props":2122,"children":2123},{"style":266},[2124],{"type":132,"value":926},{"type":127,"tag":259,"props":2126,"children":2127},{"style":322},[2128],{"type":132,"value":799},{"type":127,"tag":259,"props":2130,"children":2131},{"style":266},[2132],{"type":132,"value":269},{"type":127,"tag":259,"props":2134,"children":2135},{"style":322},[2136],{"type":132,"value":939},{"type":127,"tag":259,"props":2138,"children":2139},{"style":896},[2140],{"type":132,"value":774},{"type":127,"tag":259,"props":2142,"children":2143},{"style":266},[2144],{"type":132,"value":306},{"type":127,"tag":259,"props":2146,"children":2147},{"class":261,"line":309},[2148,2152,2156,2160,2164,2168,2172,2176,2180,2184,2188,2192,2196,2200,2204,2208,2212],{"type":127,"tag":259,"props":2149,"children":2150},{"style":322},[2151],{"type":132,"value":955},{"type":127,"tag":259,"props":2153,"children":2154},{"style":266},[2155],{"type":132,"value":269},{"type":127,"tag":259,"props":2157,"children":2158},{"style":322},[2159],{"type":132,"value":964},{"type":127,"tag":259,"props":2161,"children":2162},{"style":266},[2163],{"type":132,"value":269},{"type":127,"tag":259,"props":2165,"children":2166},{"style":367},[2167],{"type":132,"value":973},{"type":127,"tag":259,"props":2169,"children":2170},{"style":896},[2171],{"type":132,"value":375},{"type":127,"tag":259,"props":2173,"children":2174},{"style":266},[2175],{"type":132,"value":759},{"type":127,"tag":259,"props":2177,"children":2178},{"style":762},[2179],{"type":132,"value":399},{"type":127,"tag":259,"props":2181,"children":2182},{"style":266},[2183],{"type":132,"value":759},{"type":127,"tag":259,"props":2185,"children":2186},{"style":266},[2187],{"type":132,"value":404},{"type":127,"tag":259,"props":2189,"children":2190},{"style":266},[2191],{"type":132,"value":998},{"type":127,"tag":259,"props":2193,"children":2194},{"style":322},[2195],{"type":132,"value":1003},{"type":127,"tag":259,"props":2197,"children":2198},{"style":266},[2199],{"type":132,"value":1008},{"type":127,"tag":259,"props":2201,"children":2202},{"style":762},[2203],{"type":132,"value":1013},{"type":127,"tag":259,"props":2205,"children":2206},{"style":266},[2207],{"type":132,"value":1018},{"type":127,"tag":259,"props":2209,"children":2210},{"style":896},[2211],{"type":132,"value":774},{"type":127,"tag":259,"props":2213,"children":2214},{"style":266},[2215],{"type":132,"value":306},{"type":127,"tag":259,"props":2217,"children":2218},{"class":261,"line":332},[2219],{"type":127,"tag":259,"props":2220,"children":2221},{"emptyLinePlaceholder":84},[2222],{"type":132,"value":432},{"type":127,"tag":259,"props":2224,"children":2225},{"class":261,"line":354},[2226,2230,2235,2239,2243,2247,2251],{"type":127,"tag":259,"props":2227,"children":2228},{"style":723},[2229],{"type":132,"value":869},{"type":127,"tag":259,"props":2231,"children":2232},{"style":322},[2233],{"type":132,"value":2234}," viewport",{"type":127,"tag":259,"props":2236,"children":2237},{"style":266},[2238],{"type":132,"value":879},{"type":127,"tag":259,"props":2240,"children":2241},{"style":322},[2242],{"type":132,"value":799},{"type":127,"tag":259,"props":2244,"children":2245},{"style":266},[2246],{"type":132,"value":269},{"type":127,"tag":259,"props":2248,"children":2249},{"style":322},[2250],{"type":132,"value":939},{"type":127,"tag":259,"props":2252,"children":2253},{"style":266},[2254],{"type":132,"value":306},{"type":127,"tag":259,"props":2256,"children":2257},{"class":261,"line":417},[2258,2262,2267,2271,2276,2281,2285,2289,2293,2297,2302,2306,2311,2315,2319,2323,2327,2331,2335,2339,2344,2349],{"type":127,"tag":259,"props":2259,"children":2260},{"style":723},[2261],{"type":132,"value":869},{"type":127,"tag":259,"props":2263,"children":2264},{"style":322},[2265],{"type":132,"value":2266}," clamp",{"type":127,"tag":259,"props":2268,"children":2269},{"style":266},[2270],{"type":132,"value":879},{"type":127,"tag":259,"props":2272,"children":2273},{"style":266},[2274],{"type":132,"value":2275}," (",{"type":127,"tag":259,"props":2277,"children":2278},{"style":1423},[2279],{"type":132,"value":2280},"n",{"type":127,"tag":259,"props":2282,"children":2283},{"style":266},[2284],{"type":132,"value":774},{"type":127,"tag":259,"props":2286,"children":2287},{"style":723},[2288],{"type":132,"value":1096},{"type":127,"tag":259,"props":2290,"children":2291},{"style":322},[2292],{"type":132,"value":884},{"type":127,"tag":259,"props":2294,"children":2295},{"style":266},[2296],{"type":132,"value":269},{"type":127,"tag":259,"props":2298,"children":2299},{"style":367},[2300],{"type":132,"value":2301},"min",{"type":127,"tag":259,"props":2303,"children":2304},{"style":896},[2305],{"type":132,"value":375},{"type":127,"tag":259,"props":2307,"children":2308},{"style":298},[2309],{"type":132,"value":2310},"1",{"type":127,"tag":259,"props":2312,"children":2313},{"style":266},[2314],{"type":132,"value":404},{"type":127,"tag":259,"props":2316,"children":2317},{"style":322},[2318],{"type":132,"value":884},{"type":127,"tag":259,"props":2320,"children":2321},{"style":266},[2322],{"type":132,"value":269},{"type":127,"tag":259,"props":2324,"children":2325},{"style":367},[2326],{"type":132,"value":893},{"type":127,"tag":259,"props":2328,"children":2329},{"style":896},[2330],{"type":132,"value":375},{"type":127,"tag":259,"props":2332,"children":2333},{"style":298},[2334],{"type":132,"value":903},{"type":127,"tag":259,"props":2336,"children":2337},{"style":266},[2338],{"type":132,"value":404},{"type":127,"tag":259,"props":2340,"children":2341},{"style":322},[2342],{"type":132,"value":2343}," n",{"type":127,"tag":259,"props":2345,"children":2346},{"style":896},[2347],{"type":132,"value":2348},"))",{"type":127,"tag":259,"props":2350,"children":2351},{"style":266},[2352],{"type":132,"value":306},{"type":127,"tag":259,"props":2354,"children":2355},{"class":261,"line":426},[2356],{"type":127,"tag":259,"props":2357,"children":2358},{"emptyLinePlaceholder":84},[2359],{"type":132,"value":432},{"type":127,"tag":259,"props":2361,"children":2362},{"class":261,"line":435},[2363,2368,2372,2376,2381,2386,2390,2394,2399,2404],{"type":127,"tag":259,"props":2364,"children":2365},{"style":1417},[2366],{"type":132,"value":2367},"  for",{"type":127,"tag":259,"props":2369,"children":2370},{"style":896},[2371],{"type":132,"value":2275},{"type":127,"tag":259,"props":2373,"children":2374},{"style":723},[2375],{"type":132,"value":726},{"type":127,"tag":259,"props":2377,"children":2378},{"style":322},[2379],{"type":132,"value":2380}," panel",{"type":127,"tag":259,"props":2382,"children":2383},{"style":266},[2384],{"type":132,"value":2385}," of",{"type":127,"tag":259,"props":2387,"children":2388},{"style":322},[2389],{"type":132,"value":912},{"type":127,"tag":259,"props":2391,"children":2392},{"style":266},[2393],{"type":132,"value":269},{"type":127,"tag":259,"props":2395,"children":2396},{"style":322},[2397],{"type":132,"value":2398},"children",{"type":127,"tag":259,"props":2400,"children":2401},{"style":896},[2402],{"type":132,"value":2403},") ",{"type":127,"tag":259,"props":2405,"children":2406},{"style":266},[2407],{"type":132,"value":2408},"{\n",{"type":127,"tag":259,"props":2410,"children":2411},{"class":261,"line":452},[2412,2417,2422,2426,2430,2435,2439,2444,2449,2453,2458,2462,2466,2470,2475,2479,2483,2487,2491],{"type":127,"tag":259,"props":2413,"children":2414},{"style":723},[2415],{"type":132,"value":2416},"    const",{"type":127,"tag":259,"props":2418,"children":2419},{"style":322},[2420],{"type":132,"value":2421}," start",{"type":127,"tag":259,"props":2423,"children":2424},{"style":266},[2425],{"type":132,"value":879},{"type":127,"tag":259,"props":2427,"children":2428},{"style":322},[2429],{"type":132,"value":874},{"type":127,"tag":259,"props":2431,"children":2432},{"style":266},[2433],{"type":132,"value":2434}," ?",{"type":127,"tag":259,"props":2436,"children":2437},{"style":367},[2438],{"type":132,"value":2266},{"type":127,"tag":259,"props":2440,"children":2441},{"style":896},[2442],{"type":132,"value":2443},"((",{"type":127,"tag":259,"props":2445,"children":2446},{"style":322},[2447],{"type":132,"value":2448},"panel",{"type":127,"tag":259,"props":2450,"children":2451},{"style":266},[2452],{"type":132,"value":269},{"type":127,"tag":259,"props":2454,"children":2455},{"style":322},[2456],{"type":132,"value":2457},"offsetLeft",{"type":127,"tag":259,"props":2459,"children":2460},{"style":266},[2461],{"type":132,"value":926},{"type":127,"tag":259,"props":2463,"children":2464},{"style":322},[2465],{"type":132,"value":2234},{"type":127,"tag":259,"props":2467,"children":2468},{"style":896},[2469],{"type":132,"value":2403},{"type":127,"tag":259,"props":2471,"children":2472},{"style":266},[2473],{"type":132,"value":2474},"\u002F",{"type":127,"tag":259,"props":2476,"children":2477},{"style":322},[2478],{"type":132,"value":874},{"type":127,"tag":259,"props":2480,"children":2481},{"style":896},[2482],{"type":132,"value":2403},{"type":127,"tag":259,"props":2484,"children":2485},{"style":266},[2486],{"type":132,"value":295},{"type":127,"tag":259,"props":2488,"children":2489},{"style":298},[2490],{"type":132,"value":489},{"type":127,"tag":259,"props":2492,"children":2493},{"style":266},[2494],{"type":132,"value":306},{"type":127,"tag":259,"props":2496,"children":2497},{"class":261,"line":474},[2498,2502,2507,2511,2515,2519,2523,2527,2531,2535,2539,2543,2547,2551,2556,2560,2564,2568,2572,2576,2581],{"type":127,"tag":259,"props":2499,"children":2500},{"style":723},[2501],{"type":132,"value":2416},{"type":127,"tag":259,"props":2503,"children":2504},{"style":322},[2505],{"type":132,"value":2506}," end",{"type":127,"tag":259,"props":2508,"children":2509},{"style":266},[2510],{"type":132,"value":879},{"type":127,"tag":259,"props":2512,"children":2513},{"style":322},[2514],{"type":132,"value":874},{"type":127,"tag":259,"props":2516,"children":2517},{"style":266},[2518],{"type":132,"value":2434},{"type":127,"tag":259,"props":2520,"children":2521},{"style":367},[2522],{"type":132,"value":2266},{"type":127,"tag":259,"props":2524,"children":2525},{"style":896},[2526],{"type":132,"value":2443},{"type":127,"tag":259,"props":2528,"children":2529},{"style":322},[2530],{"type":132,"value":2448},{"type":127,"tag":259,"props":2532,"children":2533},{"style":266},[2534],{"type":132,"value":269},{"type":127,"tag":259,"props":2536,"children":2537},{"style":322},[2538],{"type":132,"value":2457},{"type":127,"tag":259,"props":2540,"children":2541},{"style":266},[2542],{"type":132,"value":385},{"type":127,"tag":259,"props":2544,"children":2545},{"style":322},[2546],{"type":132,"value":2380},{"type":127,"tag":259,"props":2548,"children":2549},{"style":266},[2550],{"type":132,"value":269},{"type":127,"tag":259,"props":2552,"children":2553},{"style":322},[2554],{"type":132,"value":2555},"offsetWidth",{"type":127,"tag":259,"props":2557,"children":2558},{"style":896},[2559],{"type":132,"value":2403},{"type":127,"tag":259,"props":2561,"children":2562},{"style":266},[2563],{"type":132,"value":2474},{"type":127,"tag":259,"props":2565,"children":2566},{"style":322},[2567],{"type":132,"value":874},{"type":127,"tag":259,"props":2569,"children":2570},{"style":896},[2571],{"type":132,"value":2403},{"type":127,"tag":259,"props":2573,"children":2574},{"style":266},[2575],{"type":132,"value":295},{"type":127,"tag":259,"props":2577,"children":2578},{"style":298},[2579],{"type":132,"value":2580}," 1",{"type":127,"tag":259,"props":2582,"children":2583},{"style":266},[2584],{"type":132,"value":306},{"type":127,"tag":259,"props":2586,"children":2587},{"class":261,"line":496},[2588,2593,2597,2601,2605,2609,2613,2617,2622,2626,2630,2634,2639,2644,2649,2653,2657,2662,2666,2671,2675,2679,2684,2688,2692],{"type":127,"tag":259,"props":2589,"children":2590},{"style":322},[2591],{"type":132,"value":2592},"    panel",{"type":127,"tag":259,"props":2594,"children":2595},{"style":266},[2596],{"type":132,"value":269},{"type":127,"tag":259,"props":2598,"children":2599},{"style":322},[2600],{"type":132,"value":964},{"type":127,"tag":259,"props":2602,"children":2603},{"style":266},[2604],{"type":132,"value":269},{"type":127,"tag":259,"props":2606,"children":2607},{"style":367},[2608],{"type":132,"value":973},{"type":127,"tag":259,"props":2610,"children":2611},{"style":896},[2612],{"type":132,"value":375},{"type":127,"tag":259,"props":2614,"children":2615},{"style":266},[2616],{"type":132,"value":759},{"type":127,"tag":259,"props":2618,"children":2619},{"style":762},[2620],{"type":132,"value":2621},"--panel-start",{"type":127,"tag":259,"props":2623,"children":2624},{"style":266},[2625],{"type":132,"value":759},{"type":127,"tag":259,"props":2627,"children":2628},{"style":266},[2629],{"type":132,"value":404},{"type":127,"tag":259,"props":2631,"children":2632},{"style":266},[2633],{"type":132,"value":998},{"type":127,"tag":259,"props":2635,"children":2636},{"style":322},[2637],{"type":132,"value":2638},"(start ",{"type":127,"tag":259,"props":2640,"children":2641},{"style":266},[2642],{"type":132,"value":2643},"*",{"type":127,"tag":259,"props":2645,"children":2646},{"style":298},[2647],{"type":132,"value":2648}," 100",{"type":127,"tag":259,"props":2650,"children":2651},{"style":322},[2652],{"type":132,"value":774},{"type":127,"tag":259,"props":2654,"children":2655},{"style":266},[2656],{"type":132,"value":269},{"type":127,"tag":259,"props":2658,"children":2659},{"style":367},[2660],{"type":132,"value":2661},"toFixed",{"type":127,"tag":259,"props":2663,"children":2664},{"style":322},[2665],{"type":132,"value":375},{"type":127,"tag":259,"props":2667,"children":2668},{"style":298},[2669],{"type":132,"value":2670},"3",{"type":127,"tag":259,"props":2672,"children":2673},{"style":322},[2674],{"type":132,"value":774},{"type":127,"tag":259,"props":2676,"children":2677},{"style":266},[2678],{"type":132,"value":1008},{"type":127,"tag":259,"props":2680,"children":2681},{"style":762},[2682],{"type":132,"value":2683},"%",{"type":127,"tag":259,"props":2685,"children":2686},{"style":266},[2687],{"type":132,"value":1018},{"type":127,"tag":259,"props":2689,"children":2690},{"style":896},[2691],{"type":132,"value":774},{"type":127,"tag":259,"props":2693,"children":2694},{"style":266},[2695],{"type":132,"value":306},{"type":127,"tag":259,"props":2697,"children":2698},{"class":261,"line":518},[2699,2703,2707,2711,2715,2719,2723,2727,2732,2736,2740,2744,2749,2753,2757,2761,2765,2769,2773,2777,2781,2785,2789,2793,2797],{"type":127,"tag":259,"props":2700,"children":2701},{"style":322},[2702],{"type":132,"value":2592},{"type":127,"tag":259,"props":2704,"children":2705},{"style":266},[2706],{"type":132,"value":269},{"type":127,"tag":259,"props":2708,"children":2709},{"style":322},[2710],{"type":132,"value":964},{"type":127,"tag":259,"props":2712,"children":2713},{"style":266},[2714],{"type":132,"value":269},{"type":127,"tag":259,"props":2716,"children":2717},{"style":367},[2718],{"type":132,"value":973},{"type":127,"tag":259,"props":2720,"children":2721},{"style":896},[2722],{"type":132,"value":375},{"type":127,"tag":259,"props":2724,"children":2725},{"style":266},[2726],{"type":132,"value":759},{"type":127,"tag":259,"props":2728,"children":2729},{"style":762},[2730],{"type":132,"value":2731},"--panel-end",{"type":127,"tag":259,"props":2733,"children":2734},{"style":266},[2735],{"type":132,"value":759},{"type":127,"tag":259,"props":2737,"children":2738},{"style":266},[2739],{"type":132,"value":404},{"type":127,"tag":259,"props":2741,"children":2742},{"style":266},[2743],{"type":132,"value":998},{"type":127,"tag":259,"props":2745,"children":2746},{"style":322},[2747],{"type":132,"value":2748},"(end ",{"type":127,"tag":259,"props":2750,"children":2751},{"style":266},[2752],{"type":132,"value":2643},{"type":127,"tag":259,"props":2754,"children":2755},{"style":298},[2756],{"type":132,"value":2648},{"type":127,"tag":259,"props":2758,"children":2759},{"style":322},[2760],{"type":132,"value":774},{"type":127,"tag":259,"props":2762,"children":2763},{"style":266},[2764],{"type":132,"value":269},{"type":127,"tag":259,"props":2766,"children":2767},{"style":367},[2768],{"type":132,"value":2661},{"type":127,"tag":259,"props":2770,"children":2771},{"style":322},[2772],{"type":132,"value":375},{"type":127,"tag":259,"props":2774,"children":2775},{"style":298},[2776],{"type":132,"value":2670},{"type":127,"tag":259,"props":2778,"children":2779},{"style":322},[2780],{"type":132,"value":774},{"type":127,"tag":259,"props":2782,"children":2783},{"style":266},[2784],{"type":132,"value":1008},{"type":127,"tag":259,"props":2786,"children":2787},{"style":762},[2788],{"type":132,"value":2683},{"type":127,"tag":259,"props":2790,"children":2791},{"style":266},[2792],{"type":132,"value":1018},{"type":127,"tag":259,"props":2794,"children":2795},{"style":896},[2796],{"type":132,"value":774},{"type":127,"tag":259,"props":2798,"children":2799},{"style":266},[2800],{"type":132,"value":306},{"type":127,"tag":259,"props":2802,"children":2803},{"class":261,"line":539},[2804],{"type":127,"tag":259,"props":2805,"children":2806},{"style":266},[2807],{"type":132,"value":1497},{"type":127,"tag":259,"props":2809,"children":2810},{"class":261,"line":560},[2811],{"type":127,"tag":259,"props":2812,"children":2813},{"style":266},[2814],{"type":132,"value":423},{"type":127,"tag":128,"props":2816,"children":2817},{},[2818],{"type":132,"value":2819},"Any descendant can then animate over its own panel's visibility:",{"type":127,"tag":249,"props":2821,"children":2823},{"className":251,"code":2822,"language":17,"meta":253,"style":253},".hs__panel-media {\n  animation: drift linear both;\n  animation-timeline: --hs;\n  animation-range: contain var(--panel-start) contain var(--panel-end);\n}\n",[2824],{"type":127,"tag":152,"props":2825,"children":2826},{"__ignoreMap":253},[2827,2843,2862,2881,2934],{"type":127,"tag":259,"props":2828,"children":2829},{"class":261,"line":262},[2830,2834,2839],{"type":127,"tag":259,"props":2831,"children":2832},{"style":266},[2833],{"type":132,"value":269},{"type":127,"tag":259,"props":2835,"children":2836},{"style":272},[2837],{"type":132,"value":2838},"hs__panel-media",{"type":127,"tag":259,"props":2840,"children":2841},{"style":266},[2842],{"type":132,"value":280},{"type":127,"tag":259,"props":2844,"children":2845},{"class":261,"line":283},[2846,2850,2854,2858],{"type":127,"tag":259,"props":2847,"children":2848},{"style":287},[2849],{"type":132,"value":1328},{"type":127,"tag":259,"props":2851,"children":2852},{"style":266},[2853],{"type":132,"value":295},{"type":127,"tag":259,"props":2855,"children":2856},{"style":322},[2857],{"type":132,"value":1791},{"type":127,"tag":259,"props":2859,"children":2860},{"style":266},[2861],{"type":132,"value":306},{"type":127,"tag":259,"props":2863,"children":2864},{"class":261,"line":309},[2865,2869,2873,2877],{"type":127,"tag":259,"props":2866,"children":2867},{"style":322},[2868],{"type":132,"value":1349},{"type":127,"tag":259,"props":2870,"children":2871},{"style":266},[2872],{"type":132,"value":295},{"type":127,"tag":259,"props":2874,"children":2875},{"style":322},[2876],{"type":132,"value":1358},{"type":127,"tag":259,"props":2878,"children":2879},{"style":266},[2880],{"type":132,"value":306},{"type":127,"tag":259,"props":2882,"children":2883},{"class":261,"line":332},[2884,2888,2892,2896,2901,2905,2909,2913,2917,2921,2925,2929],{"type":127,"tag":259,"props":2885,"children":2886},{"style":322},[2887],{"type":132,"value":1370},{"type":127,"tag":259,"props":2889,"children":2890},{"style":266},[2891],{"type":132,"value":295},{"type":127,"tag":259,"props":2893,"children":2894},{"style":322},[2895],{"type":132,"value":1379},{"type":127,"tag":259,"props":2897,"children":2898},{"style":367},[2899],{"type":132,"value":2900},"var",{"type":127,"tag":259,"props":2902,"children":2903},{"style":266},[2904],{"type":132,"value":375},{"type":127,"tag":259,"props":2906,"children":2907},{"style":322},[2908],{"type":132,"value":2621},{"type":127,"tag":259,"props":2910,"children":2911},{"style":266},[2912],{"type":132,"value":774},{"type":127,"tag":259,"props":2914,"children":2915},{"style":322},[2916],{"type":132,"value":1379},{"type":127,"tag":259,"props":2918,"children":2919},{"style":367},[2920],{"type":132,"value":2900},{"type":127,"tag":259,"props":2922,"children":2923},{"style":266},[2924],{"type":132,"value":375},{"type":127,"tag":259,"props":2926,"children":2927},{"style":322},[2928],{"type":132,"value":2731},{"type":127,"tag":259,"props":2930,"children":2931},{"style":266},[2932],{"type":132,"value":2933},");\n",{"type":127,"tag":259,"props":2935,"children":2936},{"class":261,"line":354},[2937],{"type":127,"tag":259,"props":2938,"children":2939},{"style":266},[2940],{"type":132,"value":423},{"type":127,"tag":128,"props":2942,"children":2943},{},[2944,2946,2952,2953,2959],{"type":132,"value":2945},"The plugin hands per-panel ranges to other code the same way, as inherited ",{"type":127,"tag":152,"props":2947,"children":2949},{"className":2948},[],[2950],{"type":132,"value":2951},"--arts-hs-panel-start",{"type":132,"value":1608},{"type":127,"tag":152,"props":2954,"children":2956},{"className":2955},[],[2957],{"type":132,"value":2958},"--arts-hs-panel-end",{"type":132,"value":2960}," properties written on resize. In my test page, the third panel's range came out as 25% to 100%. Its animation read 0 when the panel's left edge touched the right side of the viewport, and 1 when its right edge left on the other side.",{"type":127,"tag":128,"props":2962,"children":2963},{},[2964],{"type":132,"value":2965},"Elements outside the section cannot see a timeline named inside it. A fixed progress bar at the top of the page is the usual case. For those, build the same timeline in JavaScript and hand it to WAAPI:",{"type":127,"tag":249,"props":2967,"children":2969},{"className":711,"code":2968,"language":713,"meta":253,"style":253},"const progress = document.querySelector(\".progress\");\n\nif (\"ViewTimeline\" in window) {\n  const timeline = new ViewTimeline({\n    subject: section,\n    axis: \"block\",\n    inset: \"0px\",\n  });\n\n  progress.animate(\n    { transform: [\"scaleX(0)\", \"scaleX(1)\"] },\n    {\n      timeline,\n      rangeStart: \"contain 0%\",\n      rangeEnd: \"contain 100%\",\n      easing: \"linear\",\n      fill: \"both\",\n    }\n  );\n}\n",[2970],{"type":127,"tag":152,"props":2971,"children":2972},{"__ignoreMap":253},[2973,3026,3033,3072,3105,3126,3156,3185,3201,3208,3230,3291,3299,3311,3339,3367,3395,3424,3432,3444],{"type":127,"tag":259,"props":2974,"children":2975},{"class":261,"line":262},[2976,2980,2985,2989,2993,2997,3001,3005,3009,3014,3018,3022],{"type":127,"tag":259,"props":2977,"children":2978},{"style":723},[2979],{"type":132,"value":726},{"type":127,"tag":259,"props":2981,"children":2982},{"style":322},[2983],{"type":132,"value":2984}," progress ",{"type":127,"tag":259,"props":2986,"children":2987},{"style":266},[2988],{"type":132,"value":736},{"type":127,"tag":259,"props":2990,"children":2991},{"style":322},[2992],{"type":132,"value":741},{"type":127,"tag":259,"props":2994,"children":2995},{"style":266},[2996],{"type":132,"value":269},{"type":127,"tag":259,"props":2998,"children":2999},{"style":367},[3000],{"type":132,"value":750},{"type":127,"tag":259,"props":3002,"children":3003},{"style":322},[3004],{"type":132,"value":375},{"type":127,"tag":259,"props":3006,"children":3007},{"style":266},[3008],{"type":132,"value":759},{"type":127,"tag":259,"props":3010,"children":3011},{"style":762},[3012],{"type":132,"value":3013},".progress",{"type":127,"tag":259,"props":3015,"children":3016},{"style":266},[3017],{"type":132,"value":759},{"type":127,"tag":259,"props":3019,"children":3020},{"style":322},[3021],{"type":132,"value":774},{"type":127,"tag":259,"props":3023,"children":3024},{"style":266},[3025],{"type":132,"value":306},{"type":127,"tag":259,"props":3027,"children":3028},{"class":261,"line":283},[3029],{"type":127,"tag":259,"props":3030,"children":3031},{"emptyLinePlaceholder":84},[3032],{"type":132,"value":432},{"type":127,"tag":259,"props":3034,"children":3035},{"class":261,"line":309},[3036,3041,3045,3049,3054,3058,3063,3068],{"type":127,"tag":259,"props":3037,"children":3038},{"style":1417},[3039],{"type":132,"value":3040},"if",{"type":127,"tag":259,"props":3042,"children":3043},{"style":322},[3044],{"type":132,"value":2275},{"type":127,"tag":259,"props":3046,"children":3047},{"style":266},[3048],{"type":132,"value":759},{"type":127,"tag":259,"props":3050,"children":3051},{"style":762},[3052],{"type":132,"value":3053},"ViewTimeline",{"type":127,"tag":259,"props":3055,"children":3056},{"style":266},[3057],{"type":132,"value":759},{"type":127,"tag":259,"props":3059,"children":3060},{"style":266},[3061],{"type":132,"value":3062}," in",{"type":127,"tag":259,"props":3064,"children":3065},{"style":322},[3066],{"type":132,"value":3067}," window) ",{"type":127,"tag":259,"props":3069,"children":3070},{"style":266},[3071],{"type":132,"value":2408},{"type":127,"tag":259,"props":3073,"children":3074},{"class":261,"line":332},[3075,3079,3084,3088,3092,3097,3101],{"type":127,"tag":259,"props":3076,"children":3077},{"style":723},[3078],{"type":132,"value":869},{"type":127,"tag":259,"props":3080,"children":3081},{"style":322},[3082],{"type":132,"value":3083}," timeline",{"type":127,"tag":259,"props":3085,"children":3086},{"style":266},[3087],{"type":132,"value":879},{"type":127,"tag":259,"props":3089,"children":3090},{"style":266},[3091],{"type":132,"value":1078},{"type":127,"tag":259,"props":3093,"children":3094},{"style":367},[3095],{"type":132,"value":3096}," ViewTimeline",{"type":127,"tag":259,"props":3098,"children":3099},{"style":896},[3100],{"type":132,"value":375},{"type":127,"tag":259,"props":3102,"children":3103},{"style":266},[3104],{"type":132,"value":2408},{"type":127,"tag":259,"props":3106,"children":3107},{"class":261,"line":354},[3108,3113,3117,3121],{"type":127,"tag":259,"props":3109,"children":3110},{"style":896},[3111],{"type":132,"value":3112},"    subject",{"type":127,"tag":259,"props":3114,"children":3115},{"style":266},[3116],{"type":132,"value":295},{"type":127,"tag":259,"props":3118,"children":3119},{"style":322},[3120],{"type":132,"value":799},{"type":127,"tag":259,"props":3122,"children":3123},{"style":266},[3124],{"type":132,"value":3125},",\n",{"type":127,"tag":259,"props":3127,"children":3128},{"class":261,"line":417},[3129,3134,3138,3143,3148,3152],{"type":127,"tag":259,"props":3130,"children":3131},{"style":896},[3132],{"type":132,"value":3133},"    axis",{"type":127,"tag":259,"props":3135,"children":3136},{"style":266},[3137],{"type":132,"value":295},{"type":127,"tag":259,"props":3139,"children":3140},{"style":266},[3141],{"type":132,"value":3142}," \"",{"type":127,"tag":259,"props":3144,"children":3145},{"style":762},[3146],{"type":132,"value":3147},"block",{"type":127,"tag":259,"props":3149,"children":3150},{"style":266},[3151],{"type":132,"value":759},{"type":127,"tag":259,"props":3153,"children":3154},{"style":266},[3155],{"type":132,"value":3125},{"type":127,"tag":259,"props":3157,"children":3158},{"class":261,"line":426},[3159,3164,3168,3172,3177,3181],{"type":127,"tag":259,"props":3160,"children":3161},{"style":896},[3162],{"type":132,"value":3163},"    inset",{"type":127,"tag":259,"props":3165,"children":3166},{"style":266},[3167],{"type":132,"value":295},{"type":127,"tag":259,"props":3169,"children":3170},{"style":266},[3171],{"type":132,"value":3142},{"type":127,"tag":259,"props":3173,"children":3174},{"style":762},[3175],{"type":132,"value":3176},"0px",{"type":127,"tag":259,"props":3178,"children":3179},{"style":266},[3180],{"type":132,"value":759},{"type":127,"tag":259,"props":3182,"children":3183},{"style":266},[3184],{"type":132,"value":3125},{"type":127,"tag":259,"props":3186,"children":3187},{"class":261,"line":435},[3188,3193,3197],{"type":127,"tag":259,"props":3189,"children":3190},{"style":266},[3191],{"type":132,"value":3192},"  }",{"type":127,"tag":259,"props":3194,"children":3195},{"style":896},[3196],{"type":132,"value":774},{"type":127,"tag":259,"props":3198,"children":3199},{"style":266},[3200],{"type":132,"value":306},{"type":127,"tag":259,"props":3202,"children":3203},{"class":261,"line":452},[3204],{"type":127,"tag":259,"props":3205,"children":3206},{"emptyLinePlaceholder":84},[3207],{"type":132,"value":432},{"type":127,"tag":259,"props":3209,"children":3210},{"class":261,"line":474},[3211,3216,3220,3225],{"type":127,"tag":259,"props":3212,"children":3213},{"style":322},[3214],{"type":132,"value":3215},"  progress",{"type":127,"tag":259,"props":3217,"children":3218},{"style":266},[3219],{"type":132,"value":269},{"type":127,"tag":259,"props":3221,"children":3222},{"style":367},[3223],{"type":132,"value":3224},"animate",{"type":127,"tag":259,"props":3226,"children":3227},{"style":896},[3228],{"type":132,"value":3229},"(\n",{"type":127,"tag":259,"props":3231,"children":3232},{"class":261,"line":496},[3233,3238,3242,3246,3251,3255,3260,3264,3268,3272,3277,3281,3286],{"type":127,"tag":259,"props":3234,"children":3235},{"style":266},[3236],{"type":132,"value":3237},"    {",{"type":127,"tag":259,"props":3239,"children":3240},{"style":896},[3241],{"type":132,"value":1895},{"type":127,"tag":259,"props":3243,"children":3244},{"style":266},[3245],{"type":132,"value":295},{"type":127,"tag":259,"props":3247,"children":3248},{"style":896},[3249],{"type":132,"value":3250}," [",{"type":127,"tag":259,"props":3252,"children":3253},{"style":266},[3254],{"type":132,"value":759},{"type":127,"tag":259,"props":3256,"children":3257},{"style":762},[3258],{"type":132,"value":3259},"scaleX(0)",{"type":127,"tag":259,"props":3261,"children":3262},{"style":266},[3263],{"type":132,"value":759},{"type":127,"tag":259,"props":3265,"children":3266},{"style":266},[3267],{"type":132,"value":404},{"type":127,"tag":259,"props":3269,"children":3270},{"style":266},[3271],{"type":132,"value":3142},{"type":127,"tag":259,"props":3273,"children":3274},{"style":762},[3275],{"type":132,"value":3276},"scaleX(1)",{"type":127,"tag":259,"props":3278,"children":3279},{"style":266},[3280],{"type":132,"value":759},{"type":127,"tag":259,"props":3282,"children":3283},{"style":896},[3284],{"type":132,"value":3285},"] ",{"type":127,"tag":259,"props":3287,"children":3288},{"style":266},[3289],{"type":132,"value":3290},"},\n",{"type":127,"tag":259,"props":3292,"children":3293},{"class":261,"line":518},[3294],{"type":127,"tag":259,"props":3295,"children":3296},{"style":266},[3297],{"type":132,"value":3298},"    {\n",{"type":127,"tag":259,"props":3300,"children":3301},{"class":261,"line":539},[3302,3307],{"type":127,"tag":259,"props":3303,"children":3304},{"style":322},[3305],{"type":132,"value":3306},"      timeline",{"type":127,"tag":259,"props":3308,"children":3309},{"style":266},[3310],{"type":132,"value":3125},{"type":127,"tag":259,"props":3312,"children":3313},{"class":261,"line":560},[3314,3319,3323,3327,3331,3335],{"type":127,"tag":259,"props":3315,"children":3316},{"style":896},[3317],{"type":132,"value":3318},"      rangeStart",{"type":127,"tag":259,"props":3320,"children":3321},{"style":266},[3322],{"type":132,"value":295},{"type":127,"tag":259,"props":3324,"children":3325},{"style":266},[3326],{"type":132,"value":3142},{"type":127,"tag":259,"props":3328,"children":3329},{"style":762},[3330],{"type":132,"value":1535},{"type":127,"tag":259,"props":3332,"children":3333},{"style":266},[3334],{"type":132,"value":759},{"type":127,"tag":259,"props":3336,"children":3337},{"style":266},[3338],{"type":132,"value":3125},{"type":127,"tag":259,"props":3340,"children":3341},{"class":261,"line":568},[3342,3347,3351,3355,3359,3363],{"type":127,"tag":259,"props":3343,"children":3344},{"style":896},[3345],{"type":132,"value":3346},"      rangeEnd",{"type":127,"tag":259,"props":3348,"children":3349},{"style":266},[3350],{"type":132,"value":295},{"type":127,"tag":259,"props":3352,"children":3353},{"style":266},[3354],{"type":132,"value":3142},{"type":127,"tag":259,"props":3356,"children":3357},{"style":762},[3358],{"type":132,"value":1543},{"type":127,"tag":259,"props":3360,"children":3361},{"style":266},[3362],{"type":132,"value":759},{"type":127,"tag":259,"props":3364,"children":3365},{"style":266},[3366],{"type":132,"value":3125},{"type":127,"tag":259,"props":3368,"children":3369},{"class":261,"line":576},[3370,3375,3379,3383,3387,3391],{"type":127,"tag":259,"props":3371,"children":3372},{"style":896},[3373],{"type":132,"value":3374},"      easing",{"type":127,"tag":259,"props":3376,"children":3377},{"style":266},[3378],{"type":132,"value":295},{"type":127,"tag":259,"props":3380,"children":3381},{"style":266},[3382],{"type":132,"value":3142},{"type":127,"tag":259,"props":3384,"children":3385},{"style":762},[3386],{"type":132,"value":1571},{"type":127,"tag":259,"props":3388,"children":3389},{"style":266},[3390],{"type":132,"value":759},{"type":127,"tag":259,"props":3392,"children":3393},{"style":266},[3394],{"type":132,"value":3125},{"type":127,"tag":259,"props":3396,"children":3397},{"class":261,"line":593},[3398,3403,3407,3411,3416,3420],{"type":127,"tag":259,"props":3399,"children":3400},{"style":896},[3401],{"type":132,"value":3402},"      fill",{"type":127,"tag":259,"props":3404,"children":3405},{"style":266},[3406],{"type":132,"value":295},{"type":127,"tag":259,"props":3408,"children":3409},{"style":266},[3410],{"type":132,"value":3142},{"type":127,"tag":259,"props":3412,"children":3413},{"style":762},[3414],{"type":132,"value":3415},"both",{"type":127,"tag":259,"props":3417,"children":3418},{"style":266},[3419],{"type":132,"value":759},{"type":127,"tag":259,"props":3421,"children":3422},{"style":266},[3423],{"type":132,"value":3125},{"type":127,"tag":259,"props":3425,"children":3426},{"class":261,"line":623},[3427],{"type":127,"tag":259,"props":3428,"children":3429},{"style":266},[3430],{"type":132,"value":3431},"    }\n",{"type":127,"tag":259,"props":3433,"children":3434},{"class":261,"line":644},[3435,3440],{"type":127,"tag":259,"props":3436,"children":3437},{"style":896},[3438],{"type":132,"value":3439},"  )",{"type":127,"tag":259,"props":3441,"children":3442},{"style":266},[3443],{"type":132,"value":306},{"type":127,"tag":259,"props":3445,"children":3447},{"class":261,"line":3446},20,[3448],{"type":127,"tag":259,"props":3449,"children":3450},{"style":266},[3451],{"type":132,"value":423},{"type":127,"tag":128,"props":3453,"children":3454},{},[3455,3457,3463],{"type":132,"value":3456},"The ",{"type":127,"tag":152,"props":3458,"children":3460},{"className":3459},[],[3461],{"type":132,"value":3462},"\"ViewTimeline\" in window",{"type":132,"value":3464}," check keeps the script from throwing where the API is missing. What those browsers show instead is covered below.",{"type":127,"tag":140,"props":3466,"children":3468},{"id":3467},"does-this-css-horizontal-scroll-animation-really-run-off-the-main-thread",[3469],{"type":132,"value":3470},"Does this CSS horizontal scroll animation really run off the main thread?",{"type":127,"tag":128,"props":3472,"children":3473},{},[3474,3476,3481],{"type":132,"value":3475},"This build still reads layout and writes styles in ",{"type":127,"tag":152,"props":3477,"children":3479},{"className":3478},[],[3480],{"type":132,"value":1980},{"type":132,"value":3482},", but only on load and resize. The per-frame loop is gone. Whether the browser then runs the animation on the compositor depends on the browser and the property.",{"type":127,"tag":128,"props":3484,"children":3485},{},[3486,3488,3493,3495,3500,3502,3509],{"type":132,"value":3487},"Chrome and Edge shipped scroll-driven animations in version 115, and Chrome's own case study dates the off-main-thread gains to 116. There, ",{"type":127,"tag":152,"props":3489,"children":3491},{"className":3490},[],[3492],{"type":132,"value":157},{"type":132,"value":3494},", ",{"type":127,"tag":152,"props":3496,"children":3498},{"className":3497},[],[3499],{"type":132,"value":1738},{"type":132,"value":3501}," and some filters can run on the compositor. Safari shipped support in 26.0 on the main thread, and 26.4 moved it to the compositor. ",{"type":127,"tag":161,"props":3503,"children":3506},{"href":3504,"rel":3505},"https:\u002F\u002Fwebkit.org\u002Fblog\u002F17862\u002Fwebkit-features-for-safari-26-4\u002F",[165],[3507],{"type":132,"value":3508},"WebKit's release notes",{"type":132,"value":3510}," promise animations that \"stay smooth even when the main thread is busy\". Firefox 156, stable when I tested, has no native support. It is on by default only in Nightly.",{"type":127,"tag":128,"props":3512,"children":3513},{},[3514,3516,3522,3524,3530,3532,3537,3539,3545,3547,3552,3554,3560],{"type":132,"value":3515},"I checked the test page with a Chrome 153 performance trace. Chrome emits an ",{"type":127,"tag":152,"props":3517,"children":3519},{"className":3518},[],[3520],{"type":132,"value":3521},"Animation",{"type":132,"value":3523}," trace event for each running animation and attaches a ",{"type":127,"tag":152,"props":3525,"children":3527},{"className":3526},[],[3528],{"type":132,"value":3529},"compositeFailed",{"type":132,"value":3531}," reason when it cannot composite one. All four animations on the page came through clean, the WAAPI progress bar included. As a control, I switched the track's keyframe from ",{"type":127,"tag":152,"props":3533,"children":3535},{"className":3534},[],[3536],{"type":132,"value":157},{"type":132,"value":3538}," to ",{"type":127,"tag":152,"props":3540,"children":3542},{"className":3541},[],[3543],{"type":132,"value":3544},"margin-left",{"type":132,"value":3546},". The same trace then reported ",{"type":127,"tag":152,"props":3548,"children":3550},{"className":3549},[],[3551],{"type":132,"value":3529},{"type":132,"value":3553}," with ",{"type":127,"tag":152,"props":3555,"children":3557},{"className":3556},[],[3558],{"type":132,"value":3559},"unsupportedProperties: [\"margin-left\"]",{"type":132,"value":269},{"type":127,"tag":128,"props":3562,"children":3563},{},[3564],{"type":132,"value":3565},"To run the same check by hand, record a profile in the Chrome DevTools Performance panel while scrolling through the section. Then open the Animations track. DevTools puts a red triangle on non-composited animations and lists the reason in the Summary tab. No triangle on the track animation means it runs on the compositor.",{"type":127,"tag":128,"props":3567,"children":3568},{},[3569,3571,3578],{"type":132,"value":3570},"I did not measure frame rates under load. Chrome's ",{"type":127,"tag":161,"props":3572,"children":3575},{"href":3573,"rel":3574},"https:\u002F\u002Fdeveloper.chrome.com\u002Fblog\u002Fscroll-animation-performance-case-study",[165],[3576],{"type":132,"value":3577},"case study",{"type":132,"value":3579}," shows that behavior by running a heavy loop every 100 milliseconds next to two progress bars. The scroll listener version turns janky. The scroll-driven one is \"completely unaffected by the heavy JavaScript work\".",{"type":127,"tag":128,"props":3581,"children":3582},{},[3583,3585,3590,3591,3596,3598,3604,3605,3611],{"type":132,"value":3584},"The practical rule: put ",{"type":127,"tag":152,"props":3586,"children":3588},{"className":3587},[],[3589],{"type":132,"value":157},{"type":132,"value":1608},{"type":127,"tag":152,"props":3592,"children":3594},{"className":3593},[],[3595],{"type":132,"value":1738},{"type":132,"value":3597}," on the scroll timeline. Anything that changes layout, such as ",{"type":127,"tag":152,"props":3599,"children":3601},{"className":3600},[],[3602],{"type":132,"value":3603},"width",{"type":132,"value":1732},{"type":127,"tag":152,"props":3606,"children":3608},{"className":3607},[],[3609],{"type":132,"value":3610},"margin",{"type":132,"value":3612},", sends the work back to the main thread even without a listener.",{"type":127,"tag":140,"props":3614,"children":3616},{"id":3615},"what-happens-in-browsers-without-scroll-driven-animations",[3617],{"type":132,"value":3618},"What happens in browsers without scroll-driven animations?",{"type":127,"tag":128,"props":3620,"children":3621},{},[3622],{"type":132,"value":3623},"Treat the horizontal layout as an enhancement over a readable vertical stack. Where the timeline cannot run, a sticky track that never moves is worse than no effect: the first panel pins and the others are unreachable. So unsupported browsers get the stack:",{"type":127,"tag":249,"props":3625,"children":3627},{"className":251,"code":3626,"language":17,"meta":253,"style":253},"@supports not (animation-range: contain 0% contain 100%) {\n  .hs {\n    height: auto;\n  }\n\n  .hs__track {\n    position: static;\n    flex-direction: column;\n    width: auto;\n    height: auto;\n  }\n}\n",[3628],{"type":127,"tag":152,"props":3629,"children":3630},{"__ignoreMap":253},[3631,3680,3696,3716,3723,3730,3745,3766,3787,3807,3826,3833],{"type":127,"tag":259,"props":3632,"children":3633},{"class":261,"line":262},[3634,3639,3644,3648,3652,3656,3660,3664,3668,3672,3676],{"type":127,"tag":259,"props":3635,"children":3636},{"style":1417},[3637],{"type":132,"value":3638},"@supports",{"type":127,"tag":259,"props":3640,"children":3641},{"style":266},[3642],{"type":132,"value":3643}," not",{"type":127,"tag":259,"props":3645,"children":3646},{"style":266},[3647],{"type":132,"value":2275},{"type":127,"tag":259,"props":3649,"children":3650},{"style":322},[3651],{"type":132,"value":1614},{"type":127,"tag":259,"props":3653,"children":3654},{"style":266},[3655],{"type":132,"value":295},{"type":127,"tag":259,"props":3657,"children":3658},{"style":322},[3659],{"type":132,"value":1379},{"type":127,"tag":259,"props":3661,"children":3662},{"style":298},[3663],{"type":132,"value":1384},{"type":127,"tag":259,"props":3665,"children":3666},{"style":322},[3667],{"type":132,"value":1379},{"type":127,"tag":259,"props":3669,"children":3670},{"style":298},[3671],{"type":132,"value":1393},{"type":127,"tag":259,"props":3673,"children":3674},{"style":266},[3675],{"type":132,"value":774},{"type":127,"tag":259,"props":3677,"children":3678},{"style":266},[3679],{"type":132,"value":280},{"type":127,"tag":259,"props":3681,"children":3682},{"class":261,"line":283},[3683,3688,3692],{"type":127,"tag":259,"props":3684,"children":3685},{"style":266},[3686],{"type":132,"value":3687},"  .",{"type":127,"tag":259,"props":3689,"children":3690},{"style":272},[3691],{"type":132,"value":275},{"type":127,"tag":259,"props":3693,"children":3694},{"style":266},[3695],{"type":132,"value":280},{"type":127,"tag":259,"props":3697,"children":3698},{"class":261,"line":309},[3699,3704,3708,3712],{"type":127,"tag":259,"props":3700,"children":3701},{"style":287},[3702],{"type":132,"value":3703},"    height",{"type":127,"tag":259,"props":3705,"children":3706},{"style":266},[3707],{"type":132,"value":295},{"type":127,"tag":259,"props":3709,"children":3710},{"style":322},[3711],{"type":132,"value":616},{"type":127,"tag":259,"props":3713,"children":3714},{"style":266},[3715],{"type":132,"value":306},{"type":127,"tag":259,"props":3717,"children":3718},{"class":261,"line":332},[3719],{"type":127,"tag":259,"props":3720,"children":3721},{"style":266},[3722],{"type":132,"value":1497},{"type":127,"tag":259,"props":3724,"children":3725},{"class":261,"line":354},[3726],{"type":127,"tag":259,"props":3727,"children":3728},{"emptyLinePlaceholder":84},[3729],{"type":132,"value":432},{"type":127,"tag":259,"props":3731,"children":3732},{"class":261,"line":417},[3733,3737,3741],{"type":127,"tag":259,"props":3734,"children":3735},{"style":266},[3736],{"type":132,"value":3687},{"type":127,"tag":259,"props":3738,"children":3739},{"style":272},[3740],{"type":132,"value":445},{"type":127,"tag":259,"props":3742,"children":3743},{"style":266},[3744],{"type":132,"value":280},{"type":127,"tag":259,"props":3746,"children":3747},{"class":261,"line":426},[3748,3753,3757,3762],{"type":127,"tag":259,"props":3749,"children":3750},{"style":287},[3751],{"type":132,"value":3752},"    position",{"type":127,"tag":259,"props":3754,"children":3755},{"style":266},[3756],{"type":132,"value":295},{"type":127,"tag":259,"props":3758,"children":3759},{"style":322},[3760],{"type":132,"value":3761}," static",{"type":127,"tag":259,"props":3763,"children":3764},{"style":266},[3765],{"type":132,"value":306},{"type":127,"tag":259,"props":3767,"children":3768},{"class":261,"line":435},[3769,3774,3778,3783],{"type":127,"tag":259,"props":3770,"children":3771},{"style":287},[3772],{"type":132,"value":3773},"    flex-direction",{"type":127,"tag":259,"props":3775,"children":3776},{"style":266},[3777],{"type":132,"value":295},{"type":127,"tag":259,"props":3779,"children":3780},{"style":322},[3781],{"type":132,"value":3782}," column",{"type":127,"tag":259,"props":3784,"children":3785},{"style":266},[3786],{"type":132,"value":306},{"type":127,"tag":259,"props":3788,"children":3789},{"class":261,"line":452},[3790,3795,3799,3803],{"type":127,"tag":259,"props":3791,"children":3792},{"style":287},[3793],{"type":132,"value":3794},"    width",{"type":127,"tag":259,"props":3796,"children":3797},{"style":266},[3798],{"type":132,"value":295},{"type":127,"tag":259,"props":3800,"children":3801},{"style":322},[3802],{"type":132,"value":616},{"type":127,"tag":259,"props":3804,"children":3805},{"style":266},[3806],{"type":132,"value":306},{"type":127,"tag":259,"props":3808,"children":3809},{"class":261,"line":474},[3810,3814,3818,3822],{"type":127,"tag":259,"props":3811,"children":3812},{"style":287},[3813],{"type":132,"value":3703},{"type":127,"tag":259,"props":3815,"children":3816},{"style":266},[3817],{"type":132,"value":295},{"type":127,"tag":259,"props":3819,"children":3820},{"style":322},[3821],{"type":132,"value":616},{"type":127,"tag":259,"props":3823,"children":3824},{"style":266},[3825],{"type":132,"value":306},{"type":127,"tag":259,"props":3827,"children":3828},{"class":261,"line":496},[3829],{"type":127,"tag":259,"props":3830,"children":3831},{"style":266},[3832],{"type":132,"value":1497},{"type":127,"tag":259,"props":3834,"children":3835},{"class":261,"line":518},[3836],{"type":127,"tag":259,"props":3837,"children":3838},{"style":266},[3839],{"type":132,"value":423},{"type":127,"tag":128,"props":3841,"children":3842},{},[3843],{"type":132,"value":3844},"In Firefox 156, this turned the test page into four stacked panels with no script errors.",{"type":127,"tag":128,"props":3846,"children":3847},{},[3848,3850,3856],{"type":132,"value":3849},"Some layouts should stack on purpose. The plugin stacks panels on touch screens by default. A media query can reset a CSS animation, but ",{"type":127,"tag":152,"props":3851,"children":3853},{"className":3852},[],[3854],{"type":132,"value":3855},"animation: none",{"type":132,"value":3857}," does nothing to an animation created with WAAPI. Cancelling it from JavaScript means JavaScript has to know about breakpoints. The engine avoids that by folding a state variable into the endpoint and zeroing it wherever the panels stack:",{"type":127,"tag":249,"props":3859,"children":3861},{"className":251,"code":3860,"language":17,"meta":253,"style":253},"@keyframes hs-slide {\n  to {\n    transform: translateX(calc((-100% + 100cqw) * var(--move, 1)));\n  }\n}\n\n@media (hover: none) and (pointer: coarse) {\n  .hs {\n    --move: 0;\n    height: auto;\n  }\n\n  .hs__track {\n    position: static;\n    flex-direction: column;\n    width: auto;\n    height: auto;\n  }\n}\n",[3862],{"type":127,"tag":152,"props":3863,"children":3864},{"__ignoreMap":253},[3865,3880,3891,3976,3983,3990,3997,4058,4073,4093,4112,4119,4126,4141,4160,4179,4198,4217,4224],{"type":127,"tag":259,"props":3866,"children":3867},{"class":261,"line":262},[3868,3872,3876],{"type":127,"tag":259,"props":3869,"children":3870},{"style":1417},[3871],{"type":132,"value":1420},{"type":127,"tag":259,"props":3873,"children":3874},{"style":1423},[3875],{"type":132,"value":1426},{"type":127,"tag":259,"props":3877,"children":3878},{"style":266},[3879],{"type":132,"value":280},{"type":127,"tag":259,"props":3881,"children":3882},{"class":261,"line":283},[3883,3887],{"type":127,"tag":259,"props":3884,"children":3885},{"style":272},[3886],{"type":132,"value":1438},{"type":127,"tag":259,"props":3888,"children":3889},{"style":266},[3890],{"type":132,"value":280},{"type":127,"tag":259,"props":3892,"children":3893},{"class":261,"line":309},[3894,3898,3902,3906,3910,3914,3918,3922,3926,3930,3934,3938,3943,3947,3951,3956,3960,3964,3968,3972],{"type":127,"tag":259,"props":3895,"children":3896},{"style":287},[3897],{"type":132,"value":1450},{"type":127,"tag":259,"props":3899,"children":3900},{"style":266},[3901],{"type":132,"value":295},{"type":127,"tag":259,"props":3903,"children":3904},{"style":367},[3905],{"type":132,"value":1459},{"type":127,"tag":259,"props":3907,"children":3908},{"style":266},[3909],{"type":132,"value":375},{"type":127,"tag":259,"props":3911,"children":3912},{"style":367},[3913],{"type":132,"value":1468},{"type":127,"tag":259,"props":3915,"children":3916},{"style":266},[3917],{"type":132,"value":375},{"type":127,"tag":259,"props":3919,"children":3920},{"style":322},[3921],{"type":132,"value":375},{"type":127,"tag":259,"props":3923,"children":3924},{"style":298},[3925],{"type":132,"value":1477},{"type":127,"tag":259,"props":3927,"children":3928},{"style":266},[3929],{"type":132,"value":385},{"type":127,"tag":259,"props":3931,"children":3932},{"style":298},[3933],{"type":132,"value":637},{"type":127,"tag":259,"props":3935,"children":3936},{"style":266},[3937],{"type":132,"value":774},{"type":127,"tag":259,"props":3939,"children":3940},{"style":322},[3941],{"type":132,"value":3942}," * ",{"type":127,"tag":259,"props":3944,"children":3945},{"style":367},[3946],{"type":132,"value":2900},{"type":127,"tag":259,"props":3948,"children":3949},{"style":266},[3950],{"type":132,"value":375},{"type":127,"tag":259,"props":3952,"children":3953},{"style":322},[3954],{"type":132,"value":3955},"--move",{"type":127,"tag":259,"props":3957,"children":3958},{"style":266},[3959],{"type":132,"value":404},{"type":127,"tag":259,"props":3961,"children":3962},{"style":298},[3963],{"type":132,"value":2580},{"type":127,"tag":259,"props":3965,"children":3966},{"style":266},[3967],{"type":132,"value":2348},{"type":127,"tag":259,"props":3969,"children":3970},{"style":322},[3971],{"type":132,"value":774},{"type":127,"tag":259,"props":3973,"children":3974},{"style":266},[3975],{"type":132,"value":306},{"type":127,"tag":259,"props":3977,"children":3978},{"class":261,"line":332},[3979],{"type":127,"tag":259,"props":3980,"children":3981},{"style":266},[3982],{"type":132,"value":1497},{"type":127,"tag":259,"props":3984,"children":3985},{"class":261,"line":354},[3986],{"type":127,"tag":259,"props":3987,"children":3988},{"style":266},[3989],{"type":132,"value":423},{"type":127,"tag":259,"props":3991,"children":3992},{"class":261,"line":417},[3993],{"type":127,"tag":259,"props":3994,"children":3995},{"emptyLinePlaceholder":84},[3996],{"type":132,"value":432},{"type":127,"tag":259,"props":3998,"children":3999},{"class":261,"line":426},[4000,4005,4009,4014,4018,4023,4027,4032,4036,4041,4045,4050,4054],{"type":127,"tag":259,"props":4001,"children":4002},{"style":1417},[4003],{"type":132,"value":4004},"@media",{"type":127,"tag":259,"props":4006,"children":4007},{"style":266},[4008],{"type":132,"value":2275},{"type":127,"tag":259,"props":4010,"children":4011},{"style":272},[4012],{"type":132,"value":4013},"hover",{"type":127,"tag":259,"props":4015,"children":4016},{"style":266},[4017],{"type":132,"value":295},{"type":127,"tag":259,"props":4019,"children":4020},{"style":322},[4021],{"type":132,"value":4022}," none",{"type":127,"tag":259,"props":4024,"children":4025},{"style":266},[4026],{"type":132,"value":774},{"type":127,"tag":259,"props":4028,"children":4029},{"style":266},[4030],{"type":132,"value":4031}," and",{"type":127,"tag":259,"props":4033,"children":4034},{"style":266},[4035],{"type":132,"value":2275},{"type":127,"tag":259,"props":4037,"children":4038},{"style":322},[4039],{"type":132,"value":4040},"pointer",{"type":127,"tag":259,"props":4042,"children":4043},{"style":266},[4044],{"type":132,"value":295},{"type":127,"tag":259,"props":4046,"children":4047},{"style":322},[4048],{"type":132,"value":4049}," coarse",{"type":127,"tag":259,"props":4051,"children":4052},{"style":266},[4053],{"type":132,"value":774},{"type":127,"tag":259,"props":4055,"children":4056},{"style":266},[4057],{"type":132,"value":280},{"type":127,"tag":259,"props":4059,"children":4060},{"class":261,"line":435},[4061,4065,4069],{"type":127,"tag":259,"props":4062,"children":4063},{"style":266},[4064],{"type":132,"value":3687},{"type":127,"tag":259,"props":4066,"children":4067},{"style":272},[4068],{"type":132,"value":275},{"type":127,"tag":259,"props":4070,"children":4071},{"style":266},[4072],{"type":132,"value":280},{"type":127,"tag":259,"props":4074,"children":4075},{"class":261,"line":452},[4076,4081,4085,4089],{"type":127,"tag":259,"props":4077,"children":4078},{"style":322},[4079],{"type":132,"value":4080},"    --move",{"type":127,"tag":259,"props":4082,"children":4083},{"style":266},[4084],{"type":132,"value":295},{"type":127,"tag":259,"props":4086,"children":4087},{"style":298},[4088],{"type":132,"value":489},{"type":127,"tag":259,"props":4090,"children":4091},{"style":266},[4092],{"type":132,"value":306},{"type":127,"tag":259,"props":4094,"children":4095},{"class":261,"line":474},[4096,4100,4104,4108],{"type":127,"tag":259,"props":4097,"children":4098},{"style":287},[4099],{"type":132,"value":3703},{"type":127,"tag":259,"props":4101,"children":4102},{"style":266},[4103],{"type":132,"value":295},{"type":127,"tag":259,"props":4105,"children":4106},{"style":322},[4107],{"type":132,"value":616},{"type":127,"tag":259,"props":4109,"children":4110},{"style":266},[4111],{"type":132,"value":306},{"type":127,"tag":259,"props":4113,"children":4114},{"class":261,"line":496},[4115],{"type":127,"tag":259,"props":4116,"children":4117},{"style":266},[4118],{"type":132,"value":1497},{"type":127,"tag":259,"props":4120,"children":4121},{"class":261,"line":518},[4122],{"type":127,"tag":259,"props":4123,"children":4124},{"emptyLinePlaceholder":84},[4125],{"type":132,"value":432},{"type":127,"tag":259,"props":4127,"children":4128},{"class":261,"line":539},[4129,4133,4137],{"type":127,"tag":259,"props":4130,"children":4131},{"style":266},[4132],{"type":132,"value":3687},{"type":127,"tag":259,"props":4134,"children":4135},{"style":272},[4136],{"type":132,"value":445},{"type":127,"tag":259,"props":4138,"children":4139},{"style":266},[4140],{"type":132,"value":280},{"type":127,"tag":259,"props":4142,"children":4143},{"class":261,"line":560},[4144,4148,4152,4156],{"type":127,"tag":259,"props":4145,"children":4146},{"style":287},[4147],{"type":132,"value":3752},{"type":127,"tag":259,"props":4149,"children":4150},{"style":266},[4151],{"type":132,"value":295},{"type":127,"tag":259,"props":4153,"children":4154},{"style":322},[4155],{"type":132,"value":3761},{"type":127,"tag":259,"props":4157,"children":4158},{"style":266},[4159],{"type":132,"value":306},{"type":127,"tag":259,"props":4161,"children":4162},{"class":261,"line":568},[4163,4167,4171,4175],{"type":127,"tag":259,"props":4164,"children":4165},{"style":287},[4166],{"type":132,"value":3773},{"type":127,"tag":259,"props":4168,"children":4169},{"style":266},[4170],{"type":132,"value":295},{"type":127,"tag":259,"props":4172,"children":4173},{"style":322},[4174],{"type":132,"value":3782},{"type":127,"tag":259,"props":4176,"children":4177},{"style":266},[4178],{"type":132,"value":306},{"type":127,"tag":259,"props":4180,"children":4181},{"class":261,"line":576},[4182,4186,4190,4194],{"type":127,"tag":259,"props":4183,"children":4184},{"style":287},[4185],{"type":132,"value":3794},{"type":127,"tag":259,"props":4187,"children":4188},{"style":266},[4189],{"type":132,"value":295},{"type":127,"tag":259,"props":4191,"children":4192},{"style":322},[4193],{"type":132,"value":616},{"type":127,"tag":259,"props":4195,"children":4196},{"style":266},[4197],{"type":132,"value":306},{"type":127,"tag":259,"props":4199,"children":4200},{"class":261,"line":593},[4201,4205,4209,4213],{"type":127,"tag":259,"props":4202,"children":4203},{"style":287},[4204],{"type":132,"value":3703},{"type":127,"tag":259,"props":4206,"children":4207},{"style":266},[4208],{"type":132,"value":295},{"type":127,"tag":259,"props":4210,"children":4211},{"style":322},[4212],{"type":132,"value":616},{"type":127,"tag":259,"props":4214,"children":4215},{"style":266},[4216],{"type":132,"value":306},{"type":127,"tag":259,"props":4218,"children":4219},{"class":261,"line":623},[4220],{"type":127,"tag":259,"props":4221,"children":4222},{"style":266},[4223],{"type":132,"value":1497},{"type":127,"tag":259,"props":4225,"children":4226},{"class":261,"line":644},[4227],{"type":127,"tag":259,"props":4228,"children":4229},{"style":266},[4230],{"type":132,"value":423},{"type":127,"tag":128,"props":4232,"children":4233},{},[4234,4236,4242,4244,4250],{"type":132,"value":4235},"Any animation built from that endpoint, CSS or WAAPI, now resolves to ",{"type":127,"tag":152,"props":4237,"children":4239},{"className":4238},[],[4240],{"type":132,"value":4241},"translateX(0)",{"type":132,"value":4243}," in that state. The trace showed Chrome still composites it with the ",{"type":127,"tag":152,"props":4245,"children":4247},{"className":4246},[],[4248],{"type":132,"value":4249},"var()",{"type":132,"value":4251}," inside.",{"type":127,"tag":128,"props":4253,"children":4254},{},[4255,4257,4263,4265,4270],{"type":132,"value":4256},"Firefox and Safari before 26 can get the horizontal layout through a polyfill. The plugin lazy-loads a patched scroll-timeline polyfill (about 19 KB gzipped) when a ",{"type":127,"tag":152,"props":4258,"children":4260},{"className":4259},[],[4261],{"type":132,"value":4262},"CSS.supports()",{"type":132,"value":4264}," check fails. The stock copy aborts its whole setup when one stylesheet trips its parser, and Elementor's inline CSS does exactly that. Its CSS layer also maps ",{"type":127,"tag":152,"props":4266,"children":4268},{"className":4267},[],[4269],{"type":132,"value":1518},{"type":132,"value":4271}," wrongly on subjects taller than the viewport, which is every pinned section. So the engine builds the polyfilled track through WAAPI with the same endpoint, and stays vertical if loading fails. That path runs on the main thread. It keeps Firefox working. It does not make Firefox fast.",{"type":127,"tag":140,"props":4273,"children":4275},{"id":4274},"how-to-build-it-step-by-step",[4276],{"type":132,"value":4277},"How to build it, step by step",{"type":127,"tag":128,"props":4279,"children":4280},{},[4281],{"type":132,"value":4282},"Here is the whole build in order. Steps 1 to 5 produce the effect in Chrome, Edge and Safari 26 or later. Step 6 covers every other browser, and step 7 confirms the result. Each step matches a section above if you need the reasoning.",{"type":127,"tag":4284,"props":4285,"children":4286},"ol",{},[4287,4305,4348,4372,4403,4441,4473],{"type":127,"tag":1563,"props":4288,"children":4289},{},[4290,4296,4298,4303],{"type":127,"tag":4291,"props":4292,"children":4293},"strong",{},[4294],{"type":132,"value":4295},"Step 1: Structure the section.",{"type":132,"value":4297}," Nest ",{"type":127,"tag":152,"props":4299,"children":4301},{"className":4300},[],[4302],{"type":132,"value":245},{"type":132,"value":4304},". The section provides the scroll distance, and the track carries the panels sideways.",{"type":127,"tag":1563,"props":4306,"children":4307},{},[4308,4313,4315,4320,4321,4326,4328,4333,4334,4339,4341,4346],{"type":127,"tag":4291,"props":4309,"children":4310},{},[4311],{"type":132,"value":4312},"Step 2: Pin the track with CSS.",{"type":132,"value":4314}," Make the track ",{"type":127,"tag":152,"props":4316,"children":4318},{"className":4317},[],[4319],{"type":132,"value":232},{"type":132,"value":1608},{"type":127,"tag":152,"props":4322,"children":4324},{"className":4323},[],[4325],{"type":132,"value":380},{"type":132,"value":4327}," tall. Give the section ",{"type":127,"tag":152,"props":4329,"children":4331},{"className":4330},[],[4332],{"type":132,"value":677},{"type":132,"value":1608},{"type":127,"tag":152,"props":4335,"children":4337},{"className":4336},[],[4338],{"type":132,"value":658},{"type":132,"value":4340},", because ",{"type":127,"tag":152,"props":4342,"children":4344},{"className":4343},[],[4345],{"type":132,"value":685},{"type":132,"value":4347}," on any ancestor breaks the pin.",{"type":127,"tag":1563,"props":4349,"children":4350},{},[4351,4356,4358,4364,4365,4370],{"type":127,"tag":4291,"props":4352,"children":4353},{},[4354],{"type":132,"value":4355},"Step 3: Measure the travel on resize.",{"type":132,"value":4357}," One frame after each ResizeObserver callback, write ",{"type":127,"tag":152,"props":4359,"children":4361},{"className":4360},[],[4362],{"type":132,"value":4363},"track.scrollWidth - section.clientWidth",{"type":132,"value":3538},{"type":127,"tag":152,"props":4366,"children":4368},{"className":4367},[],[4369],{"type":132,"value":399},{"type":132,"value":4371}," and add it to the section's height. Waiting that frame avoids the ResizeObserver loop error.",{"type":127,"tag":1563,"props":4373,"children":4374},{},[4375,4380,4382,4388,4389,4394,4396,4401],{"type":127,"tag":4291,"props":4376,"children":4377},{},[4378],{"type":132,"value":4379},"Step 4: Name a view timeline on the section.",{"type":132,"value":4381}," Set ",{"type":127,"tag":152,"props":4383,"children":4385},{"className":4384},[],[4386],{"type":132,"value":4387},"view-timeline: --hs block",{"type":132,"value":1608},{"type":127,"tag":152,"props":4390,"children":4392},{"className":4391},[],[4393],{"type":132,"value":1627},{"type":132,"value":4395}," on the section, not the track. The pinned track never moves relative to the viewport, so a timeline on it would stall. The explicit inset stops ",{"type":127,"tag":152,"props":4397,"children":4399},{"className":4398},[],[4400],{"type":132,"value":1649},{"type":132,"value":4402}," from shifting the range.",{"type":127,"tag":1563,"props":4404,"children":4405},{},[4406,4411,4413,4418,4420,4426,4427,4432,4433,4439],{"type":127,"tag":4291,"props":4407,"children":4408},{},[4409],{"type":132,"value":4410},"Step 5: Animate the track across the contain range.",{"type":132,"value":4412}," Attach a ",{"type":127,"tag":152,"props":4414,"children":4416},{"className":4415},[],[4417],{"type":132,"value":1571},{"type":132,"value":4419}," animation ending at ",{"type":127,"tag":152,"props":4421,"children":4423},{"className":4422},[],[4424],{"type":132,"value":4425},"translateX(calc(-100% + 100cqw))",{"type":132,"value":3538},{"type":127,"tag":152,"props":4428,"children":4430},{"className":4429},[],[4431],{"type":132,"value":1750},{"type":132,"value":3553},{"type":127,"tag":152,"props":4434,"children":4436},{"className":4435},[],[4437],{"type":132,"value":4438},"animation-range: contain 0% contain 100%",{"type":132,"value":4440},". The relative endpoint stays correct after every resize, so the animation is never rebuilt.",{"type":127,"tag":1563,"props":4442,"children":4443},{},[4444,4449,4451,4457,4459,4465,4467,4472],{"type":127,"tag":4291,"props":4445,"children":4446},{},[4447],{"type":132,"value":4448},"Step 6: Stack the panels where the timeline cannot run.",{"type":132,"value":4450}," Reset the layout under ",{"type":127,"tag":152,"props":4452,"children":4454},{"className":4453},[],[4455],{"type":132,"value":4456},"@supports not",{"type":132,"value":4458}," and in stacking media queries. Set ",{"type":127,"tag":152,"props":4460,"children":4462},{"className":4461},[],[4463],{"type":132,"value":4464},"--move: 0",{"type":132,"value":4466}," there too, because WAAPI animations ignore ",{"type":127,"tag":152,"props":4468,"children":4470},{"className":4469},[],[4471],{"type":132,"value":3855},{"type":132,"value":269},{"type":127,"tag":1563,"props":4474,"children":4475},{},[4476,4481],{"type":127,"tag":4291,"props":4477,"children":4478},{},[4479],{"type":132,"value":4480},"Step 7: Verify compositing.",{"type":132,"value":4482}," Profile a scroll in DevTools and check the Animations track for red triangles. A red triangle names the property that kept an animation on the main thread.",{"type":127,"tag":140,"props":4484,"children":4486},{"id":4485},"key-takeaways",[4487],{"type":132,"value":4488},"Key takeaways",{"type":127,"tag":1559,"props":4490,"children":4491},{},[4492,4518,4523,4548,4560],{"type":127,"tag":1563,"props":4493,"children":4494},{},[4495,4497,4502,4504,4509,4511,4517],{"type":132,"value":4496},"Pin with ",{"type":127,"tag":152,"props":4498,"children":4500},{"className":4499},[],[4501],{"type":132,"value":232},{"type":132,"value":4503},", and use ",{"type":127,"tag":152,"props":4505,"children":4507},{"className":4506},[],[4508],{"type":132,"value":677},{"type":132,"value":4510}," instead of ",{"type":127,"tag":152,"props":4512,"children":4514},{"className":4513},[],[4515],{"type":132,"value":4516},"hidden",{"type":132,"value":269},{"type":127,"tag":1563,"props":4519,"children":4520},{},[4521],{"type":132,"value":4522},"Measure the travel one frame after a resize, never on scroll.",{"type":127,"tag":1563,"props":4524,"children":4525},{},[4526,4528,4533,4534,4539,4541,4546],{"type":132,"value":4527},"Put the view timeline on the section and animate the track from ",{"type":127,"tag":152,"props":4529,"children":4531},{"className":4530},[],[4532],{"type":132,"value":1535},{"type":132,"value":3538},{"type":127,"tag":152,"props":4535,"children":4537},{"className":4536},[],[4538],{"type":132,"value":1543},{"type":132,"value":4540},", with ",{"type":127,"tag":152,"props":4542,"children":4544},{"className":4543},[],[4545],{"type":132,"value":1571},{"type":132,"value":4547}," easing.",{"type":127,"tag":1563,"props":4549,"children":4550},{},[4551,4553,4558],{"type":132,"value":4552},"Animate ",{"type":127,"tag":152,"props":4554,"children":4556},{"className":4555},[],[4557],{"type":132,"value":157},{"type":132,"value":4559},", then check DevTools for red triangles.",{"type":127,"tag":1563,"props":4561,"children":4562},{},[4563],{"type":132,"value":4564},"Stack the panels wherever the timeline cannot run.",{"type":127,"tag":964,"props":4566,"children":4567},{},[4568],{"type":132,"value":4569},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}"]