[{"data":1,"prerenderedAt":1327},["ShallowReactive",2],{"blog-\u002Fblog\u002Fgsap-scrolltrigger-animated-landing":3},{"id":4,"title":5,"body":6,"date":1314,"description":1315,"extension":1316,"icon":1317,"meta":1318,"navigation":180,"path":1319,"readingTime":184,"seo":1320,"stem":1321,"tags":1322,"__hash__":1326},"content\u002Fblog\u002Fgsap-scrolltrigger-animated-landing.md","Animated landing pages with GSAP and ScrollTrigger — lessons from a motion-first build",{"type":7,"value":8,"toc":1307},"minimark",[9,19,22,27,30,33,41,44,48,51,58,849,856,863,867,870,873,1272,1279,1282,1286,1289,1292,1296,1303],[10,11,12,13,18],"p",{},"I recently completed the frontend interface for ",[14,15,17],"a",{"href":16},"\u002Fprojects\u002Fnoir-studio","Noir Studio",", a high-end agency landing page that relies heavily on cinematic transitions and dense imagery. The creative brief called for a site that felt entirely continuous — sections morphing into each other, large layout grids sliding horizontally on vertical scroll, and micro-interactions reacting to the user's cursor instantly. Building this kind of fluid experience in a React-centric world without introducing heavy frame drops requires dropping standard declarative state habits.",[10,20,21],{},"If you try to drive complex scroll positions by hooking up native event listeners to React state variables, the virtual DOM diffing cycle will quickly bottleneck your main thread. For smooth motion design, you have to bypass the React reconciler entirely during the animation loop and speak to the DOM directly.",[23,24,26],"h2",{"id":25},"why-gsap-beats-framer-motion-for-heavy-scroll-orchestration","Why GSAP beats Framer Motion for heavy scroll orchestration",[10,28,29],{},"Framer Motion is an exceptional library for standard application interfaces. If you are animating layout changes, tracking unmount lifecycles via AnimatePresence, or implementing basic spring transitions on modal windows, it is the neatest tool available.",[10,31,32],{},"However, the moment you transition from basic element fades to deeply linked scroll timelines — where the progress of a dozen distinct properties is clamped explicitly to the user's physical trackpad position — Framer Motion begins to struggle under the weight of its own declarative abstraction.",[10,34,35,36,40],{},"GSAP (GreenSock Animation Platform) wins on high-end production landing pages for a very specific architectural reason: it updates the underlying inline styles of elements directly via a single, highly optimised internal ticking ",[37,38,39],"code",{},"requestAnimationFrame"," loop. It completely skips React's rendering pipeline.",[10,42,43],{},"When a GSAP tween updates a transform matrix, React remains blissfully unaware. This direct-to-DOM execution prevents layout thrashing and ensures that even mobile browsers can maintain a flat 60fps execution path when dealing with complex multi-element staggers.",[23,45,47],{"id":46},"implementing-the-pinned-horizontal-scroll-container","Implementing the pinned horizontal scroll container",[10,49,50],{},"The cornerstone layout pattern of the Noir Studio showcase is the pinned horizontal section. The user enters a vertical scroll section, the viewport locks solidly in place, and as they continue to scroll downwards, the content shifts laterally across the screen to reveal a linear project showcase.",[10,52,53,54,57],{},"To implement this reliably inside Next.js without causing accidental layout shifting or broken entry thresholds during page hydration, you must combine the ",[37,55,56],{},"@gsap\u002Freact"," hook utility with an explicit, un-eased timeline structure.",[59,60,65],"pre",{"className":61,"code":62,"language":63,"meta":64,"style":64},"language-typescript shiki shiki-themes tokyo-night","\u002F\u002F The definitive pinned horizontal scroll pattern in Next.js\nimport { useRef } from 'react';\nimport gsap from 'gsap';\nimport { useGSAP } from '@gsap\u002Freact';\nimport { ScrollTrigger } from 'gsap\u002FScrollTrigger';\n\ngsap.registerPlugin(ScrollTrigger);\n\nexport function HorizontalShowcase({ items }: { items: Project[] }) {\n  const containerRef = useRef\u003CHTMLDivElement>(null);\n  const scrollRef = useRef\u003CHTMLDivElement>(null);\n\n  useGSAP(() => {\n    const scrollWidth = scrollRef.current?.scrollWidth || 0;\n    const viewportWidth = window.innerWidth;\n    const pinDistance = scrollWidth - viewportWidth;\n\n    if (pinDistance \u003C= 0) return;\n\n    gsap.to(scrollRef.current, {\n      x: -pinDistance,\n      ease: 'none', \u002F\u002F Critical: any easing here will desync the scroll tracking\n      scrollTrigger: {\n        trigger: containerRef.current,\n        pin: true,\n        scrub: 1, \u002F\u002F Smoothly catches up with the scroll movement\n        start: 'top top',\n        end: () => `+=${pinDistance}`,\n        invalidateOnRefresh: true, \u002F\u002F Recalculates dynamically if the user resizes the browser\n      }\n    });\n  }, { scope: containerRef });\n\n  return (\n    \u003Cdiv ref={containerRef} className=\"w-full overflow-hidden bg-black\">\n      \u003Cdiv ref={scrollRef} className=\"flex h-screen w-max items-center px-10 gap-8\">\n        {items.map(item => (\n          \u003Csection key={item.id} className=\"w-[80vw] h-[70vh] flex-shrink-0 bg-neutral-900\" \u002F>\n        ))}\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  );\n}\n","typescript","",[37,66,67,76,111,130,152,175,182,205,210,256,291,317,322,336,368,388,407,412,438,443,468,483,503,513,529,542,558,575,607,622,628,636,657,662,671,710,741,766,804,813,825,836,844],{"__ignoreMap":64},[68,69,72],"span",{"class":70,"line":71},"line",1,[68,73,75],{"class":74},"sbD-w","\u002F\u002F The definitive pinned horizontal scroll pattern in Next.js\n",[68,77,79,83,87,91,94,97,101,105,108],{"class":70,"line":78},2,[68,80,82],{"class":81},"su0L4","import",[68,84,86],{"class":85},"sgJMe"," { ",[68,88,90],{"class":89},"sySf4","useRef",[68,92,93],{"class":85}," }",[68,95,96],{"class":81}," from",[68,98,100],{"class":99},"sAklC"," '",[68,102,104],{"class":103},"sPY7s","react",[68,106,107],{"class":99},"'",[68,109,110],{"class":99},";\n",[68,112,114,116,119,121,123,126,128],{"class":70,"line":113},3,[68,115,82],{"class":81},[68,117,118],{"class":89}," gsap",[68,120,96],{"class":81},[68,122,100],{"class":99},[68,124,125],{"class":103},"gsap",[68,127,107],{"class":99},[68,129,110],{"class":99},[68,131,133,135,137,140,142,144,146,148,150],{"class":70,"line":132},4,[68,134,82],{"class":81},[68,136,86],{"class":85},[68,138,139],{"class":89},"useGSAP",[68,141,93],{"class":85},[68,143,96],{"class":81},[68,145,100],{"class":99},[68,147,56],{"class":103},[68,149,107],{"class":99},[68,151,110],{"class":99},[68,153,155,157,159,162,164,166,168,171,173],{"class":70,"line":154},5,[68,156,82],{"class":81},[68,158,86],{"class":85},[68,160,161],{"class":89},"ScrollTrigger",[68,163,93],{"class":85},[68,165,96],{"class":81},[68,167,100],{"class":99},[68,169,170],{"class":103},"gsap\u002FScrollTrigger",[68,172,107],{"class":99},[68,174,110],{"class":99},[68,176,178],{"class":70,"line":177},6,[68,179,181],{"emptyLinePlaceholder":180},true,"\n",[68,183,185,188,191,195,198,200,203],{"class":70,"line":184},7,[68,186,125],{"class":187},"sE3pS",[68,189,190],{"class":99},".",[68,192,194],{"class":193},"s3R4Z","registerPlugin",[68,196,197],{"class":85},"(",[68,199,161],{"class":187},[68,201,202],{"class":85},")",[68,204,110],{"class":99},[68,206,208],{"class":70,"line":207},8,[68,209,181],{"emptyLinePlaceholder":180},[68,211,213,216,220,223,225,228,232,235,238,241,244,247,250,253],{"class":70,"line":212},9,[68,214,215],{"class":81},"export",[68,217,219],{"class":218},"sd1Qi"," function",[68,221,222],{"class":193}," HorizontalShowcase",[68,224,197],{"class":85},[68,226,227],{"class":99},"{",[68,229,231],{"class":230},"sT800"," items",[68,233,234],{"class":99}," }:",[68,236,237],{"class":85}," {",[68,239,231],{"class":240},"syYvs",[68,242,243],{"class":99},":",[68,245,246],{"class":187}," Project",[68,248,249],{"class":85},"[]",[68,251,252],{"class":85}," })",[68,254,255],{"class":85}," {\n",[68,257,259,263,266,269,272,275,278,281,283,287,289],{"class":70,"line":258},10,[68,260,262],{"class":261},"sN7LL","  const",[68,264,265],{"class":218}," containerRef",[68,267,268],{"class":99}," =",[68,270,271],{"class":193}," useRef",[68,273,274],{"class":99},"\u003C",[68,276,277],{"class":187},"HTMLDivElement",[68,279,280],{"class":99},">",[68,282,197],{"class":85},[68,284,286],{"class":285},"sOJ5S","null",[68,288,202],{"class":85},[68,290,110],{"class":99},[68,292,294,296,299,301,303,305,307,309,311,313,315],{"class":70,"line":293},11,[68,295,262],{"class":261},[68,297,298],{"class":218}," scrollRef",[68,300,268],{"class":99},[68,302,271],{"class":193},[68,304,274],{"class":99},[68,306,277],{"class":187},[68,308,280],{"class":99},[68,310,197],{"class":85},[68,312,286],{"class":285},[68,314,202],{"class":85},[68,316,110],{"class":99},[68,318,320],{"class":70,"line":319},12,[68,321,181],{"emptyLinePlaceholder":180},[68,323,325,328,331,334],{"class":70,"line":324},13,[68,326,327],{"class":193},"  useGSAP",[68,329,330],{"class":85},"(() ",[68,332,333],{"class":218},"=>",[68,335,255],{"class":85},[68,337,339,342,345,347,349,351,354,357,360,363,366],{"class":70,"line":338},14,[68,340,341],{"class":261},"    const",[68,343,344],{"class":218}," scrollWidth",[68,346,268],{"class":99},[68,348,298],{"class":187},[68,350,190],{"class":99},[68,352,353],{"class":187},"current",[68,355,356],{"class":99},"?.",[68,358,359],{"class":81},"scrollWidth",[68,361,362],{"class":218}," ||",[68,364,365],{"class":285}," 0",[68,367,110],{"class":99},[68,369,371,373,376,378,381,383,386],{"class":70,"line":370},15,[68,372,341],{"class":261},[68,374,375],{"class":218}," viewportWidth",[68,377,268],{"class":99},[68,379,380],{"class":187}," window",[68,382,190],{"class":99},[68,384,385],{"class":81},"innerWidth",[68,387,110],{"class":99},[68,389,391,393,396,398,400,403,405],{"class":70,"line":390},16,[68,392,341],{"class":261},[68,394,395],{"class":218}," pinDistance",[68,397,268],{"class":99},[68,399,344],{"class":187},[68,401,402],{"class":99}," -",[68,404,375],{"class":187},[68,406,110],{"class":99},[68,408,410],{"class":70,"line":409},17,[68,411,181],{"emptyLinePlaceholder":180},[68,413,415,418,421,424,427,429,432,436],{"class":70,"line":414},18,[68,416,417],{"class":218},"    if",[68,419,420],{"class":85}," (",[68,422,423],{"class":187},"pinDistance",[68,425,426],{"class":218}," \u003C=",[68,428,365],{"class":285},[68,430,431],{"class":85},") ",[68,433,435],{"class":434},"sEsAJ","return",[68,437,110],{"class":99},[68,439,441],{"class":70,"line":440},19,[68,442,181],{"emptyLinePlaceholder":180},[68,444,446,449,451,454,456,459,461,463,466],{"class":70,"line":445},20,[68,447,448],{"class":187},"    gsap",[68,450,190],{"class":99},[68,452,453],{"class":193},"to",[68,455,197],{"class":85},[68,457,458],{"class":187},"scrollRef",[68,460,190],{"class":99},[68,462,353],{"class":81},[68,464,465],{"class":99},",",[68,467,255],{"class":85},[68,469,471,474,476,478,480],{"class":70,"line":470},21,[68,472,473],{"class":240},"      x",[68,475,243],{"class":99},[68,477,402],{"class":99},[68,479,423],{"class":187},[68,481,482],{"class":99},",\n",[68,484,486,489,491,493,496,498,500],{"class":70,"line":485},22,[68,487,488],{"class":240},"      ease",[68,490,243],{"class":99},[68,492,100],{"class":99},[68,494,495],{"class":103},"none",[68,497,107],{"class":99},[68,499,465],{"class":99},[68,501,502],{"class":74}," \u002F\u002F Critical: any easing here will desync the scroll tracking\n",[68,504,506,509,511],{"class":70,"line":505},23,[68,507,508],{"class":240},"      scrollTrigger",[68,510,243],{"class":99},[68,512,255],{"class":85},[68,514,516,519,521,523,525,527],{"class":70,"line":515},24,[68,517,518],{"class":240},"        trigger",[68,520,243],{"class":99},[68,522,265],{"class":187},[68,524,190],{"class":99},[68,526,353],{"class":81},[68,528,482],{"class":99},[68,530,532,535,537,540],{"class":70,"line":531},25,[68,533,534],{"class":240},"        pin",[68,536,243],{"class":99},[68,538,539],{"class":285}," true",[68,541,482],{"class":99},[68,543,545,548,550,553,555],{"class":70,"line":544},26,[68,546,547],{"class":240},"        scrub",[68,549,243],{"class":99},[68,551,552],{"class":285}," 1",[68,554,465],{"class":99},[68,556,557],{"class":74}," \u002F\u002F Smoothly catches up with the scroll movement\n",[68,559,561,564,566,568,571,573],{"class":70,"line":560},27,[68,562,563],{"class":240},"        start",[68,565,243],{"class":99},[68,567,100],{"class":99},[68,569,570],{"class":103},"top top",[68,572,107],{"class":99},[68,574,482],{"class":99},[68,576,578,581,583,586,588,591,594,597,599,602,605],{"class":70,"line":577},28,[68,579,580],{"class":193},"        end",[68,582,243],{"class":99},[68,584,585],{"class":85}," () ",[68,587,333],{"class":218},[68,589,590],{"class":99}," `",[68,592,593],{"class":103},"+=",[68,595,596],{"class":81},"${",[68,598,423],{"class":187},[68,600,601],{"class":81},"}",[68,603,604],{"class":99},"`",[68,606,482],{"class":99},[68,608,610,613,615,617,619],{"class":70,"line":609},29,[68,611,612],{"class":240},"        invalidateOnRefresh",[68,614,243],{"class":99},[68,616,539],{"class":285},[68,618,465],{"class":99},[68,620,621],{"class":74}," \u002F\u002F Recalculates dynamically if the user resizes the browser\n",[68,623,625],{"class":70,"line":624},30,[68,626,627],{"class":85},"      }\n",[68,629,631,634],{"class":70,"line":630},31,[68,632,633],{"class":85},"    })",[68,635,110],{"class":99},[68,637,639,642,644,646,649,651,653,655],{"class":70,"line":638},32,[68,640,641],{"class":85},"  }",[68,643,465],{"class":99},[68,645,86],{"class":85},[68,647,648],{"class":240},"scope",[68,650,243],{"class":99},[68,652,265],{"class":187},[68,654,252],{"class":85},[68,656,110],{"class":99},[68,658,660],{"class":70,"line":659},33,[68,661,181],{"emptyLinePlaceholder":180},[68,663,665,668],{"class":70,"line":664},34,[68,666,667],{"class":434},"  return",[68,669,670],{"class":85}," (\n",[68,672,674,677,680,683,686,688,691,694,697,699,702,705,707],{"class":70,"line":673},35,[68,675,676],{"class":218},"    \u003C",[68,678,679],{"class":187},"div",[68,681,682],{"class":187}," ref",[68,684,685],{"class":99},"=",[68,687,227],{"class":85},[68,689,690],{"class":187},"containerRef",[68,692,693],{"class":85},"} ",[68,695,696],{"class":187},"className",[68,698,685],{"class":99},[68,700,701],{"class":99},"\"",[68,703,704],{"class":103},"w-full overflow-hidden bg-black",[68,706,701],{"class":99},[68,708,709],{"class":218},">\n",[68,711,713,716,718,720,722,724,726,728,730,732,734,737,739],{"class":70,"line":712},36,[68,714,715],{"class":218},"      \u003C",[68,717,679],{"class":187},[68,719,682],{"class":187},[68,721,685],{"class":99},[68,723,227],{"class":85},[68,725,458],{"class":187},[68,727,693],{"class":85},[68,729,696],{"class":187},[68,731,685],{"class":99},[68,733,701],{"class":99},[68,735,736],{"class":103},"flex h-screen w-max items-center px-10 gap-8",[68,738,701],{"class":99},[68,740,709],{"class":218},[68,742,744,747,750,752,755,757,760,762,764],{"class":70,"line":743},37,[68,745,746],{"class":99},"        {",[68,748,749],{"class":230},"items",[68,751,190],{"class":85},[68,753,754],{"class":230},"map",[68,756,197],{"class":85},[68,758,759],{"class":230},"item",[68,761,268],{"class":99},[68,763,280],{"class":218},[68,765,670],{"class":85},[68,767,769,772,775,778,780,783,786,788,790,792,794,797,799,802],{"class":70,"line":768},38,[68,770,771],{"class":218},"          \u003C",[68,773,774],{"class":187},"section",[68,776,777],{"class":187}," key",[68,779,685],{"class":99},[68,781,782],{"class":85},"{item.",[68,784,785],{"class":187},"id",[68,787,693],{"class":85},[68,789,696],{"class":187},[68,791,685],{"class":99},[68,793,701],{"class":99},[68,795,796],{"class":103},"w-[80vw] h-[70vh] flex-shrink-0 bg-neutral-900",[68,798,701],{"class":99},[68,800,801],{"class":99}," \u002F",[68,803,709],{"class":218},[68,805,807,810],{"class":70,"line":806},39,[68,808,809],{"class":85},"        ))",[68,811,812],{"class":99},"}\n",[68,814,816,818,821,823],{"class":70,"line":815},40,[68,817,715],{"class":218},[68,819,820],{"class":99},"\u002F",[68,822,679],{"class":187},[68,824,709],{"class":218},[68,826,828,830,832,834],{"class":70,"line":827},41,[68,829,676],{"class":218},[68,831,820],{"class":99},[68,833,679],{"class":187},[68,835,709],{"class":218},[68,837,839,842],{"class":70,"line":838},42,[68,840,841],{"class":85},"  )",[68,843,110],{"class":99},[68,845,847],{"class":70,"line":846},43,[68,848,812],{"class":85},[10,850,851,852,855],{},"There are two details in this setup that save hours of debugging. First, the timeline container animation must use ",[37,853,854],{},"ease: 'none'",". If you leave the default power easing profiles turned on, the horizontal movement will accelerate and decelerate strangely in the middle of the trackpad pull instead of matching the user's hand accurately.",[10,857,858,859,862],{},"Second, ",[37,860,861],{},"invalidateOnRefresh: true"," is vital; it forces ScrollTrigger to clear out old pixel calculations and rebuild the scroll boundary limits from scratch the split-second a mobile device shifts its orientation from portrait to landscape.",[23,864,866],{"id":865},"micro-interactions-and-the-dual-element-custom-cursor","Micro-interactions and the dual-element custom cursor",[10,868,869],{},"To complement the large full-screen structural transitions, I integrated an interactive custom cursor that follows the user's mouse position and warps shapes when hovering over call-to-action buttons.",[10,871,872],{},"Instead of tracking the mouse position inside a global mousemove React handler and storing coordinates in local reactive state — which triggers an entire component tree re-render on every single pixel shift — the tracking logic runs entirely inside an isolated, frame-throttled callback loop.",[59,874,876],{"className":61,"code":875,"language":63,"meta":64,"style":64},"\u002F\u002F High-performance custom cursor tracking using quickTo\nexport function CustomCursor() {\n  const dotRef = useRef\u003CHTMLDivElement>(null);\n\n  useGSAP(() => {\n    if (!dotRef.current) return;\n\n    \u002F\u002F quickTo returns an optimised function that updates properties immediately\n    const xTo = gsap.quickTo(dotRef.current, 'x', { duration: 0.2, ease: 'power3' });\n    const yTo = gsap.quickTo(dotRef.current, 'y', { duration: 0.2, ease: 'power3' });\n\n    const handleMouseMove = (e: MouseEvent) => {\n      xTo(e.clientX);\n      yTo(e.clientY);\n    };\n\n    window.addEventListener('mousemove', handleMouseMove);\n    return () => window.removeEventListener('mousemove', handleMouseMove);\n  });\n\n  return \u003Cdiv ref={dotRef} className=\"fixed top-0 left-0 w-4 h-4 rounded-full bg-white pointer-events-none z-50 mix-blend-difference\" \u002F>;\n}\n",[37,877,878,883,897,922,926,936,958,962,967,1030,1088,1092,1117,1135,1153,1160,1164,1191,1223,1230,1234,1268],{"__ignoreMap":64},[68,879,880],{"class":70,"line":71},[68,881,882],{"class":74},"\u002F\u002F High-performance custom cursor tracking using quickTo\n",[68,884,885,887,889,892,895],{"class":70,"line":78},[68,886,215],{"class":81},[68,888,219],{"class":218},[68,890,891],{"class":193}," CustomCursor",[68,893,894],{"class":85},"()",[68,896,255],{"class":85},[68,898,899,901,904,906,908,910,912,914,916,918,920],{"class":70,"line":113},[68,900,262],{"class":261},[68,902,903],{"class":218}," dotRef",[68,905,268],{"class":99},[68,907,271],{"class":193},[68,909,274],{"class":99},[68,911,277],{"class":187},[68,913,280],{"class":99},[68,915,197],{"class":85},[68,917,286],{"class":285},[68,919,202],{"class":85},[68,921,110],{"class":99},[68,923,924],{"class":70,"line":132},[68,925,181],{"emptyLinePlaceholder":180},[68,927,928,930,932,934],{"class":70,"line":154},[68,929,327],{"class":193},[68,931,330],{"class":85},[68,933,333],{"class":218},[68,935,255],{"class":85},[68,937,938,940,942,945,948,950,952,954,956],{"class":70,"line":177},[68,939,417],{"class":218},[68,941,420],{"class":85},[68,943,944],{"class":218},"!",[68,946,947],{"class":187},"dotRef",[68,949,190],{"class":99},[68,951,353],{"class":81},[68,953,431],{"class":85},[68,955,435],{"class":434},[68,957,110],{"class":99},[68,959,960],{"class":70,"line":184},[68,961,181],{"emptyLinePlaceholder":180},[68,963,964],{"class":70,"line":207},[68,965,966],{"class":74},"    \u002F\u002F quickTo returns an optimised function that updates properties immediately\n",[68,968,969,971,974,976,978,980,983,985,987,989,991,993,995,998,1000,1002,1004,1007,1009,1012,1014,1017,1019,1021,1024,1026,1028],{"class":70,"line":212},[68,970,341],{"class":261},[68,972,973],{"class":218}," xTo",[68,975,268],{"class":99},[68,977,118],{"class":187},[68,979,190],{"class":99},[68,981,982],{"class":193},"quickTo",[68,984,197],{"class":85},[68,986,947],{"class":187},[68,988,190],{"class":99},[68,990,353],{"class":81},[68,992,465],{"class":99},[68,994,100],{"class":99},[68,996,997],{"class":103},"x",[68,999,107],{"class":99},[68,1001,465],{"class":99},[68,1003,86],{"class":85},[68,1005,1006],{"class":240},"duration",[68,1008,243],{"class":99},[68,1010,1011],{"class":285}," 0.2",[68,1013,465],{"class":99},[68,1015,1016],{"class":240}," ease",[68,1018,243],{"class":99},[68,1020,100],{"class":99},[68,1022,1023],{"class":103},"power3",[68,1025,107],{"class":99},[68,1027,252],{"class":85},[68,1029,110],{"class":99},[68,1031,1032,1034,1037,1039,1041,1043,1045,1047,1049,1051,1053,1055,1057,1060,1062,1064,1066,1068,1070,1072,1074,1076,1078,1080,1082,1084,1086],{"class":70,"line":258},[68,1033,341],{"class":261},[68,1035,1036],{"class":218}," yTo",[68,1038,268],{"class":99},[68,1040,118],{"class":187},[68,1042,190],{"class":99},[68,1044,982],{"class":193},[68,1046,197],{"class":85},[68,1048,947],{"class":187},[68,1050,190],{"class":99},[68,1052,353],{"class":81},[68,1054,465],{"class":99},[68,1056,100],{"class":99},[68,1058,1059],{"class":103},"y",[68,1061,107],{"class":99},[68,1063,465],{"class":99},[68,1065,86],{"class":85},[68,1067,1006],{"class":240},[68,1069,243],{"class":99},[68,1071,1011],{"class":285},[68,1073,465],{"class":99},[68,1075,1016],{"class":240},[68,1077,243],{"class":99},[68,1079,100],{"class":99},[68,1081,1023],{"class":103},[68,1083,107],{"class":99},[68,1085,252],{"class":85},[68,1087,110],{"class":99},[68,1089,1090],{"class":70,"line":293},[68,1091,181],{"emptyLinePlaceholder":180},[68,1093,1094,1096,1099,1101,1103,1106,1108,1111,1113,1115],{"class":70,"line":319},[68,1095,341],{"class":261},[68,1097,1098],{"class":193}," handleMouseMove",[68,1100,268],{"class":99},[68,1102,420],{"class":85},[68,1104,1105],{"class":230},"e",[68,1107,243],{"class":99},[68,1109,1110],{"class":187}," MouseEvent",[68,1112,431],{"class":85},[68,1114,333],{"class":218},[68,1116,255],{"class":85},[68,1118,1119,1122,1124,1126,1128,1131,1133],{"class":70,"line":324},[68,1120,1121],{"class":193},"      xTo",[68,1123,197],{"class":85},[68,1125,1105],{"class":187},[68,1127,190],{"class":99},[68,1129,1130],{"class":81},"clientX",[68,1132,202],{"class":85},[68,1134,110],{"class":99},[68,1136,1137,1140,1142,1144,1146,1149,1151],{"class":70,"line":338},[68,1138,1139],{"class":193},"      yTo",[68,1141,197],{"class":85},[68,1143,1105],{"class":187},[68,1145,190],{"class":99},[68,1147,1148],{"class":81},"clientY",[68,1150,202],{"class":85},[68,1152,110],{"class":99},[68,1154,1155,1158],{"class":70,"line":370},[68,1156,1157],{"class":85},"    }",[68,1159,110],{"class":99},[68,1161,1162],{"class":70,"line":390},[68,1163,181],{"emptyLinePlaceholder":180},[68,1165,1166,1169,1171,1174,1176,1178,1181,1183,1185,1187,1189],{"class":70,"line":409},[68,1167,1168],{"class":187},"    window",[68,1170,190],{"class":99},[68,1172,1173],{"class":193},"addEventListener",[68,1175,197],{"class":85},[68,1177,107],{"class":99},[68,1179,1180],{"class":103},"mousemove",[68,1182,107],{"class":99},[68,1184,465],{"class":99},[68,1186,1098],{"class":187},[68,1188,202],{"class":85},[68,1190,110],{"class":99},[68,1192,1193,1196,1198,1200,1202,1204,1207,1209,1211,1213,1215,1217,1219,1221],{"class":70,"line":414},[68,1194,1195],{"class":434},"    return",[68,1197,585],{"class":85},[68,1199,333],{"class":218},[68,1201,380],{"class":187},[68,1203,190],{"class":99},[68,1205,1206],{"class":193},"removeEventListener",[68,1208,197],{"class":85},[68,1210,107],{"class":99},[68,1212,1180],{"class":103},[68,1214,107],{"class":99},[68,1216,465],{"class":99},[68,1218,1098],{"class":187},[68,1220,202],{"class":85},[68,1222,110],{"class":99},[68,1224,1225,1228],{"class":70,"line":440},[68,1226,1227],{"class":85},"  })",[68,1229,110],{"class":99},[68,1231,1232],{"class":70,"line":445},[68,1233,181],{"emptyLinePlaceholder":180},[68,1235,1236,1238,1241,1243,1245,1248,1250,1252,1254,1256,1258,1261,1263,1266],{"class":70,"line":470},[68,1237,667],{"class":434},[68,1239,1240],{"class":85}," \u003C",[68,1242,679],{"class":187},[68,1244,682],{"class":187},[68,1246,1247],{"class":85},"={",[68,1249,947],{"class":240},[68,1251,693],{"class":85},[68,1253,696],{"class":187},[68,1255,685],{"class":85},[68,1257,701],{"class":99},[68,1259,1260],{"class":103},"fixed top-0 left-0 w-4 h-4 rounded-full bg-white pointer-events-none z-50 mix-blend-difference",[68,1262,701],{"class":99},[68,1264,1265],{"class":85}," \u002F>",[68,1267,110],{"class":99},[68,1269,1270],{"class":70,"line":485},[68,1271,812],{"class":85},[10,1273,1274,1275,1278],{},"Using ",[37,1276,1277],{},"gsap.quickTo"," bypasses the overhead of traditional tween parsing. It instantiates an optimised internal pipe dedicated solely to updating a single CSS attribute.",[10,1280,1281],{},"By applying a slight delay to the lag speed on a secondary canvas ring element running right behind the main dot pointer, you create an elegant trailing kinetic effect that makes the site interface feel immediate and organic.",[23,1283,1285],{"id":1284},"trade-offs-and-what-doesnt-work","Trade-offs and what doesn't work",[10,1287,1288],{},"The biggest trade-off with a motion-first architecture is the absolute destruction of structural SEO layouts if you over-rely on JavaScript-driven element generation. If an asset container requires a completed GSAP timeline run just to insert its text contents or clear out a hidden class node, automated search engine scrapers will often index the page as a completely blank shell. Your markup must remain readable and perfectly structured in the raw server-side HTML stream; animation should only ever alter the visual orientation properties like transform, opacity, and scale.",[10,1290,1291],{},"Furthermore, combining heavy scroll pinning with standard CSS scroll-snapping mechanics is a recipe for disaster. The two layout architectures use conflicting calculation frameworks: CSS snap works natively via the browser's thread-level scroll position controller, while ScrollTrigger overrides the viewport rendering bounds using synthetic margins and padding injection to force the window into place. Trying to run both inside the same parent tree will result in violent, unfixable page stuttering on mobile iOS devices.",[23,1293,1295],{"id":1294},"closing-take","Closing take",[10,1297,1298,1299,1302],{},"GSAP and ScrollTrigger unlock unparalleled creative capability for landing pages, but motion should always serve as an enhancement to clean information design, never a replacement for it. Keep your layouts semantically sound from the server paint, protect your main thread by decoupling mouse loops via native pipe targets, and don't make your visitors sit through a five-second loading animation just to read a paragraph of text. The full implementation lives on the ",[14,1300,1301],{"href":16},"Noir Studio project page"," if you want to inspect how all the pieces fit together.",[1304,1305,1306],"style",{},"html pre.shiki code .sbD-w, html code.shiki .sbD-w{--shiki-default:#51597D;--shiki-default-font-style:italic}html pre.shiki code .su0L4, html code.shiki .su0L4{--shiki-default:#7DCFFF}html pre.shiki code .sgJMe, html code.shiki .sgJMe{--shiki-default:#9ABDF5}html pre.shiki code .sySf4, html code.shiki .sySf4{--shiki-default:#0DB9D7}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sPY7s, html code.shiki .sPY7s{--shiki-default:#9ECE6A}html pre.shiki code .sE3pS, html code.shiki .sE3pS{--shiki-default:#C0CAF5}html pre.shiki code .s3R4Z, html code.shiki .s3R4Z{--shiki-default:#7AA2F7}html pre.shiki code .sd1Qi, html code.shiki .sd1Qi{--shiki-default:#BB9AF7}html pre.shiki code .sT800, html code.shiki .sT800{--shiki-default:#E0AF68}html pre.shiki code .syYvs, html code.shiki .syYvs{--shiki-default:#73DACA}html pre.shiki code .sN7LL, html code.shiki .sN7LL{--shiki-default:#9D7CD8;--shiki-default-font-style:italic}html pre.shiki code .sOJ5S, html code.shiki .sOJ5S{--shiki-default:#FF9E64}html pre.shiki code .sEsAJ, html code.shiki .sEsAJ{--shiki-default:#BB9AF7;--shiki-default-font-style:italic}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);}",{"title":64,"searchDepth":78,"depth":78,"links":1308},[1309,1310,1311,1312,1313],{"id":25,"depth":78,"text":26},{"id":46,"depth":78,"text":47},{"id":865,"depth":78,"text":866},{"id":1284,"depth":78,"text":1285},{"id":1294,"depth":78,"text":1295},"2026-03-14","A deep dive into building production-grade scroll animations with GSAP and Next.js, featuring smooth horizontal pinning and custom cursor mechanics.","md","🪄",{},"\u002Fblog\u002Fgsap-scrolltrigger-animated-landing",{"title":5,"description":1315},"blog\u002Fgsap-scrolltrigger-animated-landing",[1323,1324,1325],"GSAP","React","Next.js","jptW7yxtlrhIrNRziY08NuutRO9St3u7tFRZa3PaU3I",1779864409894]