"use client"; import React, { createContext, useContext, useEffect, useMemo, useRef } from "react"; import Lenis from "lenis"; import { useMotionValue, type MotionValue } from "motion/react"; type ScrollContextValue = { lenis: Lenis | null; scrollY: MotionValue; // in px progress: MotionValue; // 0..1 for whole page }; const ScrollContext = createContext(null); type LenisScrollEvent = { scroll: number; limit: number; progress?: number; }; export function useScrollContext() { const ctx = useContext(ScrollContext); if (!ctx) { throw new Error("useScrollContext must be used within "); } return ctx; } export function LenisProvider({ children }: { children: React.ReactNode }) { const lenisRef = useRef(null); const scrollY = useMotionValue(0); const progress = useMotionValue(0); useEffect(() => { // Respect user preferences const prefersReducedMotion = typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; const lenis = new Lenis({ // Softer, less "floaty" feel smoothWheel: !prefersReducedMotion, syncTouch: true, duration: 0.7, // was 1.2 easing: (t: number) => 1 - Math.pow(1 - t, 2.4), // gentler ease-out wheelMultiplier: 0.9, // slightly reduce wheel amplitude // Use native on reduced motion wrapper: undefined, content: undefined, // If reduced motion, disable smoothing entirely lerp: prefersReducedMotion ? 1 : 0.22, // was 0.1 (heavier smoothing) }); lenisRef.current = lenis; const onScroll = (e: unknown) => { const { scroll, limit, progress: p } = (e as LenisScrollEvent); scrollY.set(scroll); // Some versions of Lenis may not send progress, compute fallback if needed if (typeof p === "number" && Number.isFinite(p)) { progress.set(p); } else { const fallback = limit > 0 ? Math.min(1, Math.max(0, scroll / limit)) : 0; progress.set(fallback); } }; lenis.on("scroll", onScroll); let rafId = 0; const raf = (time: number) => { lenis.raf(time); rafId = requestAnimationFrame(raf); }; rafId = requestAnimationFrame(raf); // Initialize values onScroll({ scroll: window.scrollY, limit: Math.max(0, document.documentElement.scrollHeight - window.innerHeight), progress: (document.documentElement.scrollHeight - window.innerHeight) > 0 ? window.scrollY / (document.documentElement.scrollHeight - window.innerHeight) : 0, }); return () => { cancelAnimationFrame(rafId); lenis.off("scroll", onScroll); lenis.destroy(); lenisRef.current = null; }; }, [progress, scrollY]); const value = useMemo(() => { return { lenis: lenisRef.current, scrollY, progress, }; }, [scrollY, progress]); return {children}; }