From 3e9a6d5c4b1dd3b4966b38bb16b0b767a4e8149e Mon Sep 17 00:00:00 2001 From: Reef Date: Thu, 20 Aug 2026 10:32:19 +0000 Subject: [PATCH] feat: scroll-triggered section animations via IntersectionObserver + GSAP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a staggered fade-in/rise animation (y:30→0, opacity:0→1, stagger 0.08s, power2.out, 0.7s) for 5 section grids when they scroll into view: - .features-grid (The Concierge Standard — includes Free Trial card) - .faq-grid - .pricing-grid - .blog-grid - .final-cta .container Uses native IntersectionObserver (threshold 0.12, no extra CDN load). Children are pre-set to hidden state at page load to prevent flash, then animated once per section via GSAP. Animate-once guard per section via dataset flag. --- script.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/script.js b/script.js index c9c7957..2a861ea 100644 --- a/script.js +++ b/script.js @@ -623,3 +623,39 @@ var si = 0, blogState.currentPage = 1; renderBlog(); }); + + /* ---- Scroll-triggered section animations ---- */ + (function () { + var animated = {}; + var observer = new IntersectionObserver(function (entries) { + entries.forEach(function (entry) { + if (!entry.isIntersecting) return; + var grid = entry.target; + if (animated[grid.dataset.section]) return; + animated[grid.dataset.section] = true; + var items = grid.children; + gsap.fromTo( + items, + { y: 30, opacity: 0 }, + { + y: 0, + opacity: 1, + duration: 0.7, + ease: 'power2.out', + stagger: 0.08, + overwrite: 'auto' + } + ); + }); + }, { threshold: 0.12, rootMargin: '0px 0px -40px 0px' }); + + var grids = document.querySelectorAll( + '.features-grid, .faq-grid, .pricing-grid, .blog-grid, .final-cta .container' + ); + grids.forEach(function (g) { + g.dataset.section = g.className || 'anon'; + /* Pre-set children to hidden state so they start invisible */ + gsap.set(g.children, { y: 30, opacity: 0 }); + observer.observe(g); + }); + })();