feat(motion): add scroll reveals and micro-interactions

This commit is contained in:
2026-09-07 00:19:07 +03:30
parent fa98f70b51
commit b0c9ad1296
20 changed files with 420 additions and 24 deletions

View File

@@ -0,0 +1,75 @@
'use client';
import { useEffect } from 'react';
const REVEAL_SELECTOR = '[data-reveal]';
export const MotionController = () => {
useEffect(() => {
const elements = Array.from(
document.querySelectorAll<HTMLElement>(REVEAL_SELECTOR),
);
if (!elements.length) return;
const reduceMotion = window.matchMedia(
'(prefers-reduced-motion: reduce)',
).matches;
document.querySelectorAll<HTMLElement>('[data-reveal-group]').forEach((group) => {
group.querySelectorAll<HTMLElement>(REVEAL_SELECTOR).forEach((element, index) => {
element.style.setProperty(
'--reveal-delay',
`${Math.min(index, 5) * 50}ms`,
);
});
});
if (reduceMotion) {
elements.forEach((element) => {
element.dataset.visible = 'true';
});
document.documentElement.dataset.motionReady = 'true';
return () => {
delete document.documentElement.dataset.motionReady;
};
}
const initialRevealBoundary = window.innerHeight * 0.92;
elements.forEach((element) => {
const bounds = element.getBoundingClientRect();
if (bounds.top <= initialRevealBoundary && bounds.bottom >= 0) {
element.dataset.visible = 'true';
}
});
document.documentElement.dataset.motionReady = 'true';
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
const element = entry.target as HTMLElement;
element.dataset.visible = 'true';
observer.unobserve(element);
});
},
{
rootMargin: '0px 0px -8% 0px',
threshold: 0.12,
},
);
elements.forEach((element) => {
if (element.dataset.visible !== 'true') observer.observe(element);
});
return () => {
observer.disconnect();
delete document.documentElement.dataset.motionReady;
};
}, []);
return null;
};