Files
Alexey Pavlov b1c09aa53f feat: оживление сайта — обложки, hero-фон, статистика, анимации
- ArticleCard: реальные обложки с fallback на детерминированный градиент по id статьи
- HeroBackground: 3 анимированных blob'а + dot-grid + плавный fade к контенту
- Stats компонент: 4 карточки — статьи / минуты чтения / токены / просмотры
- Reveal компонент: IntersectionObserver-based fade-in при скролле, respect prefers-reduced-motion
- next.config: rewrites /uploads/* → engine, чтобы картинки работали с относительными путями
- На странице статьи — обложка над контентом
2026-05-31 09:17:08 +03:00

51 lines
1.5 KiB
JavaScript

'use client';
import { useEffect, useRef } from 'react';
/**
* Lightweight reveal-on-scroll. Без зависимостей, нативный IntersectionObserver.
* Дочерние элементы с классом `.reveal` плавно появятся.
*/
export default function Reveal({ children, className = '' }) {
const ref = useRef(null);
useEffect(() => {
if (!ref.current) return;
const io = new IntersectionObserver(
entries => {
entries.forEach(e => {
if (e.isIntersecting) {
e.target.classList.add('is-visible');
io.unobserve(e.target);
}
});
},
{ threshold: 0.1, rootMargin: '0px 0px -50px 0px' }
);
ref.current.querySelectorAll('.reveal').forEach(el => io.observe(el));
return () => io.disconnect();
}, []);
return (
<div ref={ref} className={className}>
<style jsx global>{`
.reveal {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.7s ease-out, transform 0.7s ease-out;
}
.reveal.is-visible {
opacity: 1;
transform: translateY(0);
}
.reveal-1 { transition-delay: 0.05s; }
.reveal-2 { transition-delay: 0.15s; }
.reveal-3 { transition-delay: 0.25s; }
@media (prefers-reduced-motion: reduce) {
.reveal { opacity: 1; transform: none; transition: none; }
}
`}</style>
{children}
</div>
);
}