{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "use-visibility-pause",
  "type": "registry:hook",
  "title": "useVisibilityPause",
  "description": "RAF loop that pauses offscreen (IntersectionObserver) and on hidden tabs. Required by every shader/canvas/R3F component.",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/hooks/use-visibility-pause/use-visibility-pause.ts",
      "type": "registry:hook",
      "target": "hooks/use-visibility-pause.ts",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\n/**\n * Drives a requestAnimationFrame loop that automatically pauses when the\n * element scrolls offscreen (IntersectionObserver) or the tab is hidden\n * (document.visibilitychange). Mandatory for every shader/canvas/R3F\n * component — offscreen WebGL must never burn GPU time.\n *\n * `tick` receives elapsed seconds since the loop started. Offscreen and\n * hidden-tab pauses are excluded (the clock resumes where it stopped), but\n * changing the `paused` option restarts the loop and the clock from 0 —\n * one-shot reveals rely on that to replay; continuous effects that must not\n * jump on un-pause should carry their own time offset across the toggle.\n * Returns a ref to attach to the observed element.\n */\nexport function useVisibilityPause<T extends HTMLElement>(\n  tick: (elapsedSeconds: number) => void,\n  { paused = false }: { paused?: boolean } = {}\n): React.RefObject<T | null> {\n  const ref = React.useRef<T>(null);\n  const tickRef = React.useRef(tick);\n  tickRef.current = tick;\n  // Accumulated running time survives effect re-runs (e.g. toggling `paused`),\n  // so un-pausing resumes the clock instead of snapping back to 0.\n  const elapsedRef = React.useRef(0);\n\n  React.useEffect(() => {\n    const el = ref.current;\n    if (!el) return;\n\n    let raf = 0;\n    let running = false;\n    let visible = false;\n    let last = 0;\n\n    const loop = (now: number) => {\n      if (!running) return;\n      // Clamp at 0: a rAF timestamp can predate the performance.now() that\n      // armed the loop by a fraction of a frame.\n      elapsedRef.current += Math.max(0, now - last) / 1000;\n      last = now;\n      tickRef.current(elapsedRef.current);\n      raf = requestAnimationFrame(loop);\n    };\n\n    const sync = () => {\n      const shouldRun = visible && !document.hidden && !paused;\n      if (shouldRun && !running) {\n        running = true;\n        last = performance.now();\n        raf = requestAnimationFrame(loop);\n      } else if (!shouldRun && running) {\n        running = false;\n        cancelAnimationFrame(raf);\n      }\n    };\n\n    const io = new IntersectionObserver(([entry]) => {\n      visible = entry.isIntersecting;\n      sync();\n    });\n    io.observe(el);\n    document.addEventListener(\"visibilitychange\", sync);\n\n    return () => {\n      running = false;\n      cancelAnimationFrame(raf);\n      io.disconnect();\n      document.removeEventListener(\"visibilitychange\", sync);\n    };\n  }, [paused]);\n\n  return ref;\n}\n"
    }
  ]
}