"use client"; import { useEffect } from "react"; const SELECTOR = "[data-reveal], [data-reveal-group]"; /** * Marks `[data-reveal]` / `[data-reveal-group]` elements with `data-in` as they scroll into view. * Render it as the last child of a page: its effect then runs only after the page's markup has * hydrated, so touching those elements can't cause a hydration mismatch. */ export default function RevealObserver() { useEffect(() => { const io = new IntersectionObserver( (entries) => { for (const entry of entries) { if (entry.isIntersecting) { (entry.target as HTMLElement).dataset.in = ""; io.unobserve(entry.target); } } }, { rootMargin: "0px 0px -12% 0px", threshold: 0.12 }, ); const watch = (root: ParentNode) => root.querySelectorAll(SELECTOR).forEach((el) => { if (!("in" in el.dataset)) io.observe(el); }); watch(document); const mo = new MutationObserver((records) => { for (const r of records) { r.addedNodes.forEach((n) => { if (n instanceof HTMLElement) { if (n.matches(SELECTOR) && !("in" in n.dataset)) io.observe(n); watch(n); } }); } }); mo.observe(document.body, { childList: true, subtree: true }); return () => { io.disconnect(); mo.disconnect(); }; }, []); return null; }