/* global React */
function Reveal({ children, delay = 0, className = "", as = "div", style }) {
const ref = React.useRef(null);
React.useEffect(() => {
const el = ref.current;
if (!el) return;
const io = new IntersectionObserver(
(entries) => {
entries.forEach((e) => {
if (e.isIntersecting) {
el.classList.add("in");
io.unobserve(el);
}
});
},
{ threshold: 0.12 }
);
io.observe(el);
return () => io.disconnect();
}, []);
const Tag = as;
const cls = `reveal ${delay ? `reveal-delay-${delay}` : ""} ${className}`.trim();
return {children};
}
function Parallax({ children, speed = 0.1 }) {
const ref = React.useRef(null);
React.useEffect(() => {
const el = ref.current; if (!el) return;
let raf;
const onScroll = () => {
cancelAnimationFrame(raf);
raf = requestAnimationFrame(() => {
const r = el.getBoundingClientRect();
const c = (r.top + r.height / 2) - window.innerHeight / 2;
el.style.transform = `translate3d(0, ${-c * speed}px, 0)`;
});
};
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
return () => { window.removeEventListener("scroll", onScroll); cancelAnimationFrame(raf); };
}, [speed]);
return
{children}
;
}
function Counter({ to, suffix = "" }) {
const [v, setV] = React.useState(0);
const ref = React.useRef(null);
React.useEffect(() => {
const el = ref.current; if (!el) return;
const io = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting) {
const start = performance.now();
const dur = 1200;
const tick = (t) => {
const p = Math.min(1, (t - start) / dur);
const eased = 1 - Math.pow(1 - p, 3);
setV(Math.round(to * eased));
if (p < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
io.unobserve(el);
}
});
}, { threshold: 0.5 });
io.observe(el);
return () => io.disconnect();
}, [to]);
return {v}{suffix};
}
function ScrollProgress() {
const [p, setP] = React.useState(0);
React.useEffect(() => {
const onScroll = () => {
const h = document.documentElement;
const max = h.scrollHeight - h.clientHeight;
setP(max > 0 ? window.scrollY / max : 0);
};
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, []);
return ;
}
function useElementScrollProgress(ref) {
const [p, setP] = React.useState(0);
React.useEffect(() => {
const onScroll = () => {
const el = ref.current; if (!el) return;
const r = el.getBoundingClientRect();
const vh = window.innerHeight;
const total = r.height + vh;
const seen = vh - r.top;
setP(Math.max(0, Math.min(1, seen / total)));
};
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, [ref]);
return p;
}
window.Reveal = Reveal;
window.Parallax = Parallax;
window.Counter = Counter;
window.ScrollProgress = ScrollProgress;
window.useElementScrollProgress = useElementScrollProgress;