import { S as SubscribeOptions, a as ConductorLane, F as FrameFn, g as getConductor$1 } from './conductor-CjhZukuO.js'; import { m as SensorBus, B as BudgetState, A as AdaptiveState, i as PressureState, b as MagneticOptions, d as PointerIntentOptions, p as VideoScrubberOptions, o as VideoScrubber, e as PointerIntentSensitivity, f as PointerIntentTuning, r as clamp01$1, s as damp$1, u as getAdaptiveQuality$1, v as getAnimationBudget$1, w as getFramePressure$1, x as getRendererHealth$1, y as getSensorBus$1, z as rayRectIntersect$1 } from './VideoScrubber-BbjCv9Nc.js'; export { M as MountCost, a as MountCostThresholds, b as SAFE_TO_MOUNT_COST, c as SceneGate, S as SceneState, U as UseSafeToMountOptions, d as UseSceneGateOptions, u as useSafeToMount, e as useSceneGate } from './useSceneGate-Ct7RvB1m.js'; import React, { RefObject } from 'react'; /** * A React hook that retains the unified `SensorBus` instance for the lifetime * of the component, automatically managing event-listener attachments and reference counting. * * ### Why this one returns the bus and its siblings return state * * `useAdaptiveQuality`, `useAnimationBudget` and `useFramePressure` all hand * back a state object directly, and the extra `.state` hop here looks like an * oversight. It is load-bearing. * * Those three change rarely: a tier flips, pressure emits at about 2Hz, and * re-rendering on that is cheap. Sensor state changes EVERY FRAME. Returning it * from a hook would mean either a snapshot that is stale the moment you hold * it, or a re-render per frame, which is the layout thrashing this whole * runtime exists to prevent. * * So the bus is the return value and `.state` is read fresh inside the frame * loop. This was queued for "consistency" in the 3.0 cleanup and reverted once * the contract below was read. Please do not flatten it. * * ### โšก Performance Optimization Contract * `useSensorBus` deliberately returns a **stable reference** to the bus object and **does not trigger React state re-renders** * when mouse moves, scroll offsets change, or window resizes. This is to avoid severe rendering bottlenecks (60 FPS layout thrashing). * Read and apply values inside the global `FrameConductor` frame loop instead. * * ### ๐Ÿ“š Usage Example: * ```tsx * import { useEffect, useRef } from "react"; * import { useSensorBus, getConductor } from "@vectorvesper/motion"; * * export function InteractiveCard() { * const elementRef = useRef(null); * const bus = useSensorBus(); * * useEffect(() => { * // Subscribe to the render lane of the central loop * return getConductor().subscribe("render", () => { * if (!elementRef.current) return; * * // Read fresh sensor data per-frame without re-rendering the component * const { x, y, speed } = bus.state.pointer; * const scale = 1 + Math.min(0.1, speed / 1000); * * elementRef.current.style.transform = `translate3d(${x * 0.05}px, ${y * 0.05}px, 0) scale(${scale})`; * }); * }, [bus]); * * return
Move pointer here
; * } * ``` * * @returns {SensorBus} The singleton SensorBus instance, with automatic lifecycle hook attachments. */ declare function useSensorBus(): SensorBus; /** * Subscribe to the frame-headroom governor. * * Returns a {@link BudgetState} snapshot that updates on tier changes and on * a slow ~2 Hz heartbeat (useful for HUD `avgFrameMs` displays). * * ### Tier contract * | tier | label | meaning | * |------|----------|---------------------------------------------| * | `0` | `"high"` | Frames are healthy โ€” run the full look | * | `1` | `"medium"`| Sustained drops below ~54 fps โ€” shed extras | * | `2` | `"low"` | Sustained drops below ~30 fps โ€” survival | * * ### Why React state * Tier changes are hysteresis-gated (fast to degrade, 8 s to recover), so * they are rare. Using React state here is intentional: conditional rendering * of expensive effects is exactly the right consumption pattern. Do **not** * read this inside a `requestAnimationFrame` loop โ€” poll `getAnimationBudget().state` * there instead. * * ### Usage patterns * * **1. Gate an expensive layer** * ```tsx * const { tier } = useAnimationBudget(); * {tier === 0 && } * ``` * * **2. Conditional CSS class** * ```tsx * const { tier } = useAnimationBudget(); *
* ``` * * **3. Live telemetry HUD** * ```tsx * const { avgFrameMs, slowRatio, label } = useAnimationBudget(); * {avgFrameMs.toFixed(1)} ms ยท {label} * ``` * * @returns The current {@link BudgetState} โ€” stable reference until the next * tier change or heartbeat tick. */ declare function useAnimationBudget(): BudgetState; interface InteractionScopeProps { children?: React.ReactNode; /** * A human-readable name for this region, shown in devtools. It has no effect * on behaviour, and it does not have to be unique. */ label?: string; /** * When this region counts as the one the visitor is working in. * * - `"pointer"` (default) activates while a pointer is down inside it. * - `true` holds it active. For a camera engaged by keyboard, an open modal. * - `false` never activates. * * Up to 2.x this was `boolean | undefined`, where `undefined` and `false` * meant different things: leaving it off gave you pointer activation, and * setting it to anything at all turned pointer activation off. Opting back * in therefore meant passing `undefined`, and an A/B toggle came out as * `active={on ? undefined : false}`, which is a shape no reader can guess. * * Three named values, no hidden state in the absence of a prop. * * Worth knowing: `true` and `false` run through React state, so they arrive a * render later than `"pointer"` does, which is a direct DOM listener. Fine * for a modal. Not a drop-in for a drag. */ active?: boolean | "pointer"; /** * Use the single child element instead of rendering a wrapper `div`. Use it * when an extra element would break a flex or grid layout. */ asChild?: boolean; className?: string; style?: React.CSSProperties; } /** * Mark the region the visitor is working in. * * ```tsx * * * * ``` * * While a pointer is down inside it, VV's own non-essential work *outside* the * region yields earlier when the page runs out of frame time. On a healthy * frame nothing changes at all. * * Reach for it around a draggable, a scrubber, or an interactive canvas that * shares a page with other VV visuals. Two things it cannot do: pause another * library's animation, or undo GPU work that has already been submitted. It * only coordinates work registered with VV. * * Every `useTick` rendered inside joins this region automatically. Nesting * works, and the innermost provider wins. * * ## How activation tracks a drag * * Three details, each because the obvious version got it wrong: * * - **Pointer down is caught in the capture phase**, on the DOM node rather * than through a React prop. Drag code very often calls `stopPropagation()` * on pointerdown so an outer handler does not also react to the press. A * bubble-phase listener never runs when that happens, and the region would * silently do nothing in the exact case it is for. * - **The end of the drag is watched on `window`, not on the region.** A drag * routinely travels outside the element it started in, and that is still the * same drag. * - **Activation lasts until the last pointer lifts.** Pointers are counted by * id, so lifting a second finger does not end a one-finger drag. * * Pointer capture is deliberately not used. Calling `setPointerCapture` on this * wrapper would fight any child that captures the pointer for its own drag. */ declare function InteractionScope({ children, label, active, asChild, className, style, }: InteractionScopeProps): React.JSX.Element; /** The scope this subtree belongs to, or `null` outside any provider. */ declare function useInteractionScope(): string | null; interface UseTickOptions extends Omit { /** * Keep this work in the background even when it is rendered inside an * {@link InteractionScope} โ€” an ambient layer behind a gallery, say, which * should yield like everything else while the gallery is being used. */ background?: boolean; /** Skip subscribing entirely while false. Default `true`. */ enabled?: boolean; } /** * Run a function on the shared frame loop. * * ```tsx * useTick("render", (dt) => { * x.current += (target.current - x.current) * (1 - Math.exp(-18 * dt)); * el.current.style.transform = `translate3d(${x.current}px,0,0)`; * }); * ``` * * Prefer this over calling `getConductor().subscribe` from a component. The * callback is held in a ref, so it can read current props without * resubscribing on every render, and it joins the surrounding * {@link InteractionScope} on its own. * * The subscription is removed on unmount. Changing `lane`, `priority`, `hz` or * `background` resubscribes; changing the function alone does not. */ declare function useTick(lane: ConductorLane, fn: FrameFn, options?: UseTickOptions): void; /** * The complete quality signal: device floor, live budget, and what is * * const quality = useAdaptiveQuality(); * {quality.tier === 0 && } * {quality.reducedMotion ? : } * * Tier changes are rare (hysteresis + static floor), so this is React * state by design โ€” the gatekeeper for conditional rendering of expensive * work. `reasons` explains the device verdict for HUDs and support. */ declare function useAdaptiveQuality(): AdaptiveState; /** * What is currently eating the frame, and how sure the runtime is about it. * * ```tsx * const { source, confidence } = useFramePressure(); * * // Only reduce the scene when rendering is what is actually slow. * const dpr = source === "render" && confidence > 0.5 ? 1 : 2; * ``` * * Verdicts change slowly and are emitted at most twice a second, so React * state is the right shape here. Do not read it inside a frame callback โ€” * use `getFramePressure().state` there. * * Read the meaning of each `source` on {@link PressureState}. The short * version: `"runtime"` means shedding our own work will help, `"main-thread"` * means delay mounts rather than dropping quality, `"render"` means lower the * quality, and `"unknown"` means change nothing. * * Nothing in the runtime acts on this yet. It reports so that it can be * checked against real pages before it is allowed to make decisions. */ declare function useFramePressure(): PressureState; /** * Magnetic attraction with anticipation โ€” the element starts reaching * while the cursor is still approaching (PointerIntent confidence drives * pre-touch pull; proximity takes over up close). * * const { ref } = useMagneticIntent(); * * * Owns the element's inline `transform` while mounted (restored exactly on * unmount). Inactive on touch devices and under reduced motion โ€” this one * IS motion, so it fails open to a perfectly normal element. */ declare function useMagneticIntent(options?: MagneticOptions): { ref: RefObject; active: boolean; }; interface UsePointerIntentOptions extends PointerIntentOptions { /** * Callback fired when the pointer's intent state changes. */ onIntentChange?: (intent: boolean) => void; } interface UsePointerIntentReturn { /** * Ref to attach to the target DOM element. * * **Destructure this hook's result**, as the example below does. Reaching it * as `pointer.ref` is a lint error under the React Compiler rules, and it * poisons the sibling fields with it. See hybrid-ref.ts. */ ref: RefObject; /** * Boolean state that flips to true when the cursor trajectory is headed towards the element * at a sufficient speed, and false when it drifts away or slows down. Useful for triggering * React conditional pre-rendering or warming. */ intent: boolean; /** * Ref containing the smoothed time-to-impact confidence value (0 to 1). * Updated frame-accurately in the background WITHOUT triggering component re-renders. * Perfect for driving GPU-accelerated styling (e.g. magnetic pull, CSS variables, canvas distortion). */ confidenceRef: RefObject; } /** * A React hook that predicts whether the pointer is going to hover over an element *before* it arrives, * by casting the pointer's velocity vector forward and calculating time-to-impact against the element's bounding rect. * * Use this hook to hide the latency of expensive interactions (e.g., preloading 3D models, pre-compiling shaders, * fetching API data, warming up magnetic fields) 100ms - 300ms before the user actually hovers. * * ### ๐Ÿ“š Usage Example: * ```tsx * import { usePointerIntent } from "@vectorvesper/motion/react"; * * export function InteractiveCard() { * const { ref, intent, confidenceRef } = usePointerIntent({ * horizon: 0.4, // Cast velocity vector 400ms forward * extend: 15, // Inflate the target rect by 15px * minSpeed: 100, // Only predict if speed is > 100px/s * }); * * // confidenceRef is deliberately not state, so it must not be read during * // render. Read it from a frame callback or an event handler instead. * return ( *
* {intent && } * *
* ); * } * ``` * * @param {UsePointerIntentOptions} [options={}] Configuration options specifying predictive horizons, thresholds, and callbacks. * @returns {UsePointerIntentReturn} Object containing the hybrid ref, the reactive intent state, and the non-reactive confidence ref. */ declare function usePointerIntent(options?: UsePointerIntentOptions): UsePointerIntentReturn; /** * useImageTrail โ€” images spawn along the pointer's path and fade away: * the classic award-site gallery flourish. What the free tutorials leak, * this pools: a fixed set of nodes created once and recycled, each * flight animated with the Web Animations API โ€” no rAF loop, no GC churn, * nothing allocated per move. * * const { ref } = useImageTrail({ images: ["/a.jpg", "/b.jpg", โ€ฆ] }); *
โ€ฆ
* * Fails open (does nothing) on touch devices and under reduced motion. * * A *creative* hook, not a foundation one: it is deliberately self-contained * and does NOT join the shared conductor or SensorBus. Each flight is handed to * the Web Animations API, which runs on the compositor โ€” cheaper than a * per-frame conductor subscription would be for this effect. It needs nothing * else in the package to work. */ interface UseImageTrailOptions { /** Image sources, cycled in order. */ images: string[]; /** Rendered width of each trail image, px. Default 160. */ size?: number; /** Pointer distance between spawns, px. Default 90. */ spacing?: number; /** How long each image stays on screen, in ms. Default 900. */ duration?: number; /** Pool size = max simultaneously visible images. Default 10. */ maxActive?: number; } declare function useImageTrail(options: UseImageTrailOptions): { ref: RefObject; }; interface UseNumberTickerOptions { /** * How quickly the number reaches its target. Higher arrives sooner. * Default 6, which reads as a smooth deceleration. * * Not a duration: the value chases its target rather than running a fixed * timeline, which is what lets it retarget mid-flight without restarting. */ speed?: number; /** * Intl.NumberFormat options to style currencies, percentages, decimals, etc. */ format?: Intl.NumberFormatOptions; /** * Locale string for number formatting (e.g., "en-US", "de-DE"). * Defaults to the user's browser locale. */ locale?: string; /** * Prefix string to prepend directly to the formatted number output (e.g., "+", "> "). */ prefix?: string; /** * Suffix string to append directly to the formatted number output (e.g., "%", " ms", " units"). */ suffix?: string; } /** * A high-performance numbers ticker hook that animates count values smoothly toward a target target value. * Bypasses React state updates entirely, writing frame updates directly to the DOM node's `textContent` * to achieve 120 FPS performance with zero component re-renders. * * Automatically respects the `prefers-reduced-motion` media query, snaps instantly to the target, * and enforces tabular layout to prevent horizontal digit wobbling. * * ### ๐Ÿ“š Usage Example: * ```tsx * import { useNumberTicker } from "@vectorvesper/motion/react"; * * export function ScoreDisplay() { * const { ref } = useNumberTicker(8500, { * prefix: "+", * suffix: " pts", * format: { style: "decimal" } * }); * * return ; * } * ``` * * @param {number} value The target number value to count towards. * @param {UseNumberTickerOptions} [options={}] Formatting configuration, damping speeds, prefixes, and suffixes. * @returns {{ ref: RefObject }} A React ref to bind to the displaying DOM element. */ declare function useNumberTicker(value: number, options?: UseNumberTickerOptions): { ref: RefObject; }; type UseVideoScrubberOptions = Omit; interface UseVideoScrubberReturn { /** * Ref to attach to the `