Files
aigrowthtech/src/components/reveal-observer.tsx
T
Alejandro Veras b8cf7cfbcb Build the full site from the copy decks
Adds About, Contact, Services (index + 8) and Industries (index + 7),
generated from structured JSON converted out of the .docx copy decks
by scripts/convert-docs.py. Shared header with dropdowns, footer,
contact form (mailto until a backend exists), FAQ/breadcrumb JSON-LD,
sitemap and robots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 22:54:50 -04:00

51 lines
1.4 KiB
TypeScript

"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<HTMLElement>(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;
}