// hero.jsx — Hero + анимированная инфографика круговорота
const { useEffect, useRef, useState } = React;
// useInView — простой хук, чтобы стартовать анимацию при появлении
function useInView(threshold = 0.15) {
const ref = useRef(null);
const [seen, setSeen] = useState(false);
useEffect(() => {
if (!ref.current || seen) return;
// First, check if element is already in view on mount (e.g. hero above the fold)
const rect = ref.current.getBoundingClientRect();
const vh = window.innerHeight || 800;
if (rect.top < vh && rect.bottom > 0) {
setSeen(true);
return;
}
const io = new IntersectionObserver(([e]) => {
if (e.isIntersecting) {setSeen(true);io.disconnect();}
}, { threshold });
io.observe(ref.current);
return () => io.disconnect();
}, [seen, threshold]);
return [ref, seen];
}
window.useInView = useInView;
function CycleDiagram() {
// Анимированный круговорот: 4 узла на окружности, вращается медленно
const nodes = [
{ label: "Очистки", sub: "0,5–1 кг / сутки", angle: -90, color: "var(--accent)" },
{ label: "Черви", sub: "Eisenia fetida", angle: 0, color: "var(--accent-2)" },
{ label: "Биогумус", sub: "Премиум-удобрение", angle: 90, color: "var(--accent)" },
{ label: "Урожай", sub: "+25–40% к норме", angle: 180, color: "var(--accent-2)" }];
return (
{/* HTML-узлы поверх SVG — для нормальной типографики */}
{nodes.map((n, i) => {
const rad = n.angle * Math.PI / 180;
const x = 50 + 44 * Math.cos(rad);
const y = 50 + 44 * Math.sin(rad);
return (
);
})}
);
}
window.CycleDiagram = CycleDiagram;
function StatCounter({ to, suffix = "", duration = 1400, decimals = 0, delay = 0 }) {
const [val, setVal] = useState(0);
const ref = useRef(null);
const [started, setStarted] = useState(false);
useEffect(() => {
if (started || !ref.current) return;
const el = ref.current;
const start = () => {
if (started) return;
setStarted(true);
};
const rect = el.getBoundingClientRect();
const vh = window.innerHeight || 800;
// Already visible? Start now.
if (rect.top < vh + 200 && rect.bottom > -200) {
const t = setTimeout(start, delay);
return () => clearTimeout(t);
}
const io = new IntersectionObserver(([e]) => {
if (e.isIntersecting) {start();io.disconnect();}
}, { threshold: 0.1 });
io.observe(el);
return () => io.disconnect();
}, [started, delay]);
useEffect(() => {
if (!started) return;
const t0 = performance.now();
let raf;
const step = (t) => {
const p = Math.min(1, (t - t0) / duration);
const eased = 1 - Math.pow(1 - p, 3);
setVal(to * eased);
if (p < 1) raf = requestAnimationFrame(step);
};
raf = requestAnimationFrame(step);
return () => cancelAnimationFrame(raf);
}, [started, to, duration]);
const formatted = decimals ?
val.toFixed(decimals) :
Math.round(val).toLocaleString("ru-RU");
return {formatted}{suffix};
}
window.StatCounter = StatCounter;
function Hero({ heroTitle, heroSub, logoVariant }) {
const [ref, seen] = useInView(0);
return (
домашняя вермиферма · ekokrug.ru
{heroTitle}
{heroSub}
Очистки
Черви работают
Биогумус
Вермичай
{/* Декоративные фоновые элементы */}
);
}
window.Hero = Hero;