fix(home): resilient homepage animations + anchors + counters

Subagent 1 (Codex review pass):

LandingPageClient.tsx
- Guard GSAP .scroll-reveal loop to prevent 'missing target' warnings when no targets exist

brands/page.tsx
- Comment cleanup for selector scoping

tuxedo/page.tsx
- Add pageScopeRef + use as explicit GSAP context scope (fixes 'Invalid scope' warnings)
- Reduced-motion guard for counter animations
- Comments noting scope is now resilient

HeroSection.tsx
- Wrap sections in containerRef for proper GSAP context
- Reduced-motion support (final states shown immediately, no heavy anims)
- Counter animation: switched from textContent tween (unreliable, left values at 0) to proxy object pattern (val -> display)
- Added IO + rAF fallback if GSAP is unavailable
- Added section ids: #features, #stats, #reviews (matches nav anchors)
- Reduced blank scroll region (300vh -> 140vh on story section)
- Removed duplicate inline footer (LandingPageWrapper <Footer /> now sole source)

LandingPageWrapper.tsx
- Anchor comments noting ids added in HeroSection

Docs + scripts:
- docs/ADMIN_FUNCTIONALITY_CHECKLIST.md (admin app coverage notes)
- docs/pricing-assessment.md (pricing model notes)
- scripts/apply-admin-create-stop.js, check-stop-fns*.js, verify-stop-fns.js (admin stop creation tooling)
This commit is contained in:
2026-06-03 15:36:40 +00:00
parent 84ad60b4b0
commit 03ae372509
12 changed files with 775 additions and 98 deletions
+12 -3
View File
@@ -442,6 +442,9 @@ export default function TuxedoPage() {
const [brandBgColor, setBrandBgColor] = useState<string | null>(null);
const [brandTextColor, setBrandTextColor] = useState<string | null>(null);
// Scope ref for GSAP context (fixes "invalid scope"/missing targets by providing explicit scope; selectors now limited to page)
const pageScopeRef = useRef<HTMLDivElement>(null);
useEffect(() => {
async function load() {
const slug = "tuxedo";
@@ -504,6 +507,7 @@ export default function TuxedoPage() {
}
// ─── GSAP SCROLL ANIMATIONS ──────────────────────────────────────────────
// Now explicitly scoped + resilient guards (no more "invalid scope" warnings)
useEffect(() => {
if (typeof window === "undefined") return;
@@ -564,10 +568,15 @@ export default function TuxedoPage() {
);
});
// Counter animations
// Counter animations (uses reliable proxy like before; now also has reduced-motion guard for resilience)
const prefersReduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
const counterElements = gsap.utils.toArray<Element>(".counter-animate");
counterElements.forEach((el) => {
const target = parseInt(el.getAttribute("data-target") || "0", 10);
if (prefersReduced) {
el.textContent = target.toLocaleString();
return;
}
const obj = { val: 0 };
gsap.to(obj, {
val: target,
@@ -604,7 +613,7 @@ export default function TuxedoPage() {
}
);
});
});
}, pageScopeRef);
return () => ctx.revert();
}, []);
@@ -612,7 +621,7 @@ export default function TuxedoPage() {
const featuredProducts = products.slice(0, 3);
return (
<div className="min-h-screen bg-stone-50">
<div ref={pageScopeRef} className="min-h-screen bg-stone-50">
<BrandStylesProvider
primaryColor={brandPrimaryColor}
bgColor={brandBgColor}