(function() {
  const { useEffect } = React;

  // Re-scans after every render (no dep array): async data can replace
  // list items with new keys, producing fresh .reveal nodes that the
  // mount-time scan never observed — those would stay invisible forever.
  function useReveal(ref) {
    useEffect(() => {
      if (!ref.current) return;
      const obs = new IntersectionObserver(entries => {
        entries.forEach(e => {
          if (e.isIntersecting) {
            e.target.classList.add('in');
            obs.unobserve(e.target);
          }
        });
      }, { threshold: 0.12 });
      ref.current.querySelectorAll('.reveal:not(.in)').forEach(el => obs.observe(el));
      return () => obs.disconnect();
    });
  }

  window.useReveal = useReveal;
})();
