import { gsap } from 'gsap'; /** A seekable GSAP timeline or tween. Both expose `.time()`, * `.duration()`, `.pause()`, and `.kill()`. */ type GsapPlayhead = gsap.core.Timeline | gsap.core.Tween; /** * Options for {@link useLoupeGsap}. */ type UseLoupeGsapOptions = { /** * Build the GSAP timeline (or tween). Receives the loaded `gsap` * instance so you don't have to import it yourself, and must * return the timeline/tween you want Loupe to drive. * * The timeline is created with its own clock disabled — Loupe's * `time` becomes the single source of truth. Build it the way you * always would (`gsap.timeline().to(...).from(...)`); the hook * pauses it for you. * * @example * ```ts * build: (gsap) => * gsap.timeline() * .from('.title', { y: 40, opacity: 0, duration: 0.5 }) * .from('.subtitle', { opacity: 0, duration: 0.4 }, '-=0.2') * ``` */ build: (g: typeof gsap) => GsapPlayhead; /** * Optional element to scope GSAP selector text (`'.title'`) and * cleanup to. When set, the timeline is built inside a * `gsap.context(fn, scope)` so selectors only match descendants * and every tween is reverted on unmount/rebuild. Strongly * recommended in React. Pass the ref's `.current`. */ scope?: Element | null; /** * Rebuild the timeline when any of these change (same contract as * a `useEffect` dep array). Defaults to `[]` — built once. */ deps?: ReadonlyArray; /** * Wrap the playhead at the timeline's duration boundary so it * loops with Loupe's own phase loop. Default `true`. Set `false` * to clamp at the end instead (the timeline holds its last frame * past its duration). */ loop?: boolean; }; type UseLoupeGsapResult = { /** The built GSAP timeline/tween, once ready. Null on the first * render before the dynamic import resolves. */ timeline: GsapPlayhead | null; }; /** * Drive a GSAP timeline's playhead from the nearest * `TimelineProvider`'s `time` MotionValue. * * GSAP's own ticker is never used — the timeline is paused on * creation and every position comes from Loupe's deterministic * clock. When Loupe plays, the timeline scrubs forward; when Loupe * pauses, it freezes on the current position; scrubbing the panel * scrubs the timeline. That makes the same animation reproducible * for thumbnails, still renders, and review. * * `gsap` is an optional peer dependency — install it in your app: * `npm i gsap`. It's dynamic-imported so consumers who don't touch * the `/gsap` subpath never pay for it. * * @example * ```tsx * function Hero() { * const scopeRef = useRef(null); * useLoupeGsap({ * scope: scopeRef.current, * deps: [scopeRef.current], * build: (gsap) => * gsap.timeline() * .from('.title', { y: 40, opacity: 0, duration: 0.5 }) * .from('.cta', { opacity: 0, duration: 0.4 }, '-=0.2'), * }); * return ( *
*

Loupe

* *
* ); * } * ``` */ declare function useLoupeGsap(opts: UseLoupeGsapOptions): UseLoupeGsapResult; export { type UseLoupeGsapOptions, type UseLoupeGsapResult, useLoupeGsap };