/* global React */
/* Hook: reveal on scroll */
function useReveal() {
const ref = React.useRef(null);
React.useEffect(() => {
const el = ref.current;
if (!el) return;
// immediate check: if already in viewport, reveal now (skip transition)
const r = el.getBoundingClientRect();
if (r.top < window.innerHeight && r.bottom > 0) {
el.style.transition = "none";
el.classList.add("in");
// force reflow then restore transition
void el.offsetWidth;
requestAnimationFrame(() => {
el.style.transition = "";
});
return;
}
const io = new IntersectionObserver(
(entries) => {
entries.forEach((e) => {
if (e.isIntersecting) {
e.target.classList.add("in");
io.unobserve(e.target);
}
});
},
{ threshold: 0.12, rootMargin: "0px 0px -60px 0px" }
);
io.observe(el);
// fallback: ensure visible within 800ms even if IO fails
const fallback = setTimeout(() => {
if (el && !el.classList.contains("in")) el.classList.add("in");
}, 800);
return () => { io.disconnect(); clearTimeout(fallback); };
}, []);
return ref;
}
function Reveal({ as: Tag = "div", className = "", delay, scale, stagger, children, ...rest }) {
const ref = useReveal();
const cls = [
scale ? "reveal-scale" : (stagger ? "reveal-stagger" : "reveal"),
delay ? `delay-${delay}` : "",
className,
].filter(Boolean).join(" ");
return